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