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