xref: /PHP-5.5/sapi/fpm/fpm/fpm_status.c (revision 68eb608f)
1 
2 	/* $Id$ */
3 	/* (c) 2009 Jerome Loyet */
4 
5 #include "php.h"
6 #include "SAPI.h"
7 #include <stdio.h>
8 
9 #include "fpm_config.h"
10 #include "fpm_scoreboard.h"
11 #include "fpm_status.h"
12 #include "fpm_clock.h"
13 #include "fpm_scoreboard.h"
14 #include "zlog.h"
15 #include "fpm_atomic.h"
16 #include "fpm_conf.h"
17 #include "fpm_php.h"
18 #include <ext/standard/html.h>
19 
20 static char *fpm_status_uri = NULL;
21 static char *fpm_status_ping_uri = NULL;
22 static char *fpm_status_ping_response = NULL;
23 
24 
fpm_status_init_child(struct fpm_worker_pool_s * wp)25 int fpm_status_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
26 {
27 	if (!wp || !wp->config) {
28 		zlog(ZLOG_ERROR, "unable to init fpm_status because conf structure is NULL");
29 		return -1;
30 	}
31 
32 	if (wp->config->pm_status_path) {
33 		fpm_status_uri = strdup(wp->config->pm_status_path);
34 	}
35 
36 	if (wp->config->ping_path) {
37 		if (!wp->config->ping_response) {
38 			zlog(ZLOG_ERROR, "[pool %s] ping is set (%s) but ping.response is not set.", wp->config->name, wp->config->ping_path);
39 			return -1;
40 		}
41 		fpm_status_ping_uri = strdup(wp->config->ping_path);
42 		fpm_status_ping_response = strdup(wp->config->ping_response);
43 	}
44 
45 	return 0;
46 }
47 /* }}} */
48 
fpm_status_handle_request(TSRMLS_D)49 int fpm_status_handle_request(TSRMLS_D) /* {{{ */
50 {
51 	struct fpm_scoreboard_s scoreboard, *scoreboard_p;
52 	struct fpm_scoreboard_proc_s proc;
53 	char *buffer, *time_format, time_buffer[64];
54 	time_t now_epoch;
55 	int full, encode;
56 	char *short_syntax, *short_post;
57 	char *full_pre, *full_syntax, *full_post, *full_separator;
58 
59 	if (!SG(request_info).request_uri) {
60 		return 0;
61 	}
62 
63 	/* PING */
64 	if (fpm_status_ping_uri && fpm_status_ping_response && !strcmp(fpm_status_ping_uri, SG(request_info).request_uri)) {
65 		fpm_request_executing();
66 		sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC);
67 		sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC);
68 		sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC);
69 		SG(sapi_headers).http_response_code = 200;
70 
71 		/* handle HEAD */
72 		if (SG(request_info).headers_only) {
73 			return 1;
74 		}
75 
76 		PUTS(fpm_status_ping_response);
77 		return 1;
78 	}
79 
80 	/* STATUS */
81 	if (fpm_status_uri && !strcmp(fpm_status_uri, SG(request_info).request_uri)) {
82 		fpm_request_executing();
83 
84 		scoreboard_p = fpm_scoreboard_get();
85 		if (!scoreboard_p) {
86 			zlog(ZLOG_ERROR, "status: unable to find or access status shared memory");
87 			SG(sapi_headers).http_response_code = 500;
88 			sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC);
89 			sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC);
90 			sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC);
91 			PUTS("Internal error. Please review log file for errors.");
92 			return 1;
93 		}
94 
95 		if (!fpm_spinlock(&scoreboard_p->lock, 1)) {
96 			zlog(ZLOG_NOTICE, "[pool %s] status: scoreboard already in used.", scoreboard_p->pool);
97 			SG(sapi_headers).http_response_code = 503;
98 			sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC);
99 			sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC);
100 			sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC);
101 			PUTS("Server busy. Please try again later.");
102 			return 1;
103 		}
104 		/* copy the scoreboard not to bother other processes */
105 		scoreboard = *scoreboard_p;
106 		fpm_unlock(scoreboard_p->lock);
107 
108 		if (scoreboard.idle < 0 || scoreboard.active < 0) {
109 			zlog(ZLOG_ERROR, "[pool %s] invalid status values", scoreboard.pool);
110 			SG(sapi_headers).http_response_code = 500;
111 			sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC);
112 			sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC);
113 			sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC);
114 			PUTS("Internal error. Please review log file for errors.");
115 			return 1;
116 		}
117 
118 		/* send common headers */
119 		sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC);
120 		sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC);
121 		SG(sapi_headers).http_response_code = 200;
122 
123 		/* handle HEAD */
124 		if (SG(request_info).headers_only) {
125 			return 1;
126 		}
127 
128 		/* full status ? */
129 		full = (fpm_php_get_string_from_table("_GET", "full" TSRMLS_CC) != NULL);
130 		short_syntax = short_post = NULL;
131 		full_separator = full_pre = full_syntax = full_post = NULL;
132 		encode = 0;
133 
134 		/* HTML */
135 		if (fpm_php_get_string_from_table("_GET", "html" TSRMLS_CC)) {
136 			sapi_add_header_ex(ZEND_STRL("Content-Type: text/html"), 1, 1 TSRMLS_CC);
137 			time_format = "%d/%b/%Y:%H:%M:%S %z";
138 			encode = 1;
139 
140 			short_syntax =
141 				"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
142 				"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
143 				"<head><title>PHP-FPM Status Page</title></head>\n"
144 				"<body>\n"
145 				"<table>\n"
146 					"<tr><th>pool</th><td>%s</td></tr>\n"
147 					"<tr><th>process manager</th><td>%s</td></tr>\n"
148 					"<tr><th>start time</th><td>%s</td></tr>\n"
149 					"<tr><th>start since</th><td>%lu</td></tr>\n"
150 					"<tr><th>accepted conn</th><td>%lu</td></tr>\n"
151 #ifdef HAVE_FPM_LQ
152 					"<tr><th>listen queue</th><td>%u</td></tr>\n"
153 					"<tr><th>max listen queue</th><td>%u</td></tr>\n"
154 					"<tr><th>listen queue len</th><td>%d</td></tr>\n"
155 #endif
156 					"<tr><th>idle processes</th><td>%d</td></tr>\n"
157 					"<tr><th>active processes</th><td>%d</td></tr>\n"
158 					"<tr><th>total processes</th><td>%d</td></tr>\n"
159 					"<tr><th>max active processes</th><td>%d</td></tr>\n"
160 					"<tr><th>max children reached</th><td>%u</td></tr>\n"
161 					"<tr><th>slow requests</th><td>%lu</td></tr>\n"
162 				"</table>\n";
163 
164 			if (!full) {
165 				short_post = "</body></html>";
166 			} else {
167 				full_pre =
168 					"<table border=\"1\">\n"
169 					"<tr>"
170 						"<th>pid</th>"
171 						"<th>state</th>"
172 						"<th>start time</th>"
173 						"<th>start since</th>"
174 						"<th>requests</th>"
175 						"<th>request duration</th>"
176 						"<th>request method</th>"
177 						"<th>request uri</th>"
178 						"<th>content length</th>"
179 						"<th>user</th>"
180 						"<th>script</th>"
181 #ifdef HAVE_FPM_LQ
182 						"<th>last request cpu</th>"
183 #endif
184 						"<th>last request memory</th>"
185 					"</tr>\n";
186 
187 				full_syntax =
188 					"<tr>"
189 						"<td>%d</td>"
190 						"<td>%s</td>"
191 						"<td>%s</td>"
192 						"<td>%lu</td>"
193 						"<td>%lu</td>"
194 						"<td>%lu</td>"
195 						"<td>%s</td>"
196 						"<td>%s%s%s</td>"
197 						"<td>%zu</td>"
198 						"<td>%s</td>"
199 						"<td>%s</td>"
200 #ifdef HAVE_FPM_LQ
201 						"<td>%.2f</td>"
202 #endif
203 						"<td>%zu</td>"
204 					"</tr>\n";
205 
206 				full_post = "</table></body></html>";
207 			}
208 
209 		/* XML */
210 		} else if (fpm_php_get_string_from_table("_GET", "xml" TSRMLS_CC)) {
211 			sapi_add_header_ex(ZEND_STRL("Content-Type: text/xml"), 1, 1 TSRMLS_CC);
212 			time_format = "%s";
213 			encode = 1;
214 
215 			short_syntax =
216 				"<?xml version=\"1.0\" ?>\n"
217 				"<status>\n"
218 				"<pool>%s</pool>\n"
219 				"<process-manager>%s</process-manager>\n"
220 				"<start-time>%s</start-time>\n"
221 				"<start-since>%lu</start-since>\n"
222 				"<accepted-conn>%lu</accepted-conn>\n"
223 #ifdef HAVE_FPM_LQ
224 				"<listen-queue>%u</listen-queue>\n"
225 				"<max-listen-queue>%u</max-listen-queue>\n"
226 				"<listen-queue-len>%d</listen-queue-len>\n"
227 #endif
228 				"<idle-processes>%d</idle-processes>\n"
229 				"<active-processes>%d</active-processes>\n"
230 				"<total-processes>%d</total-processes>\n"
231 				"<max-active-processes>%d</max-active-processes>\n"
232 				"<max-children-reached>%u</max-children-reached>\n"
233 				"<slow-requests>%lu</slow-requests>\n";
234 
235 				if (!full) {
236 					short_post = "</status>";
237 				} else {
238 					full_pre = "<processes>\n";
239 					full_syntax =
240 						"<process>"
241 							"<pid>%d</pid>"
242 							"<state>%s</state>"
243 							"<start-time>%s</start-time>"
244 							"<start-since>%lu</start-since>"
245 							"<requests>%lu</requests>"
246 							"<request-duration>%lu</request-duration>"
247 							"<request-method>%s</request-method>"
248 							"<request-uri>%s%s%s</request-uri>"
249 							"<content-length>%zu</content-length>"
250 							"<user>%s</user>"
251 							"<script>%s</script>"
252 #ifdef HAVE_FPM_LQ
253 							"<last-request-cpu>%.2f</last-request-cpu>"
254 #endif
255 							"<last-request-memory>%zu</last-request-memory>"
256 						"</process>\n"
257 					;
258 					full_post = "</processes>\n</status>";
259 				}
260 
261 			/* JSON */
262 		} else if (fpm_php_get_string_from_table("_GET", "json" TSRMLS_CC)) {
263 			sapi_add_header_ex(ZEND_STRL("Content-Type: application/json"), 1, 1 TSRMLS_CC);
264 			time_format = "%s";
265 
266 			short_syntax =
267 				"{"
268 				"\"pool\":\"%s\","
269 				"\"process manager\":\"%s\","
270 				"\"start time\":%s,"
271 				"\"start since\":%lu,"
272 				"\"accepted conn\":%lu,"
273 #ifdef HAVE_FPM_LQ
274 				"\"listen queue\":%u,"
275 				"\"max listen queue\":%u,"
276 				"\"listen queue len\":%d,"
277 #endif
278 				"\"idle processes\":%d,"
279 				"\"active processes\":%d,"
280 				"\"total processes\":%d,"
281 				"\"max active processes\":%d,"
282 				"\"max children reached\":%u,"
283 				"\"slow requests\":%lu";
284 
285 			if (!full) {
286 				short_post = "}";
287 			} else {
288 				full_separator = ",";
289 				full_pre = ", \"processes\":[";
290 
291 				full_syntax = "{"
292 					"\"pid\":%d,"
293 					"\"state\":\"%s\","
294 					"\"start time\":%s,"
295 					"\"start since\":%lu,"
296 					"\"requests\":%lu,"
297 					"\"request duration\":%lu,"
298 					"\"request method\":\"%s\","
299 					"\"request uri\":\"%s%s%s\","
300 					"\"content length\":%zu,"
301 					"\"user\":\"%s\","
302 					"\"script\":\"%s\","
303 #ifdef HAVE_FPM_LQ
304 					"\"last request cpu\":%.2f,"
305 #endif
306 					"\"last request memory\":%zu"
307 					"}";
308 
309 				full_post = "]}";
310 			}
311 
312 		/* TEXT */
313 		} else {
314 			sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC);
315 			time_format = "%d/%b/%Y:%H:%M:%S %z";
316 
317 			short_syntax =
318 				"pool:                 %s\n"
319 				"process manager:      %s\n"
320 				"start time:           %s\n"
321 				"start since:          %lu\n"
322 				"accepted conn:        %lu\n"
323 #ifdef HAVE_FPM_LQ
324 				"listen queue:         %u\n"
325 				"max listen queue:     %u\n"
326 				"listen queue len:     %d\n"
327 #endif
328 				"idle processes:       %d\n"
329 				"active processes:     %d\n"
330 				"total processes:      %d\n"
331 				"max active processes: %d\n"
332 				"max children reached: %u\n"
333 				"slow requests:        %lu\n";
334 
335 				if (full) {
336 					full_syntax =
337 						"\n"
338 						"************************\n"
339 						"pid:                  %d\n"
340 						"state:                %s\n"
341 						"start time:           %s\n"
342 						"start since:          %lu\n"
343 						"requests:             %lu\n"
344 						"request duration:     %lu\n"
345 						"request method:       %s\n"
346 						"request URI:          %s%s%s\n"
347 						"content length:       %zu\n"
348 						"user:                 %s\n"
349 						"script:               %s\n"
350 #ifdef HAVE_FPM_LQ
351 						"last request cpu:     %.2f\n"
352 #endif
353 						"last request memory:  %zu\n";
354 				}
355 		}
356 
357 		strftime(time_buffer, sizeof(time_buffer) - 1, time_format, localtime(&scoreboard.start_epoch));
358 		now_epoch = time(NULL);
359 		spprintf(&buffer, 0, short_syntax,
360 				scoreboard.pool,
361 				PM2STR(scoreboard.pm),
362 				time_buffer,
363 				now_epoch - scoreboard.start_epoch,
364 				scoreboard.requests,
365 #ifdef HAVE_FPM_LQ
366 				scoreboard.lq,
367 				scoreboard.lq_max,
368 				scoreboard.lq_len,
369 #endif
370 				scoreboard.idle,
371 				scoreboard.active,
372 				scoreboard.idle + scoreboard.active,
373 				scoreboard.active_max,
374 				scoreboard.max_children_reached,
375 				scoreboard.slow_rq);
376 
377 		PUTS(buffer);
378 		efree(buffer);
379 
380 		if (short_post) {
381 			PUTS(short_post);
382 		}
383 
384 		/* no need to test the var 'full' */
385 		if (full_syntax) {
386 			int i, first;
387 			size_t len;
388 			char *query_string;
389 			struct timeval duration, now;
390 #ifdef HAVE_FPM_LQ
391 			float cpu;
392 #endif
393 
394 			fpm_clock_get(&now);
395 
396 			if (full_pre) {
397 				PUTS(full_pre);
398 			}
399 
400 			first = 1;
401 			for (i=0; i<scoreboard_p->nprocs; i++) {
402 				if (!scoreboard_p->procs[i] || !scoreboard_p->procs[i]->used) {
403 					continue;
404 				}
405 				proc = *scoreboard_p->procs[i];
406 
407 				if (first) {
408 					first = 0;
409 				} else {
410 					if (full_separator) {
411 						PUTS(full_separator);
412 					}
413 				}
414 
415 				query_string = NULL;
416 				len = 0;
417 				if (proc.query_string[0] != '\0') {
418 					if (!encode) {
419 						query_string = proc.query_string;
420 					} else {
421 						query_string = php_escape_html_entities_ex((unsigned char *)proc.query_string, strlen(proc.query_string), &len, 1, ENT_HTML_IGNORE_ERRORS & ENT_COMPAT, NULL, 1 TSRMLS_CC);
422 					}
423 				}
424 
425 #ifdef HAVE_FPM_LQ
426 				/* prevent NaN */
427 				if (proc.cpu_duration.tv_sec == 0 && proc.cpu_duration.tv_usec == 0) {
428 					cpu = 0.;
429 				} else {
430 					cpu = (proc.last_request_cpu.tms_utime + proc.last_request_cpu.tms_stime + proc.last_request_cpu.tms_cutime + proc.last_request_cpu.tms_cstime) / fpm_scoreboard_get_tick() / (proc.cpu_duration.tv_sec + proc.cpu_duration.tv_usec / 1000000.) * 100.;
431 				}
432 #endif
433 
434 				if (proc.request_stage == FPM_REQUEST_ACCEPTING) {
435 					duration = proc.duration;
436 				} else {
437 					timersub(&now, &proc.accepted, &duration);
438 				}
439 				strftime(time_buffer, sizeof(time_buffer) - 1, time_format, localtime(&proc.start_epoch));
440 				spprintf(&buffer, 0, full_syntax,
441 					proc.pid,
442 					fpm_request_get_stage_name(proc.request_stage),
443 					time_buffer,
444 					now_epoch - proc.start_epoch,
445 					proc.requests,
446 					duration.tv_sec * 1000000UL + duration.tv_usec,
447 					proc.request_method[0] != '\0' ? proc.request_method : "-",
448 					proc.request_uri[0] != '\0' ? proc.request_uri : "-",
449 					query_string ? "?" : "",
450 					query_string ? query_string : "",
451 					proc.content_length,
452 					proc.auth_user[0] != '\0' ? proc.auth_user : "-",
453 					proc.script_filename[0] != '\0' ? proc.script_filename : "-",
454 #ifdef HAVE_FPM_LQ
455 					proc.request_stage == FPM_REQUEST_ACCEPTING ? cpu : 0.,
456 #endif
457 					proc.request_stage == FPM_REQUEST_ACCEPTING ? proc.memory : 0);
458 				PUTS(buffer);
459 				efree(buffer);
460 
461 				if (len > 0 && query_string) {
462 					efree(query_string);
463 				}
464 			}
465 
466 			if (full_post) {
467 				PUTS(full_post);
468 			}
469 		}
470 
471 		return 1;
472 	}
473 
474 	return 0;
475 }
476 /* }}} */
477 
478