diff --git a/sapi/fpm/fpm/fpm_status.c b/sapi/fpm/fpm/fpm_status.c index ff6ef68f3949..6ed342cfbdbc 100644 --- a/sapi/fpm/fpm/fpm_status.c +++ b/sapi/fpm/fpm/fpm_status.c @@ -2,6 +2,7 @@ #include "php.h" #include "zend_long.h" +#include "zend_smart_str.h" #include "SAPI.h" #include @@ -13,6 +14,7 @@ #include "fpm_atomic.h" #include "fpm_conf.h" #include "fpm_php.h" +#include "fpm_request.h" #include "ext/standard/html.h" #include "ext/json/php_json.h" @@ -122,17 +124,752 @@ int fpm_status_export_to_zval(zval *status) } /* }}} */ -int fpm_status_handle_request(void) /* {{{ */ +static void fpm_status_proc_get_duration_and_cpu( + struct fpm_scoreboard_proc_s *proc, + struct timeval *now, + struct timeval *duration, + float *cpu) { - struct fpm_scoreboard_s *scoreboard_p; + if (proc->request_stage == FPM_REQUEST_ACCEPTING) { + *duration = proc->duration; + } else { + timersub(now, &proc->accepted, duration); + } + + if (proc->cpu_duration.tv_sec == 0 && proc->cpu_duration.tv_usec == 0) { + *cpu = 0.; + } else { + *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.; + } +} + +static void fpm_status_handle_plaintext(struct fpm_scoreboard_s *scoreboard_p, int full) +{ + char *buffer; + char time_buffer[64]; + time_t now_epoch; + + sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1); + + now_epoch = time(NULL); + strftime(time_buffer, sizeof(time_buffer) - 1, "%d/%b/%Y:%H:%M:%S %z", localtime(&scoreboard_p->start_epoch)); + + spprintf(&buffer, 0, + "pool: %s\n" + "process manager: %s\n" + "start time: %s\n" + "start since: %lu\n" + "accepted conn: %lu\n" + "listen queue: %d\n" + "max listen queue: %d\n" + "listen queue len: %u\n" + "idle processes: %d\n" + "active processes: %d\n" + "total processes: %d\n" + "max active processes: %d\n" + "max children reached: %u\n" + "slow requests: %lu\n" + "memory peak: %zu\n", + scoreboard_p->pool, + PM2STR(scoreboard_p->pm), + time_buffer, + (unsigned long) (now_epoch - scoreboard_p->start_epoch), + scoreboard_p->requests, + scoreboard_p->lq, + scoreboard_p->lq_max, + scoreboard_p->lq_len, + scoreboard_p->idle, + scoreboard_p->active, + scoreboard_p->idle + scoreboard_p->active, + scoreboard_p->active_max, + scoreboard_p->max_children_reached, + scoreboard_p->slow_rq, + scoreboard_p->memory_peak); + + PUTS(buffer); + efree(buffer); + + if (!full) { + return; + } + + unsigned int i; + struct fpm_scoreboard_proc_s *proc; + struct timeval duration, now; + float cpu; + + fpm_clock_get(&now); + + for (i = 0; i < scoreboard_p->nprocs; i++) { + if (!scoreboard_p->procs[i].used) { + continue; + } + + proc = &scoreboard_p->procs[i]; + + fpm_status_proc_get_duration_and_cpu(proc, &now, &duration, &cpu); + + strftime(time_buffer, sizeof(time_buffer) - 1, "%d/%b/%Y:%H:%M:%S %z", localtime(&proc->start_epoch)); + + spprintf(&buffer, 0, + "\n" + "************************\n" + "pid: %d\n" + "state: %s\n" + "start time: %s\n" + "start since: %lu\n" + "requests: %lu\n" + "request duration: %lu\n" + "request method: %s\n" + "request URI: %s%s%s\n" + "content length: %zu\n" + "user: %s\n" + "script: %s\n" + "last request cpu: %.2f\n" + "last request memory: %zu\n", + (int) proc->pid, + fpm_request_get_stage_name(proc->request_stage), + time_buffer, + (unsigned long) (now_epoch - proc->start_epoch), + proc->requests, + (unsigned long) (duration.tv_sec * 1000000UL + duration.tv_usec), + proc->request_method[0] != '\0' ? proc->request_method : "-", + proc->request_uri[0] != '\0' ? proc->request_uri : "-", + proc->query_string[0] != '\0' ? "?" : "", + proc->query_string[0] != '\0' ? proc->query_string : "", + proc->content_length, + proc->auth_user[0] != '\0' ? proc->auth_user : "-", + proc->script_filename[0] != '\0' ? proc->script_filename : "-", + proc->request_stage == FPM_REQUEST_ACCEPTING ? cpu : 0., + proc->request_stage == FPM_REQUEST_ACCEPTING ? proc->memory : 0); + + PUTS(buffer); + efree(buffer); + } +} + +static void fpm_status_handle_html(struct fpm_scoreboard_s *scoreboard_p, int full) +{ + char *buffer; + char time_buffer[64]; + time_t now_epoch; + + sapi_add_header_ex(ZEND_STRL("Content-Type: text/html"), 1, 1); + + now_epoch = time(NULL); + strftime(time_buffer, sizeof(time_buffer) - 1, "%d/%b/%Y:%H:%M:%S %z", localtime(&scoreboard_p->start_epoch)); + + spprintf(&buffer, 0, + "\n" + "\n" + "PHP-FPM Status Page\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "
pool%s
process manager%s
start time%s
start since%lu
accepted conn%lu
listen queue%d
max listen queue%d
listen queue len%u
idle processes%d
active processes%d
total processes%d
max active processes%d
max children reached%u
slow requests%lu
memory peak%zu
\n", + scoreboard_p->pool, + PM2STR(scoreboard_p->pm), + time_buffer, + (unsigned long) (now_epoch - scoreboard_p->start_epoch), + scoreboard_p->requests, + scoreboard_p->lq, + scoreboard_p->lq_max, + scoreboard_p->lq_len, + scoreboard_p->idle, + scoreboard_p->active, + scoreboard_p->idle + scoreboard_p->active, + scoreboard_p->active_max, + scoreboard_p->max_children_reached, + scoreboard_p->slow_rq, + scoreboard_p->memory_peak); + + PUTS(buffer); + efree(buffer); + + if (!full) { + PUTS(""); + return; + } + + unsigned int i; + struct fpm_scoreboard_proc_s *proc; + struct timeval duration, now; + float cpu; + zend_string *tmp_request_uri_string, *tmp_query_string; + char *request_uri_string, *query_string; + + fpm_clock_get(&now); + + PUTS( + "\n" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "\n"); + + for (i = 0; i < scoreboard_p->nprocs; i++) { + if (!scoreboard_p->procs[i].used) { + continue; + } + proc = &scoreboard_p->procs[i]; + + request_uri_string = NULL; + tmp_request_uri_string = NULL; + if (proc->request_uri[0] != '\0') { + tmp_request_uri_string = php_escape_html_entities_ex( + (const unsigned char *) proc->request_uri, + strlen(proc->request_uri), 1, ENT_DISALLOWED | ENT_HTML_DOC_XML1 | ENT_COMPAT, + NULL, /* double_encode */ 1, /* quiet */ 0); + request_uri_string = ZSTR_VAL(tmp_request_uri_string); + } + + query_string = NULL; + tmp_query_string = NULL; + if (proc->query_string[0] != '\0') { + tmp_query_string = php_escape_html_entities_ex( + (const unsigned char *) proc->query_string, + strlen(proc->query_string), 1, ENT_DISALLOWED | ENT_HTML_DOC_XML1 | ENT_COMPAT, + NULL, /* double_encode */ 1, /* quiet */ 0); + if (tmp_query_string) { + query_string = ZSTR_VAL(tmp_query_string); + } + } + + fpm_status_proc_get_duration_and_cpu(proc, &now, &duration, &cpu); + + strftime(time_buffer, sizeof(time_buffer) - 1, "%d/%b/%Y:%H:%M:%S %z", localtime(&proc->start_epoch)); + + spprintf(&buffer, 0, + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "\n", + (int) proc->pid, + fpm_request_get_stage_name(proc->request_stage), + time_buffer, + (unsigned long) (now_epoch - proc->start_epoch), + proc->requests, + (unsigned long) (duration.tv_sec * 1000000UL + duration.tv_usec), + proc->request_method[0] != '\0' ? proc->request_method : "-", + request_uri_string ? request_uri_string : "-", + query_string ? "?" : "", + query_string ? query_string : "", + proc->content_length, + proc->auth_user[0] != '\0' ? proc->auth_user : "-", + proc->script_filename[0] != '\0' ? proc->script_filename : "-", + proc->request_stage == FPM_REQUEST_ACCEPTING ? cpu : 0., + proc->request_stage == FPM_REQUEST_ACCEPTING ? proc->memory : 0); + + PUTS(buffer); + efree(buffer); + + if (tmp_request_uri_string) { + zend_string_free(tmp_request_uri_string); + } + if (tmp_query_string) { + zend_string_free(tmp_query_string); + } + } + + PUTS("
pidstatestart timestart sincerequestsrequest durationrequest methodrequest uricontent lengthuserscriptlast request cpulast request memory
%d%s%s%lu%lu%lu%s%s%s%s%zu%s%s%.2f%zu
"); +} + +static void fpm_status_handle_xml(struct fpm_scoreboard_s *scoreboard_p, int full) +{ + char *buffer; + char time_buffer[64]; + time_t now_epoch; + + sapi_add_header_ex(ZEND_STRL("Content-Type: text/xml"), 1, 1); + + now_epoch = time(NULL); + strftime(time_buffer, sizeof(time_buffer) - 1, "%s", localtime(&scoreboard_p->start_epoch)); + + spprintf(&buffer, 0, + "\n" + "\n" + "%s\n" + "%s\n" + "%s\n" + "%lu\n" + "%lu\n" + "%d\n" + "%d\n" + "%u\n" + "%d\n" + "%d\n" + "%d\n" + "%d\n" + "%u\n" + "%lu\n" + "%zu\n", + scoreboard_p->pool, + PM2STR(scoreboard_p->pm), + time_buffer, + (unsigned long) (now_epoch - scoreboard_p->start_epoch), + scoreboard_p->requests, + scoreboard_p->lq, + scoreboard_p->lq_max, + scoreboard_p->lq_len, + scoreboard_p->idle, + scoreboard_p->active, + scoreboard_p->idle + scoreboard_p->active, + scoreboard_p->active_max, + scoreboard_p->max_children_reached, + scoreboard_p->slow_rq, + scoreboard_p->memory_peak); + + PUTS(buffer); + efree(buffer); + + if (!full) { + PUTS(""); + return; + } + + unsigned int i; struct fpm_scoreboard_proc_s *proc; - char *buffer, *time_format, time_buffer[64]; + struct timeval duration, now; + float cpu; + zend_string *tmp_request_uri_string, *tmp_query_string; + char *request_uri_string, *query_string; + + fpm_clock_get(&now); + + PUTS("\n"); + + for (i = 0; i < scoreboard_p->nprocs; i++) { + if (!scoreboard_p->procs[i].used) { + continue; + } + proc = &scoreboard_p->procs[i]; + + request_uri_string = NULL; + tmp_request_uri_string = NULL; + if (proc->request_uri[0] != '\0') { + tmp_request_uri_string = php_escape_html_entities_ex( + (const unsigned char *) proc->request_uri, + strlen(proc->request_uri), 1, ENT_DISALLOWED | ENT_HTML_DOC_XML1 | ENT_COMPAT, + NULL, /* double_encode */ 1, /* quiet */ 0); + request_uri_string = ZSTR_VAL(tmp_request_uri_string); + } + + query_string = NULL; + tmp_query_string = NULL; + if (proc->query_string[0] != '\0') { + tmp_query_string = php_escape_html_entities_ex( + (const unsigned char *) proc->query_string, + strlen(proc->query_string), 1, ENT_DISALLOWED | ENT_HTML_DOC_XML1 | ENT_COMPAT, + NULL, /* double_encode */ 1, /* quiet */ 0); + if (tmp_query_string) { + query_string = ZSTR_VAL(tmp_query_string); + } + } + + fpm_status_proc_get_duration_and_cpu(proc, &now, &duration, &cpu); + + strftime(time_buffer, sizeof(time_buffer) - 1, "%s", localtime(&proc->start_epoch)); + + spprintf(&buffer, 0, + "" + "%d" + "%s" + "%s" + "%lu" + "%lu" + "%lu" + "%s" + "%s%s%s" + "%zu" + "%s" + "" + "%.2f" + "%zu" + "\n", + (int) proc->pid, + fpm_request_get_stage_name(proc->request_stage), + time_buffer, + (unsigned long) (now_epoch - proc->start_epoch), + proc->requests, + (unsigned long) (duration.tv_sec * 1000000UL + duration.tv_usec), + proc->request_method[0] != '\0' ? proc->request_method : "-", + request_uri_string ? request_uri_string : "-", + query_string ? "?" : "", + query_string ? query_string : "", + proc->content_length, + proc->auth_user[0] != '\0' ? proc->auth_user : "-", + proc->script_filename[0] != '\0' ? proc->script_filename : "-", + proc->request_stage == FPM_REQUEST_ACCEPTING ? cpu : 0., + proc->request_stage == FPM_REQUEST_ACCEPTING ? proc->memory : 0); + + PUTS(buffer); + efree(buffer); + + if (tmp_request_uri_string) { + zend_string_free(tmp_request_uri_string); + } + if (tmp_query_string) { + zend_string_free(tmp_query_string); + } + } + + PUTS("\n"); +} + +static void fpm_status_handle_json(struct fpm_scoreboard_s *scoreboard_p, int full) +{ + char *buffer; + char time_buffer[64]; time_t now_epoch; - int full, has_start_time; - bool encode_html, encode_json; - char *short_syntax, *short_post; - char *full_pre, *full_syntax, *full_post, *full_separator; + sapi_add_header_ex(ZEND_STRL("Content-Type: application/json"), 1, 1); + + now_epoch = time(NULL); + strftime(time_buffer, sizeof(time_buffer) - 1, "%s", localtime(&scoreboard_p->start_epoch)); + + spprintf(&buffer, 0, + "{" + "\"pool\":\"%s\"," + "\"process manager\":\"%s\"," + "\"start time\":%s," + "\"start since\":%lu," + "\"accepted conn\":%lu," + "\"listen queue\":%d," + "\"max listen queue\":%d," + "\"listen queue len\":%u," + "\"idle processes\":%d," + "\"active processes\":%d," + "\"total processes\":%d," + "\"max active processes\":%d," + "\"max children reached\":%u," + "\"slow requests\":%lu," + "\"memory peak\":%zu", + scoreboard_p->pool, + PM2STR(scoreboard_p->pm), + time_buffer, + (unsigned long) (now_epoch - scoreboard_p->start_epoch), + scoreboard_p->requests, + scoreboard_p->lq, + scoreboard_p->lq_max, + scoreboard_p->lq_len, + scoreboard_p->idle, + scoreboard_p->active, + scoreboard_p->idle + scoreboard_p->active, + scoreboard_p->active_max, + scoreboard_p->max_children_reached, + scoreboard_p->slow_rq, + scoreboard_p->memory_peak); + + PUTS(buffer); + efree(buffer); + + if (!full) { + PUTS("}"); + return; + } + + unsigned int i; + int first; + struct fpm_scoreboard_proc_s *proc; + struct timeval duration, now; + float cpu; + zend_string *tmp_request_uri_string, *tmp_query_string; + char *request_uri_string, *query_string; + + fpm_clock_get(&now); + + PUTS(", \"processes\":["); + + first = 1; + for (i = 0; i < scoreboard_p->nprocs; i++) { + if (!scoreboard_p->procs[i].used) { + continue; + } + proc = &scoreboard_p->procs[i]; + + if (first) { + first = 0; + } else { + PUTS(","); + } + + request_uri_string = NULL; + tmp_request_uri_string = NULL; + if (proc->request_uri[0] != '\0') { + tmp_request_uri_string = php_json_encode_string(proc->request_uri, + strlen(proc->request_uri), PHP_JSON_INVALID_UTF8_IGNORE); + request_uri_string = ZSTR_VAL(tmp_request_uri_string); + if (ZSTR_LEN(tmp_request_uri_string) >= 2) { + request_uri_string[ZSTR_LEN(tmp_request_uri_string) - 1] = '\0'; + ++request_uri_string; + } + } + + query_string = NULL; + tmp_query_string = NULL; + if (proc->query_string[0] != '\0') { + tmp_query_string = php_json_encode_string(proc->query_string, + strlen(proc->query_string), PHP_JSON_INVALID_UTF8_IGNORE); + if (tmp_query_string) { + query_string = ZSTR_VAL(tmp_query_string); + if (ZSTR_LEN(tmp_query_string) >= 2) { + query_string[ZSTR_LEN(tmp_query_string) - 1] = '\0'; + ++query_string; + } + } + } + + fpm_status_proc_get_duration_and_cpu(proc, &now, &duration, &cpu); + + strftime(time_buffer, sizeof(time_buffer) - 1, "%s", localtime(&proc->start_epoch)); + + spprintf(&buffer, 0, + "{" + "\"pid\":%d," + "\"state\":\"%s\"," + "\"start time\":%s," + "\"start since\":%lu," + "\"requests\":%lu," + "\"request duration\":%lu," + "\"request method\":\"%s\"," + "\"request uri\":\"%s%s%s\"," + "\"content length\":%zu," + "\"user\":\"%s\"," + "\"script\":\"%s\"," + "\"last request cpu\":%.2f," + "\"last request memory\":%zu" + "}", + (int) proc->pid, + fpm_request_get_stage_name(proc->request_stage), + time_buffer, + (unsigned long) (now_epoch - proc->start_epoch), + proc->requests, + (unsigned long) (duration.tv_sec * 1000000UL + duration.tv_usec), + proc->request_method[0] != '\0' ? proc->request_method : "-", + request_uri_string ? request_uri_string : "-", + query_string ? "?" : "", + query_string ? query_string : "", + proc->content_length, + proc->auth_user[0] != '\0' ? proc->auth_user : "-", + proc->script_filename[0] != '\0' ? proc->script_filename : "-", + proc->request_stage == FPM_REQUEST_ACCEPTING ? cpu : 0., + proc->request_stage == FPM_REQUEST_ACCEPTING ? proc->memory : 0); + + PUTS(buffer); + efree(buffer); + + if (tmp_request_uri_string) { + zend_string_free(tmp_request_uri_string); + } + if (tmp_query_string) { + zend_string_free(tmp_query_string); + } + } + + PUTS("]}"); +} + +static void fpm_status_handle_openmetrics(struct fpm_scoreboard_s *scoreboard_p, int full) +{ + char *buffer; + time_t now_epoch; + + sapi_add_header_ex(ZEND_STRL("Content-Type: application/openmetrics-text; version=1.0.0; charset=utf-8"), 1, 1); + + now_epoch = time(NULL); + + spprintf(&buffer, 0, + "# HELP phpfpm_up Could pool %s using a %s PM on PHP-FPM be reached?\n" + "# TYPE phpfpm_up gauge\n" + "phpfpm_up 1\n" + "# HELP phpfpm_start_since The number of seconds since FPM has started.\n" + "# TYPE phpfpm_start_since counter\n" + "phpfpm_start_since %lu\n" + "# HELP phpfpm_accepted_connections The number of requests accepted by the pool.\n" + "# TYPE phpfpm_accepted_connections counter\n" + "phpfpm_accepted_connections %lu\n" + "# HELP phpfpm_listen_queue The number of requests in the queue of pending connections.\n" + "# TYPE phpfpm_listen_queue gauge\n" + "phpfpm_listen_queue %d\n" + "# HELP phpfpm_max_listen_queue The maximum number of requests in the queue of pending connections since FPM has started.\n" + "# TYPE phpfpm_max_listen_queue counter\n" + "phpfpm_max_listen_queue %d\n" + "# HELP phpfpm_listen_queue_length The size of the socket queue of pending connections.\n" + "# TYPE phpfpm_listen_queue_length gauge\n" + "phpfpm_listen_queue_length %u\n" + "# HELP phpfpm_idle_processes The number of idle processes.\n" + "# TYPE phpfpm_idle_processes gauge\n" + "phpfpm_idle_processes %d\n" + "# HELP phpfpm_active_processes The number of active processes.\n" + "# TYPE phpfpm_active_processes gauge\n" + "phpfpm_active_processes %d\n" + "# HELP phpfpm_total_processes The number of idle + active processes.\n" + "# TYPE phpfpm_total_processes gauge\n" + "phpfpm_total_processes %d\n" + "# HELP phpfpm_max_active_processes The maximum number of active processes since FPM has started.\n" + "# TYPE phpfpm_max_active_processes counter\n" + "phpfpm_max_active_processes %d\n" + "# HELP phpfpm_max_children_reached The number of times, the process limit has been reached, when pm tries to start more children (works only for pm 'dynamic' and 'ondemand').\n" + "# TYPE phpfpm_max_children_reached counter\n" + "phpfpm_max_children_reached %u\n" + "# HELP phpfpm_slow_requests The number of requests that exceeded your 'request_slowlog_timeout' value.\n" + "# TYPE phpfpm_slow_requests counter\n" + "phpfpm_slow_requests %lu\n" + "# HELP phpfpm_memory_peak The memory usage peak since FPM has started.\n" + "# TYPE phpfpm_memory_peak gauge\n" + "phpfpm_memory_peak %zu\n", + scoreboard_p->pool, + PM2STR(scoreboard_p->pm), + (unsigned long) (now_epoch - scoreboard_p->start_epoch), + scoreboard_p->requests, + scoreboard_p->lq, + scoreboard_p->lq_max, + scoreboard_p->lq_len, + scoreboard_p->idle, + scoreboard_p->active, + scoreboard_p->idle + scoreboard_p->active, + scoreboard_p->active_max, + scoreboard_p->max_children_reached, + scoreboard_p->slow_rq, + scoreboard_p->memory_peak); + + PUTS(buffer); + efree(buffer); + + if (!full) { + PUTS("# EOF\n"); + return; + } + + unsigned int i; + struct fpm_scoreboard_proc_s *proc; + struct timeval duration, now; + float cpu; + smart_str buf_state = {0}; + smart_str buf_requests = {0}; + smart_str buf_duration = {0}; + smart_str buf_cpu = {0}; + smart_str buf_memory = {0}; + + fpm_clock_get(&now); + + for (i = 0; i < scoreboard_p->nprocs; i++) { + if (!scoreboard_p->procs[i].used) { + continue; + } + proc = &scoreboard_p->procs[i]; + + fpm_status_proc_get_duration_and_cpu(proc, &now, &duration, &cpu); + + for (int s = FPM_REQUEST_CREATING; s <= FPM_REQUEST_FINISHED; s++) { + smart_str_append_printf(&buf_state, + "phpfpm_process_state{pool=\"%s\",child=\"%u\",state=\"%s\"} %d\n", + scoreboard_p->pool, i, fpm_request_get_stage_name(s), + proc->request_stage == s ? 1 : 0); + } + + smart_str_append_printf(&buf_requests, + "phpfpm_process_requests{pool=\"%s\",child=\"%u\"} %lu\n", + scoreboard_p->pool, i, proc->requests); + + smart_str_append_printf(&buf_duration, + "phpfpm_process_request_duration{pool=\"%s\",child=\"%u\"} %lu\n", + scoreboard_p->pool, i, + (unsigned long) (duration.tv_sec * 1000000UL + duration.tv_usec)); + + smart_str_append_printf(&buf_cpu, + "phpfpm_process_last_request_cpu{pool=\"%s\",child=\"%u\"} %.2f\n", + scoreboard_p->pool, i, + proc->request_stage == FPM_REQUEST_ACCEPTING ? cpu : 0.); + + smart_str_append_printf(&buf_memory, + "phpfpm_process_last_request_memory{pool=\"%s\",child=\"%u\"} %zu\n", + scoreboard_p->pool, i, + proc->request_stage == FPM_REQUEST_ACCEPTING ? proc->memory : 0); + } + + /* buf.s is NULL for all smart_strs when no workers have used == true */ + PUTS("# HELP phpfpm_process_state The state of the process (Idle, Running, ...).\n" + "# TYPE phpfpm_process_state gauge\n"); + smart_str_0(&buf_state); + if (buf_state.s) { + PUTS(ZSTR_VAL(buf_state.s)); + } + smart_str_free(&buf_state); + + PUTS("# HELP phpfpm_process_requests The number of requests the process has served.\n" + "# TYPE phpfpm_process_requests counter\n"); + smart_str_0(&buf_requests); + if (buf_requests.s) { + PUTS(ZSTR_VAL(buf_requests.s)); + } + smart_str_free(&buf_requests); + + PUTS("# HELP phpfpm_process_request_duration The duration in microseconds of the current or last request.\n" + "# TYPE phpfpm_process_request_duration gauge\n"); + smart_str_0(&buf_duration); + if (buf_duration.s) { + PUTS(ZSTR_VAL(buf_duration.s)); + } + smart_str_free(&buf_duration); + + PUTS("# HELP phpfpm_process_last_request_cpu The %cpu the last request consumed.\n" + "# TYPE phpfpm_process_last_request_cpu gauge\n"); + smart_str_0(&buf_cpu); + if (buf_cpu.s) { + PUTS(ZSTR_VAL(buf_cpu.s)); + } + smart_str_free(&buf_cpu); + + PUTS("# HELP phpfpm_process_last_request_memory The max amount of memory the last request consumed.\n" + "# TYPE phpfpm_process_last_request_memory gauge\n"); + smart_str_0(&buf_memory); + if (buf_memory.s) { + PUTS(ZSTR_VAL(buf_memory.s)); + } + smart_str_free(&buf_memory); + + PUTS("# EOF\n"); +} + +int fpm_status_handle_request(void) /* {{{ */ +{ if (!SG(request_info).request_uri) { return 0; } @@ -156,18 +893,15 @@ int fpm_status_handle_request(void) /* {{{ */ /* STATUS */ if (fpm_status_uri && !strcmp(fpm_status_uri, SG(request_info).request_uri)) { + struct fpm_scoreboard_s *scoreboard_p; zend_string *_GET_str; + int full; fpm_request_executing(); /* full status ? */ _GET_str = ZSTR_INIT_LITERAL("_GET", 0); full = fpm_php_is_key_in_table(_GET_str, ZEND_STRL("full")); - short_syntax = short_post = NULL; - full_separator = full_pre = full_syntax = full_post = NULL; - encode_html = false; - encode_json = false; - has_start_time = 1; scoreboard_p = fpm_scoreboard_get(); if (scoreboard_p) { @@ -180,17 +914,19 @@ int fpm_status_handle_request(void) /* {{{ */ sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1); sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1); PUTS("Internal error. Please review log file for errors."); + zend_string_release_ex(_GET_str, 0); return 1; } if (scoreboard_p->idle < 0 || scoreboard_p->active < 0) { - fpm_scoreboard_free_copy(scoreboard_p); zlog(ZLOG_ERROR, "[pool %s] invalid status values", scoreboard_p->pool); SG(sapi_headers).http_response_code = 500; sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1); sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1); sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1); PUTS("Internal error. Please review log file for errors."); + zend_string_release_ex(_GET_str, 0); + fpm_scoreboard_free_copy(scoreboard_p); return 1; } @@ -201,445 +937,24 @@ int fpm_status_handle_request(void) /* {{{ */ /* handle HEAD */ if (SG(request_info).headers_only) { + zend_string_release_ex(_GET_str, 0); fpm_scoreboard_free_copy(scoreboard_p); return 1; } - /* HTML */ if (fpm_php_is_key_in_table(_GET_str, ZEND_STRL("html"))) { - sapi_add_header_ex(ZEND_STRL("Content-Type: text/html"), 1, 1); - time_format = "%d/%b/%Y:%H:%M:%S %z"; - encode_html = true; - - short_syntax = - "\n" - "\n" - "PHP-FPM Status Page\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "
pool%s
process manager%s
start time%s
start since%lu
accepted conn%lu
listen queue%d
max listen queue%d
listen queue len%u
idle processes%d
active processes%d
total processes%d
max active processes%d
max children reached%u
slow requests%lu
memory peak%zu
\n"; - - if (!full) { - short_post = ""; - } else { - full_pre = - "\n" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "\n"; - - full_syntax = - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "\n"; - - full_post = "
pidstatestart timestart sincerequestsrequest durationrequest methodrequest uricontent lengthuserscriptlast request cpulast request memory
%d%s%s%lu%lu%lu%s%s%s%s%zu%s%s%.2f%zu
"; - } - - /* XML */ + fpm_status_handle_html(scoreboard_p, full); } else if (fpm_php_is_key_in_table(_GET_str, ZEND_STRL("xml"))) { - sapi_add_header_ex(ZEND_STRL("Content-Type: text/xml"), 1, 1); - time_format = "%s"; - encode_html = true; - - short_syntax = - "\n" - "\n" - "%s\n" - "%s\n" - "%s\n" - "%lu\n" - "%lu\n" - "%d\n" - "%d\n" - "%u\n" - "%d\n" - "%d\n" - "%d\n" - "%d\n" - "%u\n" - "%lu\n" - "%zu\n"; - - if (!full) { - short_post = ""; - } else { - full_pre = "\n"; - full_syntax = - "" - "%d" - "%s" - "%s" - "%lu" - "%lu" - "%lu" - "%s" - "%s%s%s" - "%zu" - "%s" - "" - "%.2f" - "%zu" - "\n" - ; - full_post = "\n"; - } - - /* JSON */ + fpm_status_handle_xml(scoreboard_p, full); } else if (fpm_php_is_key_in_table(_GET_str, ZEND_STRL("json"))) { - sapi_add_header_ex(ZEND_STRL("Content-Type: application/json"), 1, 1); - time_format = "%s"; - - encode_json = true; - - short_syntax = - "{" - "\"pool\":\"%s\"," - "\"process manager\":\"%s\"," - "\"start time\":%s," - "\"start since\":%lu," - "\"accepted conn\":%lu," - "\"listen queue\":%d," - "\"max listen queue\":%d," - "\"listen queue len\":%u," - "\"idle processes\":%d," - "\"active processes\":%d," - "\"total processes\":%d," - "\"max active processes\":%d," - "\"max children reached\":%u," - "\"slow requests\":%lu," - "\"memory peak\":%zu"; - - if (!full) { - short_post = "}"; - } else { - full_separator = ","; - full_pre = ", \"processes\":["; - - full_syntax = "{" - "\"pid\":%d," - "\"state\":\"%s\"," - "\"start time\":%s," - "\"start since\":%lu," - "\"requests\":%lu," - "\"request duration\":%lu," - "\"request method\":\"%s\"," - "\"request uri\":\"%s%s%s\"," - "\"content length\":%zu," - "\"user\":\"%s\"," - "\"script\":\"%s\"," - "\"last request cpu\":%.2f," - "\"last request memory\":%zu" - "}"; - - full_post = "]}"; - } - - /* OpenMetrics */ + fpm_status_handle_json(scoreboard_p, full); } else if (fpm_php_is_key_in_table(_GET_str, ZEND_STRL("openmetrics"))) { - sapi_add_header_ex(ZEND_STRL("Content-Type: application/openmetrics-text; version=1.0.0; charset=utf-8"), 1, 1); - time_format = "%s"; - - short_syntax = - "# HELP phpfpm_up Could pool %s using a %s PM on PHP-FPM be reached?\n" - "# TYPE phpfpm_up gauge\n" - "phpfpm_up 1\n" - "# HELP phpfpm_start_since The number of seconds since FPM has started.\n" - "# TYPE phpfpm_start_since counter\n" - "phpfpm_start_since %lu\n" - "# HELP phpfpm_accepted_connections The number of requests accepted by the pool.\n" - "# TYPE phpfpm_accepted_connections counter\n" - "phpfpm_accepted_connections %lu\n" - "# HELP phpfpm_listen_queue The number of requests in the queue of pending connections.\n" - "# TYPE phpfpm_listen_queue gauge\n" - "phpfpm_listen_queue %d\n" - "# HELP phpfpm_max_listen_queue The maximum number of requests in the queue of pending connections since FPM has started.\n" - "# TYPE phpfpm_max_listen_queue counter\n" - "phpfpm_max_listen_queue %d\n" - "# TYPE phpfpm_listen_queue_length gauge\n" - "# HELP phpfpm_listen_queue_length The size of the socket queue of pending connections.\n" - "phpfpm_listen_queue_length %u\n" - "# HELP phpfpm_idle_processes The number of idle processes.\n" - "# TYPE phpfpm_idle_processes gauge\n" - "phpfpm_idle_processes %d\n" - "# HELP phpfpm_active_processes The number of active processes.\n" - "# TYPE phpfpm_active_processes gauge\n" - "phpfpm_active_processes %d\n" - "# HELP phpfpm_total_processes The number of idle + active processes.\n" - "# TYPE phpfpm_total_processes gauge\n" - "phpfpm_total_processes %d\n" - "# HELP phpfpm_max_active_processes The maximum number of active processes since FPM has started.\n" - "# TYPE phpfpm_max_active_processes counter\n" - "phpfpm_max_active_processes %d\n" - "# HELP phpfpm_max_children_reached The number of times, the process limit has been reached, when pm tries to start more children (works only for pm 'dynamic' and 'ondemand').\n" - "# TYPE phpfpm_max_children_reached counter\n" - "phpfpm_max_children_reached %u\n" - "# HELP phpfpm_slow_requests The number of requests that exceeded your 'request_slowlog_timeout' value.\n" - "# TYPE phpfpm_slow_requests counter\n" - "phpfpm_slow_requests %lu\n" - "# HELP phpfpm_memory_peak The memory usage peak since FPM has started.\n" - "# TYPE phpfpm_memory_peak gauge\n" - "phpfpm_memory_peak %zu\n" - "# EOF\n"; - - has_start_time = 0; - if (!full) { - short_post = ""; - } else { - full_separator = ""; - full_pre = ""; - full_syntax = ""; - full_post = ""; - } - - /* TEXT */ - } else { - sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1); - time_format = "%d/%b/%Y:%H:%M:%S %z"; - - short_syntax = - "pool: %s\n" - "process manager: %s\n" - "start time: %s\n" - "start since: %lu\n" - "accepted conn: %lu\n" - "listen queue: %d\n" - "max listen queue: %d\n" - "listen queue len: %u\n" - "idle processes: %d\n" - "active processes: %d\n" - "total processes: %d\n" - "max active processes: %d\n" - "max children reached: %u\n" - "slow requests: %lu\n" - "memory peak: %zu\n"; - - if (full) { - full_syntax = - "\n" - "************************\n" - "pid: %d\n" - "state: %s\n" - "start time: %s\n" - "start since: %lu\n" - "requests: %lu\n" - "request duration: %lu\n" - "request method: %s\n" - "request URI: %s%s%s\n" - "content length: %zu\n" - "user: %s\n" - "script: %s\n" - "last request cpu: %.2f\n" - "last request memory: %zu\n"; - } - } - - now_epoch = time(NULL); - if (has_start_time) { - strftime(time_buffer, sizeof(time_buffer) - 1, time_format, localtime(&scoreboard_p->start_epoch)); - spprintf(&buffer, 0, short_syntax, - scoreboard_p->pool, - PM2STR(scoreboard_p->pm), - time_buffer, - (unsigned long) (now_epoch - scoreboard_p->start_epoch), - scoreboard_p->requests, - scoreboard_p->lq, - scoreboard_p->lq_max, - scoreboard_p->lq_len, - scoreboard_p->idle, - scoreboard_p->active, - scoreboard_p->idle + scoreboard_p->active, - scoreboard_p->active_max, - scoreboard_p->max_children_reached, - scoreboard_p->slow_rq, - scoreboard_p->memory_peak); + fpm_status_handle_openmetrics(scoreboard_p, full); } else { - spprintf(&buffer, 0, short_syntax, - scoreboard_p->pool, - PM2STR(scoreboard_p->pm), - (unsigned long) (now_epoch - scoreboard_p->start_epoch), - scoreboard_p->requests, - scoreboard_p->lq, - scoreboard_p->lq_max, - scoreboard_p->lq_len, - scoreboard_p->idle, - scoreboard_p->active, - scoreboard_p->idle + scoreboard_p->active, - scoreboard_p->active_max, - scoreboard_p->max_children_reached, - scoreboard_p->slow_rq, - scoreboard_p->memory_peak); + fpm_status_handle_plaintext(scoreboard_p, full); } - PUTS(buffer); - efree(buffer); zend_string_release_ex(_GET_str, 0); - - if (short_post) { - PUTS(short_post); - } - - /* no need to test the var 'full' */ - if (full_syntax) { - unsigned int i; - int first; - zend_string *tmp_query_string, *tmp_request_uri_string; - char *query_string, *request_uri_string; - struct timeval duration, now; - float cpu; - - fpm_clock_get(&now); - - if (full_pre) { - PUTS(full_pre); - } - - first = 1; - for (i=0; inprocs; i++) { - if (!scoreboard_p->procs[i].used) { - continue; - } - proc = &scoreboard_p->procs[i]; - - if (first) { - first = 0; - } else { - if (full_separator) { - PUTS(full_separator); - } - } - - request_uri_string = NULL; - tmp_request_uri_string = NULL; - if (proc->request_uri[0] != '\0') { - if (encode_html) { - tmp_request_uri_string = php_escape_html_entities_ex( - (const unsigned char *) proc->request_uri, - strlen(proc->request_uri), 1, ENT_DISALLOWED | ENT_HTML_DOC_XML1 | ENT_COMPAT, - NULL, /* double_encode */ 1, /* quiet */ 0); - request_uri_string = ZSTR_VAL(tmp_request_uri_string); - } else if (encode_json) { - tmp_request_uri_string = php_json_encode_string(proc->request_uri, - strlen(proc->request_uri), PHP_JSON_INVALID_UTF8_IGNORE); - request_uri_string = ZSTR_VAL(tmp_request_uri_string); - /* remove quotes around the string */ - if (ZSTR_LEN(tmp_request_uri_string) >= 2) { - request_uri_string[ZSTR_LEN(tmp_request_uri_string) - 1] = '\0'; - ++request_uri_string; - } - } else { - request_uri_string = proc->request_uri; - } - } - - query_string = NULL; - tmp_query_string = NULL; - if (proc->query_string[0] != '\0') { - if (encode_html) { - tmp_query_string = php_escape_html_entities_ex( - (const unsigned char *) proc->query_string, - strlen(proc->query_string), 1, ENT_DISALLOWED | ENT_HTML_DOC_XML1 | ENT_COMPAT, - NULL, /* double_encode */ 1, /* quiet */ 0); - } else if (encode_json) { - tmp_query_string = php_json_encode_string(proc->query_string, - strlen(proc->query_string), PHP_JSON_INVALID_UTF8_IGNORE); - } else { - query_string = proc->query_string; - } - if (tmp_query_string) { - query_string = ZSTR_VAL(tmp_query_string); - /* remove quotes around the string */ - if (encode_json && ZSTR_LEN(tmp_query_string) >= 2) { - query_string[ZSTR_LEN(tmp_query_string) - 1] = '\0'; - ++query_string; - } - } - } - - /* prevent NaN */ - if (proc->cpu_duration.tv_sec == 0 && proc->cpu_duration.tv_usec == 0) { - cpu = 0.; - } else { - 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.; - } - - if (proc->request_stage == FPM_REQUEST_ACCEPTING) { - duration = proc->duration; - } else { - timersub(&now, &proc->accepted, &duration); - } - strftime(time_buffer, sizeof(time_buffer) - 1, time_format, localtime(&proc->start_epoch)); - spprintf(&buffer, 0, full_syntax, - (int) proc->pid, - fpm_request_get_stage_name(proc->request_stage), - time_buffer, - (unsigned long) (now_epoch - proc->start_epoch), - proc->requests, - (unsigned long) (duration.tv_sec * 1000000UL + duration.tv_usec), - proc->request_method[0] != '\0' ? proc->request_method : "-", - request_uri_string ? request_uri_string : "-", - query_string ? "?" : "", - query_string ? query_string : "", - proc->content_length, - proc->auth_user[0] != '\0' ? proc->auth_user : "-", - proc->script_filename[0] != '\0' ? proc->script_filename : "-", - proc->request_stage == FPM_REQUEST_ACCEPTING ? cpu : 0., - proc->request_stage == FPM_REQUEST_ACCEPTING ? proc->memory : 0); - PUTS(buffer); - efree(buffer); - - if (tmp_request_uri_string) { - zend_string_free(tmp_request_uri_string); - } - if (tmp_query_string) { - zend_string_free(tmp_query_string); - } - } - - if (full_post) { - PUTS(full_post); - } - } - fpm_scoreboard_free_copy(scoreboard_p); return 1; } diff --git a/sapi/fpm/tests/status-openmetrics-full.phpt b/sapi/fpm/tests/status-openmetrics-full.phpt new file mode 100644 index 000000000000..bd19818e00ab --- /dev/null +++ b/sapi/fpm/tests/status-openmetrics-full.phpt @@ -0,0 +1,122 @@ +--TEST-- +FPM: OpenMetrics full status with per-worker metrics +--SKIPIF-- + +--FILE-- +start(); +$tester->expectLogStartNotices(); +$tester->request()->expectEmptyBody(); + +$response = $tester->request( + 'openmetrics&full', + [], + '/status' +); + +$body = $response->getBody('application/openmetrics-text; version=1.0.0; charset=utf-8'); + +if ($body === null) { + echo "ERROR: Could not get OpenMetrics body\n"; + $tester->terminate(); + $tester->close(); + return; +} + +$ok = true; + +/* Pool-level metrics should be present */ +if (!preg_match('/^# HELP phpfpm_up /m', $body)) { + echo "ERROR: Missing phpfpm_up metric\n"; + $ok = false; +} + +/* Per-worker state metrics with one-hot encoding */ +if (!preg_match('/^# HELP phpfpm_process_state /m', $body)) { + echo "ERROR: Missing phpfpm_process_state HELP\n"; + $ok = false; +} +if (!preg_match('/^# TYPE phpfpm_process_state gauge$/m', $body)) { + echo "ERROR: Missing phpfpm_process_state TYPE\n"; + $ok = false; +} + +/* Each child should have exactly 7 state lines (one per possible state) */ +for ($child = 0; $child < 2; $child++) { + $statePattern = '/phpfpm_process_state\{pool="unconfined",child="' . $child . '",state="[^"]+"\} [01]/'; + preg_match_all($statePattern, $body, $matches); + if (count($matches[0]) !== 7) { + echo "ERROR: Expected 7 state lines for child $child, got " . count($matches[0]) . "\n"; + $ok = false; + } + /* Exactly one state should be 1 */ + $activePattern = '/phpfpm_process_state\{pool="unconfined",child="' . $child . '",state="[^"]+"\} 1/'; + preg_match_all($activePattern, $body, $matches); + if (count($matches[0]) !== 1) { + echo "ERROR: Expected exactly 1 active state for child $child, got " . count($matches[0]) . "\n"; + $ok = false; + } +} + +/* Per-worker numeric metrics */ +$workerMetrics = [ + 'phpfpm_process_requests' => 'counter', + 'phpfpm_process_request_duration' => 'gauge', + 'phpfpm_process_last_request_cpu' => 'gauge', + 'phpfpm_process_last_request_memory' => 'gauge', +]; + +foreach ($workerMetrics as $metric => $type) { + if (!preg_match('/^# HELP ' . $metric . ' /m', $body)) { + echo "ERROR: Missing $metric HELP\n"; + $ok = false; + } + if (!preg_match('/^# TYPE ' . $metric . ' ' . $type . '$/m', $body)) { + echo "ERROR: Missing $metric TYPE\n"; + $ok = false; + } + for ($child = 0; $child < 2; $child++) { + $pattern = '/' . $metric . '\{pool="unconfined",child="' . $child . '"\} /'; + if (!preg_match($pattern, $body)) { + echo "ERROR: Missing $metric for child $child\n"; + $ok = false; + } + } +} + +/* Must end with # EOF */ +if (!preg_match('/^# EOF$/m', $body)) { + echo "ERROR: Missing # EOF\n"; + $ok = false; +} + +if ($ok) { + echo "Done\n"; +} + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/status.inc b/sapi/fpm/tests/status.inc index 76ef7fd55e11..f51702a0b25b 100644 --- a/sapi/fpm/tests/status.inc +++ b/sapi/fpm/tests/status.inc @@ -246,8 +246,8 @@ class Status "# HELP phpfpm_max_listen_queue The maximum number of requests in the queue of pending connections since FPM has started\.\n" . "# TYPE phpfpm_max_listen_queue counter\n" . "phpfpm_max_listen_queue " . $fields['max listen queue'] . "\n" . - "# TYPE phpfpm_listen_queue_length gauge\n" . "# HELP phpfpm_listen_queue_length The size of the socket queue of pending connections\.\n" . + "# TYPE phpfpm_listen_queue_length gauge\n" . "phpfpm_listen_queue_length " . $fields['listen queue len'] . "\n" . "# HELP phpfpm_idle_processes The number of idle processes\.\n" . "# TYPE phpfpm_idle_processes gauge\n" .