]> granicus.if.org Git - php/commitdiff
- Implemented FR #54577 (Enhanced status page with full status and details about...
authorJérôme Loyet <fat@php.net>
Sat, 2 Jul 2011 16:10:18 +0000 (16:10 +0000)
committerJérôme Loyet <fat@php.net>
Sat, 2 Jul 2011 16:10:18 +0000 (16:10 +0000)
- Added a web page (status.html) for real-time FPM status
- Fixed missing Expires and Cache-Control headers for ping and status pages

13 files changed:
NEWS
sapi/fpm/Makefile.frag
sapi/fpm/fpm/fpm.c
sapi/fpm/fpm/fpm_children.c
sapi/fpm/fpm/fpm_log.c
sapi/fpm/fpm/fpm_log.h
sapi/fpm/fpm/fpm_request.c
sapi/fpm/fpm/fpm_request.h
sapi/fpm/fpm/fpm_scoreboard.c
sapi/fpm/fpm/fpm_scoreboard.h
sapi/fpm/fpm/fpm_status.c
sapi/fpm/php-fpm.conf.in
sapi/fpm/status.html [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index d0f503c03918d0dd430759c445ac5294217fde2a..79be61e8bda15c0f9e157fd9b5dd00869945414f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,13 @@ PHP                                                                        NEWS
 - PDO ODBC driver:
   . Fixed data type usage in 64bit. (leocsilva at gmail dot com)
 
+- PHP-FPM SAPI:
+  . Implemented FR #54577 (Enhanced status page with full status and details
+    about each processes. Also provide a web page (status.html) for
+    real-time FPM status. (fat)
+  . Fixed missing Expires and Cache-Control headers for ping and status pages.
+    (fat)
+
 - SPL extension:
   . Fixed bug #54971 (Wrong result when using iterator_to_array with use_keys
     on true). (Pierrick)
index 0859ef10ca96511935948b7690ab95d2af919fc7..85a0ba70f9c46c1055bd457df7a13e093a278996 100644 (file)
@@ -26,3 +26,6 @@ install-fpm: install-sapi
        @$(mkinstalldirs) $(INSTALL_ROOT)$(mandir)/man8
        @$(INSTALL_DATA) sapi/fpm/php-fpm.8 $(INSTALL_ROOT)$(mandir)/man8/php-fpm$(program_suffix).8
 
+       @echo "Installing PHP FPM status page:      $(INSTALL_ROOT)$(datadir)/fpm/"
+       @$(mkinstalldirs) $(INSTALL_ROOT)$(datadir)/fpm
+       @$(INSTALL_DATA) sapi/fpm/status.html $(INSTALL_ROOT)$(datadir)/fpm/status.html
index b0b3b2b7a4cddf909bb63941f276459dfd4f6873..13106dfe4d8e12526d91aeacae7f7f19d1c12330 100644 (file)
@@ -49,7 +49,6 @@ int fpm_init(int argc, char **argv, char *config, char *prefix, int test_conf) /
 
        if (0 > fpm_php_init_main()            ||
                0 > fpm_stdio_init_main()            ||
-               0 > fpm_log_init_main()              ||
                0 > fpm_conf_init_main(test_conf)    ||
                0 > fpm_unix_init_main()             ||
                0 > fpm_scoreboard_init_main()       ||
index 05d741cb89b7c02e1ad3c67a83ea7ff8d7e1bfc2..65f56e7269b156a5b8bd6b60b5783e08363c73e7 100644 (file)
@@ -344,7 +344,8 @@ static void fpm_child_resources_use(struct fpm_child_s *child) /* {{{ */
                }
                fpm_scoreboard_free(wp->scoreboard);
        }
-       fpm_scoreboard_child_use(child->wp->scoreboard, child->scoreboard_i, child->pid);
+
+       fpm_scoreboard_child_use(child->wp->scoreboard, child->scoreboard_i, getpid());
        fpm_stdio_child_use_pipes(child);
        fpm_child_free(child);
 }
index ef1c0c3be382c655beff5c4d1c514c1bf07445e0..5360c45f7acef919d61205d0236b7373d24b4b67 100644 (file)
@@ -29,9 +29,6 @@
 
 static char *fpm_log_format = NULL;
 static int fpm_log_fd = -1;
-#ifdef HAVE_TIMES
-static float tick;
-#endif
 
 int fpm_log_open(int reopen) /* {{{ */
 {
@@ -67,24 +64,6 @@ int fpm_log_open(int reopen) /* {{{ */
 }
 /* }}} */
 
-int fpm_log_init_main() /* {{{ */
-{
-#ifdef HAVE_TIMES
-#if (defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK))
-       tick = sysconf(_SC_CLK_TCK);
-#else /* _SC_CLK_TCK */
-#ifdef HZ
-       tick = HZ;
-#else /* HZ */
-       tick = 100;
-#endif /* HZ */
-#endif /* _SC_CLK_TCK */
-       zlog(ZLOG_DEBUG, "got clock tick '%.0f'", tick);
-#endif /* HAVE_TIMES */
-       return 0;
-}
-/* }}} */
-
 /* }}} */
 int fpm_log_init_child(struct fpm_worker_pool_s *wp)  /* {{{ */
 {
@@ -122,7 +101,6 @@ int fpm_log_write(char *log_format TSRMLS_DC) /* {{{ */
        size_t len, len2;
        struct fpm_scoreboard_proc_s proc, *proc_p;
        struct fpm_scoreboard_s *scoreboard;
-       struct timeval uptime, now;
        char tmp[129];
        char format[129];
        time_t now_epoch;
@@ -141,7 +119,6 @@ int fpm_log_write(char *log_format TSRMLS_DC) /* {{{ */
                test = 1;
        }
 
-       fpm_clock_get(&now);
        now_epoch = time(NULL);
 
        if (!test) {
@@ -157,8 +134,6 @@ int fpm_log_write(char *log_format TSRMLS_DC) /* {{{ */
                }
                proc = *proc_p;
                fpm_scoreboard_proc_release(proc_p);
-
-               timersub(&now, &proc.accepted, &uptime);
        }
 
        token = 0;
@@ -198,19 +173,15 @@ int fpm_log_write(char *log_format TSRMLS_DC) /* {{{ */
                                case 'C': /* %CPU */
                                        if (format[0] == '\0' || !strcasecmp(format, "total")) {
                                                if (!test) {
-                                                       tms_total = 
-                                                               (proc.cpu_finished.tms_utime + proc.cpu_finished.tms_stime + proc.cpu_finished.tms_cutime + proc.cpu_finished.tms_cstime)
-                                                               - 
-                                                               (proc.cpu_accepted.tms_utime + proc.cpu_accepted.tms_stime + proc.cpu_accepted.tms_cutime + proc.cpu_accepted.tms_cstime)
-                                                               ;
+                                                       tms_total = proc.last_request_cpu.tms_utime + proc.last_request_cpu.tms_stime + proc.last_request_cpu.tms_cutime + proc.last_request_cpu.tms_cstime;
                                                }
                                        } else if (!strcasecmp(format, "user")) {
                                                if (!test) {
-                                                       tms_total = (proc.cpu_finished.tms_utime + proc.cpu_finished.tms_cutime) - (proc.cpu_accepted.tms_utime + proc.cpu_accepted.tms_cutime);
+                                                       tms_total = proc.last_request_cpu.tms_utime + proc.last_request_cpu.tms_cutime;
                                                }
                                        } else if (!strcasecmp(format, "system")) {
                                                if (!test) {
-                                                       tms_total = (proc.cpu_finished.tms_stime + proc.cpu_finished.tms_cstime) - (proc.cpu_accepted.tms_stime + proc.cpu_accepted.tms_cstime);
+                                                       tms_total = proc.last_request_cpu.tms_stime + proc.last_request_cpu.tms_cstime;
                                                }
                                        } else {
                                                zlog(ZLOG_WARNING, "only 'total', 'user' or 'system' are allowed as a modifier for %%%c ('%s')", *s, format);
@@ -219,7 +190,7 @@ int fpm_log_write(char *log_format TSRMLS_DC) /* {{{ */
 
                                        format[0] = '\0';
                                        if (!test) {
-                                               len2 = snprintf(b, FPM_LOG_BUFFER - len, "%.2f", tms_total / tick / (proc.cpu_duration.tv_sec + proc.cpu_duration.tv_usec / 1000000.) * 100.);
+                                               len2 = snprintf(b, FPM_LOG_BUFFER - len, "%.2f", tms_total / fpm_scoreboard_get_tick() / (proc.cpu_duration.tv_sec + proc.cpu_duration.tv_usec / 1000000.) * 100.);
                                        }
                                        break;
 #endif
@@ -228,19 +199,19 @@ int fpm_log_write(char *log_format TSRMLS_DC) /* {{{ */
                                        /* seconds */
                                        if (format[0] == '\0' || !strcasecmp(format, "seconds")) {
                                                if (!test) {
-                                                       len2 = snprintf(b, FPM_LOG_BUFFER - len, "%.3f", uptime.tv_sec + uptime.tv_usec / 1000000.);
+                                                       len2 = snprintf(b, FPM_LOG_BUFFER - len, "%.3f", proc.duration.tv_sec + proc.duration.tv_usec / 1000000.);
                                                }
 
                                        /* miliseconds */
                                        } else if (!strcasecmp(format, "miliseconds") || !strcasecmp(format, "mili")) {
                                                if (!test) {
-                                                       len2 = snprintf(b, FPM_LOG_BUFFER - len, "%.3f", uptime.tv_sec * 1000. + uptime.tv_usec / 1000.);
+                                                       len2 = snprintf(b, FPM_LOG_BUFFER - len, "%.3f", proc.duration.tv_sec * 1000. + proc.duration.tv_usec / 1000.);
                                                }
 
                                        /* microseconds */
                                        } else if (!strcasecmp(format, "microseconds") || !strcasecmp(format, "micro")) {
                                                if (!test) {
-                                                       len2 = snprintf(b, FPM_LOG_BUFFER - len, "%lu", uptime.tv_sec * 1000000UL + uptime.tv_usec);
+                                                       len2 = snprintf(b, FPM_LOG_BUFFER - len, "%lu", proc.duration.tv_sec * 1000000UL + proc.duration.tv_usec);
                                                }
 
                                        } else {
index 74f842646fa0243e22c84de3ecb33318cdd54fff..f0199d9cb6dc01176b3622a34984c1d3fae08bce 100644 (file)
@@ -6,7 +6,6 @@
 #define FPM_LOG_H 1
 #include "fpm_worker_pool.h"
 
-int fpm_log_init_main();
 int fpm_log_init_child(struct fpm_worker_pool_s *wp);
 int fpm_log_write(char *log_format TSRMLS_DC);
 int fpm_log_open(int reopen);
index e0d1815dbe99b4c9f32d55f73ecf1730bb5f0533..f94196b562f7ca75520af200fee009f3b8ef8ee9 100644 (file)
 
 #include "zlog.h"
 
+static const char *requests_stages[] = {
+       [FPM_REQUEST_ACCEPTING]       = "Idle",
+       [FPM_REQUEST_READING_HEADERS] = "Reading headers",
+       [FPM_REQUEST_INFO]            = "Getting request informations",
+       [FPM_REQUEST_EXECUTING]       = "Running",
+       [FPM_REQUEST_END]             = "Ending",
+       [FPM_REQUEST_FINISHED]        = "Finishing",
+};
+
+const char *fpm_request_get_stage_name(int stage) {
+       return requests_stages[stage];
+}
+
 void fpm_request_accepting() /* {{{ */
 {
        struct fpm_scoreboard_proc_s *proc;
@@ -38,10 +51,6 @@ void fpm_request_accepting() /* {{{ */
 
        proc->request_stage = FPM_REQUEST_ACCEPTING;
        proc->tv = now;
-       proc->request_uri[0] = '\0';
-       proc->request_method[0] = '\0';
-       proc->script_filename[0] = '\0';
-       proc->content_length = 0;
        fpm_scoreboard_proc_release(proc);
 
        /* idle++, active-- */
@@ -78,6 +87,12 @@ void fpm_request_reading_headers() /* {{{ */
 #ifdef HAVE_TIMES
        proc->cpu_accepted = cpu;
 #endif
+       proc->requests++;
+       proc->request_uri[0] = '\0';
+       proc->request_method[0] = '\0';
+       proc->script_filename[0] = '\0';
+       proc->query_string[0] = '\0';
+       proc->content_length = 0;
        fpm_scoreboard_proc_release(proc);
 
        /* idle--, active++, request++ */
@@ -176,9 +191,13 @@ void fpm_request_end(TSRMLS_D) /* {{{ */
        }
        proc->request_stage = FPM_REQUEST_FINISHED;
        proc->tv = now;
+       timersub(&now, &proc->accepted, &proc->duration);
 #ifdef HAVE_TIMES
-       proc->cpu_finished = cpu;
        timersub(&proc->tv, &proc->accepted, &proc->cpu_duration);
+       proc->last_request_cpu.tms_utime = cpu.tms_utime - proc->cpu_accepted.tms_utime;
+       proc->last_request_cpu.tms_stime = cpu.tms_stime - proc->cpu_accepted.tms_stime;
+       proc->last_request_cpu.tms_cutime = cpu.tms_cutime - proc->cpu_accepted.tms_cutime;
+       proc->last_request_cpu.tms_cstime = cpu.tms_cstime - proc->cpu_accepted.tms_cstime;
 #endif
        proc->memory = memory;
        fpm_scoreboard_proc_release(proc);
index b3014a5f1dcb0c5240c2dfee3ffdd9e8615f156a..05cca228b1948fec4b7a797124697edb5509a5ff 100644 (file)
@@ -17,6 +17,7 @@ struct timeval;
 
 void fpm_request_check_timed_out(struct fpm_child_s *child, struct timeval *tv, int terminate_timeout, int slowlog_timeout);
 int fpm_request_is_idle(struct fpm_child_s *child);
+const char *fpm_request_get_stage_name(int stage);
 
 enum fpm_request_stage_e {
        FPM_REQUEST_ACCEPTING = 1,
index a68e13ae8dddae68890b3b6fcac09b1b156d24b5..a980df91069c11dada6e24f368ebc8eb08ddd744 100644 (file)
 
 static struct fpm_scoreboard_s *fpm_scoreboard = NULL;
 static int fpm_scoreboard_i = -1;
+#ifdef HAVE_TIMES
+static float fpm_scoreboard_tick;
+#endif
+
 
 int fpm_scoreboard_init_main() /* {{{ */
 {
        struct fpm_worker_pool_s *wp;
        int i;
 
+#ifdef HAVE_TIMES
+#if (defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK))
+       fpm_scoreboard_tick = sysconf(_SC_CLK_TCK);
+#else /* _SC_CLK_TCK */
+#ifdef HZ
+       fpm_scoreboard_tick = HZ;
+#else /* HZ */
+       fpm_scoreboard_tick = 100;
+#endif /* HZ */
+#endif /* _SC_CLK_TCK */
+       zlog(ZLOG_DEBUG, "got clock tick '%.0f'", fpm_scoreboard_tick);
+#endif /* HAVE_TIMES */
+
+
        for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
                if (wp->config->pm_max_children < 1) {
                        zlog(ZLOG_ERROR, "[pool %s] Unable to create scoreboard SHM because max_client is not set", wp->config->name);
@@ -40,7 +58,11 @@ int fpm_scoreboard_init_main() /* {{{ */
                }
                wp->scoreboard->nprocs = wp->config->pm_max_children;
                for (i=0; i<wp->scoreboard->nprocs; i++) {
-                       wp->scoreboard->procs[i] = NULL;
+                       wp->scoreboard->procs[i] = fpm_shm_alloc(sizeof(struct fpm_scoreboard_proc_s));
+                       if (!wp->scoreboard->procs[i]) {
+                               return -1;
+                       }
+                       memset(wp->scoreboard->procs[i], 0, sizeof(struct fpm_scoreboard_proc_s));
                }
 
                wp->scoreboard->pm = wp->config->pm;
@@ -229,6 +251,7 @@ void fpm_scoreboard_child_use(struct fpm_scoreboard_s *scoreboard, int child_ind
                return;
        }
        proc->pid = pid;
+       proc->start_epoch = time(NULL);
 }
 /* }}} */
 
@@ -242,9 +265,8 @@ void fpm_scoreboard_proc_free(struct fpm_scoreboard_s *scoreboard, int child_ind
                return;
        }
 
-       if (scoreboard->procs[child_index]) {
-               fpm_shm_free(scoreboard->procs[child_index], sizeof(struct fpm_scoreboard_proc_s));
-               scoreboard->procs[child_index] = NULL;
+       if (scoreboard->procs[child_index] && scoreboard->procs[child_index]->used > 0) {
+               memset(scoreboard->procs[child_index], 0, sizeof(struct fpm_scoreboard_proc_s));
        }
 
        /* set this slot as free to avoid search on next alloc */
@@ -262,7 +284,7 @@ int fpm_scoreboard_proc_alloc(struct fpm_scoreboard_s *scoreboard, int *child_in
 
        /* first try the slot which is supposed to be free */
        if (scoreboard->free_proc >= 0 && scoreboard->free_proc < scoreboard->nprocs) {
-               if (!scoreboard->procs[scoreboard->free_proc]) {
+               if (scoreboard->procs[scoreboard->free_proc] && !scoreboard->procs[scoreboard->free_proc]->used) {
                        i = scoreboard->free_proc;
                }
        }
@@ -270,7 +292,7 @@ int fpm_scoreboard_proc_alloc(struct fpm_scoreboard_s *scoreboard, int *child_in
        if (i < 0) { /* the supposed free slot is not, let's search for a free slot */
                zlog(ZLOG_DEBUG, "[pool %s] the proc->free_slot was not free. Let's search", scoreboard->pool);
                for (i=0; i<scoreboard->nprocs; i++) {
-                       if (!scoreboard->procs[i]) { /* found */
+                       if (scoreboard->procs[i] && !scoreboard->procs[i]->used) { /* found */
                                break;
                        }
                }
@@ -282,10 +304,7 @@ int fpm_scoreboard_proc_alloc(struct fpm_scoreboard_s *scoreboard, int *child_in
                return -1;
        }
 
-       scoreboard->procs[i] = fpm_shm_alloc(sizeof(struct fpm_scoreboard_proc_s));
-       if (!scoreboard->procs[i]) {
-               return -1;
-       }
+       scoreboard->procs[i]->used = 1;
        *child_index = i;
 
        /* supposed next slot is free */
@@ -299,3 +318,11 @@ int fpm_scoreboard_proc_alloc(struct fpm_scoreboard_s *scoreboard, int *child_in
 }
 /* }}} */
 
+#ifdef HAVE_TIMES
+float fpm_scoreboard_get_tick() /* {{{ */
+{
+       return fpm_scoreboard_tick;
+}
+/* }}} */
+#endif
+
index 00860878cb0272a46ed3f27c76f2346408b17792..136ea481a4a5e0970fcfe15b4a7e01d8c96672f5 100644 (file)
@@ -22,9 +22,13 @@ struct fpm_scoreboard_proc_s {
                atomic_t lock;
                char dummy[16];
        };
+       int used;
+       time_t start_epoch;
        pid_t pid;
+       unsigned long requests;
        enum fpm_request_stage_e request_stage;
        struct timeval accepted;
+       struct timeval duration;
        time_t accepted_epoch;
        struct timeval tv;
        char request_uri[128];
@@ -35,8 +39,9 @@ struct fpm_scoreboard_proc_s {
        char auth_user[32];
 #ifdef HAVE_TIMES
        struct tms cpu_accepted;
-       struct tms cpu_finished;
        struct timeval cpu_duration;
+       struct tms last_request_cpu;
+       struct timeval last_request_cpu_duration;
 #endif
        size_t memory;
 };
@@ -81,4 +86,8 @@ void fpm_scoreboard_child_use(struct fpm_scoreboard_s *scoreboard, int child_ind
 void fpm_scoreboard_proc_free(struct fpm_scoreboard_s *scoreboard, int child_index);
 int fpm_scoreboard_proc_alloc(struct fpm_scoreboard_s *scoreboard, int *child_index);
 
+#ifdef HAVE_TIMES
+float fpm_scoreboard_get_tick();
+#endif
+
 #endif
index 9fab7e7fb3dbd870f47b4bd6b0508d528bc44e98..ac9365de8a0a3bcef8bcf695cf7abbe58ecc3f5b 100644 (file)
@@ -13,6 +13,7 @@
 #include "fpm_scoreboard.h"
 #include "zlog.h"
 #include "fpm_atomic.h"
+#include <ext/standard/html.h>
 
 static char *fpm_status_uri = NULL;
 static char *fpm_status_ping_uri = NULL;
@@ -46,9 +47,12 @@ int fpm_status_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
 int fpm_status_handle_request(TSRMLS_D) /* {{{ */
 {
        struct fpm_scoreboard_s scoreboard, *scoreboard_p;
-//     struct fpm_scoreboard_proc_s proc;
-       char *buffer, *syntax, *time_format, time_buffer[64];
+       struct fpm_scoreboard_proc_s proc;
+       char *buffer, *time_format, time_buffer[64];
        time_t now_epoch;
+       int full, encode;
+       char *short_syntax, *short_post;
+       char *full_pre, *full_syntax, *full_post, *full_separator;
 
        if (!SG(request_info).request_uri) {
                return 0;
@@ -56,7 +60,10 @@ int fpm_status_handle_request(TSRMLS_D) /* {{{ */
 
        /* PING */
        if (fpm_status_ping_uri && fpm_status_ping_response && !strcmp(fpm_status_ping_uri, SG(request_info).request_uri)) {
+               fpm_request_executing();
                sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC);
+               sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC);
+               sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC);
                SG(sapi_headers).http_response_code = 200;
                PUTS(fpm_status_ping_response);
                return 1;
@@ -64,12 +71,15 @@ int fpm_status_handle_request(TSRMLS_D) /* {{{ */
 
        /* STATUS */
        if (fpm_status_uri && !strcmp(fpm_status_uri, SG(request_info).request_uri)) {
+               fpm_request_executing();
 
                scoreboard_p = fpm_scoreboard_get();
                if (!scoreboard_p) {
                        zlog(ZLOG_ERROR, "status: unable to find or access status shared memory");
                        SG(sapi_headers).http_response_code = 500;
                        sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC);
+                       sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC);
+                       sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC);
                        PUTS("Internal error. Please review log file for errors.");
                        return 1;
                }
@@ -78,6 +88,8 @@ int fpm_status_handle_request(TSRMLS_D) /* {{{ */
                        zlog(ZLOG_NOTICE, "[pool %s] status: scoreboard already in used.", scoreboard_p->pool);
                        SG(sapi_headers).http_response_code = 503;
                        sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC);
+                       sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC);
+                       sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC);
                        PUTS("Server busy. Please try again later.");
                        return 1;
                }
@@ -89,38 +101,99 @@ int fpm_status_handle_request(TSRMLS_D) /* {{{ */
                        zlog(ZLOG_ERROR, "[pool %s] invalid status values", scoreboard.pool);
                        SG(sapi_headers).http_response_code = 500;
                        sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC);
+                       sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC);
+                       sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC);
                        PUTS("Internal error. Please review log file for errors.");
                        return 1;
                }
 
+               /* full status ? */
+               full = SG(request_info).request_uri && strstr(SG(request_info).query_string, "full");
+               short_syntax = short_post = NULL;
+               full_separator = full_pre = full_syntax = full_post = NULL;
+               encode = 0;
+
                /* HTML */
                if (SG(request_info).query_string && strstr(SG(request_info).query_string, "html")) {
                        sapi_add_header_ex(ZEND_STRL("Content-Type: text/html"), 1, 1 TSRMLS_CC);
                        time_format = "%d/%b/%Y:%H:%M:%S %z";
-                       syntax =
+                       encode = 1;
+
+                       short_syntax =
+                               "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
+                               "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
+                               "<head><title>PHP-FPM Status Page</title></head>\n"
+                               "<body>\n"
                                "<table>\n"
-                               "<tr><th>pool</th><td>%s</td></tr>\n"
-                               "<tr><th>process manager</th><td>%s</td></tr>\n"
-                               "<tr><th>start time</th><td>%s</td></tr>\n"
-                               "<tr><th>start since</th><td>%lu</td></tr>\n"
-                               "<tr><th>accepted conn</th><td>%lu</td></tr>\n"
+                                       "<tr><th>pool</th><td>%s</td></tr>\n"
+                                       "<tr><th>process manager</th><td>%s</td></tr>\n"
+                                       "<tr><th>start time</th><td>%s</td></tr>\n"
+                                       "<tr><th>start since</th><td>%lu</td></tr>\n"
+                                       "<tr><th>accepted conn</th><td>%lu</td></tr>\n"
+#if HAVE_FPM_LQ
+                                       "<tr><th>listen queue</th><td>%u</td></tr>\n"
+                                       "<tr><th>max listen queue</th><td>%u</td></tr>\n"
+                                       "<tr><th>listen queue len</th><td>%d</td></tr>\n"
+#endif
+                                       "<tr><th>idle processes</th><td>%d</td></tr>\n"
+                                       "<tr><th>active processes</th><td>%d</td></tr>\n"
+                                       "<tr><th>total processes</th><td>%d</td></tr>\n"
+                                       "<tr><th>max active processes</th><td>%d</td></tr>\n"
+                                       "<tr><th>max children reached</th><td>%u</td></tr>\n"
+                               "</table>\n";
+
+                       if (!full) {
+                               short_post = "</body></html>";
+                       } else {
+                               full_pre =
+                                       "<table border=\"1\">\n"
+                                       "<tr>"
+                                               "<th>pid</th>"
+                                               "<th>state</th>"
+                                               "<th>start time</th>"
+                                               "<th>start since</th>"
+                                               "<th>requests</th>"
+                                               "<th>request duration</th>"
+                                               "<th>request method</th>"
+                                               "<th>request uri</th>"
+                                               "<th>content length</th>"
+                                               "<th>user</th>"
+                                               "<th>script</th>"
 #if HAVE_FPM_LQ
-                               "<tr><th>listen queue</th><td>%u</td></tr>\n"
-                               "<tr><th>max listen queue</th><td>%u</td></tr>\n"
-                               "<tr><th>listen queue len</th><td>%d</td></tr>\n"
+                                               "<th>last request cpu</th>"
 #endif
-                               "<tr><th>idle processes</th><td>%d</td></tr>\n"
-                               "<tr><th>active processes</th><td>%d</td></tr>\n"
-                               "<tr><th>total processes</th><td>%d</td></tr>\n"
-                               "<tr><th>max active processes</th><td>%d</td></tr>\n"
-                               "<tr><th>max children reached</th><td>%u</td></tr>\n"
-                               "</table>";
+                                               "<th>last request memory</th>"
+                                       "</tr>\n";
+
+                               full_syntax =
+                                       "<tr>"
+                                               "<td>%d</td>"
+                                               "<td>%s</td>"
+                                               "<td>%s</td>"
+                                               "<td>%lu</td>"
+                                               "<td>%lu</td>"
+                                               "<td>%lu</td>"
+                                               "<td>%s</td>"
+                                               "<td>%s%s%s</td>"
+                                               "<td>%zu</td>"
+                                               "<td>%s</td>"
+                                               "<td>%s</td>"
+#if HAVE_FPM_LQ
+                                               "<td>%.2f</td>"
+#endif
+                                               "<td>%zu</td>"
+                                       "</tr>\n";
+
+                               full_post = "</table></body></html>";
+                       }
 
                /* XML */
                } else if (SG(request_info).request_uri && strstr(SG(request_info).query_string, "xml")) {
                        sapi_add_header_ex(ZEND_STRL("Content-Type: text/xml"), 1, 1 TSRMLS_CC);
                        time_format = "%s";
-                       syntax =
+                       encode = 1;
+
+                       short_syntax =
                                "<?xml version=\"1.0\" ?>\n"
                                "<status>\n"
                                "<pool>%s</pool>\n"
@@ -137,14 +210,40 @@ int fpm_status_handle_request(TSRMLS_D) /* {{{ */
                                "<active-processes>%d</active-processes>\n"
                                "<total-processes>%d</total-processes>\n"
                                "<max-active-processes>%d</max-active-processes>\n"
-                               "<max-children-reached>%u</max-children-reached>\n"
-                               "</status>";
+                               "<max-children-reached>%u</max-children-reached>\n";
+
+                               if (!full) {
+                                       short_post = "</status>";
+                               } else {
+                                       full_pre = "<processes>\n";
+                                       full_syntax = 
+                                               "<process>"
+                                                       "<pid>%d</pid>"
+                                                       "<state>%s</state>"
+                                                       "<start-time>%s</start-time>"
+                                                       "<start-since>%lu</start-since>"
+                                                       "<requests>%lu</requests>"
+                                                       "<request-duration>%lu</request-duration>"
+                                                       "<request-method>%s</request-method>"
+                                                       "<request-uri>%s%s%s</request-uri>"
+                                                       "<content-length>%zu</content-length>"
+                                                       "<user>%s</user>"
+                                                       "<script>%s</script>"
+#if HAVE_FPM_LQ
+                                                       "<last-request-cpu>%.2f</last-request-cpu>"
+#endif
+                                                       "<last-request-memory>%zu</last-request-memory>"
+                                               "</process>\n"
+                                       ;
+                                       full_post = "</processes>\n</status>";
+                               }
 
                        /* JSON */
                } else if (SG(request_info).request_uri && strstr(SG(request_info).query_string, "json")) {
                        sapi_add_header_ex(ZEND_STRL("Content-Type: application/json"), 1, 1 TSRMLS_CC);
                        time_format = "%s";
-                       syntax =
+
+                       short_syntax =
                                "{"
                                "\"pool\":\"%s\","
                                "\"process manager\":\"%s\","
@@ -160,14 +259,41 @@ int fpm_status_handle_request(TSRMLS_D) /* {{{ */
                                "\"active processes\":%d,"
                                "\"total processes\":%d,"
                                "\"max active processes\":%d,"
-                               "\"max children reached\":%u"
-                               "}";
+                               "\"max children reached\":%u";
+
+                       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\","
+#if HAVE_FPM_LQ
+                                       "\"last request cpu\":%.2f,"
+#endif
+                                       "\"last request memory\":%zu"
+                                       "}";
+
+                               full_post = "]}";
+                       }
 
                /* TEXT */
                } else {
                        sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC);
                        time_format = "%d/%b/%Y:%H:%M:%S %z";
-                       syntax =
+
+                       short_syntax =
                                "pool:                 %s\n"
                                "process manager:      %s\n"
                                "start time:           %s\n"
@@ -183,11 +309,35 @@ int fpm_status_handle_request(TSRMLS_D) /* {{{ */
                                "total processes:      %d\n"
                                "max active processes: %d\n"
                                "max children reached: %u\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"
+#ifdef HAVE_FPM_LQ
+                                               "last request cpu:     %.2f\n"
+#endif
+                                               "last request memory:  %zu\n";
+                               }
                }
 
+               sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC);
+               sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC);
+
                strftime(time_buffer, sizeof(time_buffer) - 1, time_format, localtime(&scoreboard.start_epoch));
                now_epoch = time(NULL);
-               spprintf(&buffer, 0, syntax,
+               spprintf(&buffer, 0, short_syntax,
                                scoreboard.pool,
                                scoreboard.pm == PM_STYLE_STATIC ? "static" : "dynamic",
                                time_buffer,
@@ -208,6 +358,96 @@ int fpm_status_handle_request(TSRMLS_D) /* {{{ */
                PUTS(buffer);
                efree(buffer);
 
+               if (short_post) {
+                       PUTS(short_post);
+               }
+
+               /* no need to test the var 'full' */
+               if (full_syntax) {
+                       int i, len, first;
+                       char *query_string;
+                       struct timeval duration, now;
+#ifdef HAVE_FPM_LQ
+                       float cpu;
+#endif
+
+                       fpm_clock_get(&now);
+
+                       if (full_pre) {
+                               PUTS(full_pre);
+                       }
+
+                       first = 1;
+                       for (i=0; i<scoreboard_p->nprocs; i++) {
+                               if (!scoreboard_p->procs[i] || !scoreboard_p->procs[i]->used) {
+                                       continue;
+                               }
+                               proc = *scoreboard_p->procs[i];
+
+                               if (first) {
+                                       first = 0;
+                               } else {
+                                       if (full_separator) {
+                                               PUTS(full_separator);
+                                       }
+                               }
+
+                               query_string = NULL;
+                               len = 0;
+                               if (proc.query_string[0] != '\0') {
+                                       if (!encode) {
+                                               query_string = proc.query_string;
+                                       } else {
+                                               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);
+                                       }
+                               }
+
+#ifdef HAVE_FPM_LQ
+                               /* 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.;
+                               }
+#endif
+
+                               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,
+                                       proc.pid,
+                                       fpm_request_get_stage_name(proc.request_stage),
+                                       time_buffer,
+                                       now_epoch - proc.start_epoch,
+                                       proc.requests,
+                                       duration.tv_sec * 1000000UL + duration.tv_usec,
+                                       proc.request_method[0] != '\0' ? proc.request_method : "-",
+                                       proc.request_uri[0] != '\0' ? proc.request_uri : "-",
+                                       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 : "-",
+#ifdef HAVE_FPM_LQ
+                                       proc.request_stage == FPM_REQUEST_ACCEPTING ? cpu : 0.,
+#endif
+                                       proc.request_stage == FPM_REQUEST_ACCEPTING ? proc.memory : 0);
+                               PUTS(buffer);
+                               efree(buffer);
+
+                               if (len > 0 && query_string) {
+                                       efree(query_string);
+                               }
+                       }
+
+                       if (full_post) {
+                               PUTS(full_post);
+                       }
+               }
+
                return 1;
        }
 
index 1d6f2c66d63f94e0b4733a5eff0fa0a53d942174..6cd6c1bf522aeda54f43ca590464284f1878dfad 100644 (file)
@@ -182,34 +182,98 @@ pm.max_children = 50
 ;pm.max_requests = 500
 
 ; The URI to view the FPM status page. If this value is not set, no URI will be
-; recognized as a status page. By default, the status page shows the following
-; information:
-;   accepted conn        - the number of request accepted by the pool;
+; recognized as a status page. It shows the following informations:
 ;   pool                 - the name of the pool;
 ;   process manager      - static or dynamic;
+;   start time           - the date and time FPM has started;
+;   start since          - number of seconds since FPM has started;
+;   accepted conn        - the number of request accepted by the pool;
+;   listen queue         - the number of request in the queue of pending
+;                          connections (see backlog in listen(2));
+;   max listen queue     - the maximum number of requests in the queue
+;                          of pending connections since FPM has started;
+;   listen queue len     - the size of the socket queue of pending connections;
 ;   idle processes       - the number of idle processes;
 ;   active processes     - the number of active processes;
-;   total processes      - the number of idle + active processes.
+;   total processes      - the number of idle + active processes;
+;   max active processes - the maximum number of active processes since FPM
+;                          has started;
 ;   max children reached - number of times, the process limit has been reached,
 ;                          when pm tries to start more children (works only for
-;                          pm 'dynamic')
-; The values of 'idle processes', 'active processes' and 'total processes' are
-; updated each second. The value of 'accepted conn' is updated in real time.
+;                          pm 'dynamic');
+; Value are updated in real time.
 ; Example output:
-;   accepted conn:        12073
 ;   pool:                 www
 ;   process manager:      static
-;   idle processes:       35
-;   active processes:     65
-;   total processes:      100
-;   max children reached: 1
+;   start time:           01/Jul/2011:17:53:49 +0200
+;   start since:          62636
+;   accepted conn:        190460
+;   listen queue:         0
+;   max listen queue:     1
+;   listen queue len:     42
+;   idle processes:       4
+;   active processes:     11
+;   total processes:      15
+;   max active processes: 12
+;   max children reached: 0
+;
 ; By default the status page output is formatted as text/plain. Passing either
-; 'html', 'xml' or 'json' as a query string will return the corresponding output
-; syntax. Example:
+; 'html', 'xml' or 'json' in the query string will return the corresponding
+; output syntax. Example:
 ;   http://www.foo.bar/status
 ;   http://www.foo.bar/status?json
 ;   http://www.foo.bar/status?html
 ;   http://www.foo.bar/status?xml
+;
+; By default the status page only outputs short status. Passing 'full' in the
+; query string will also return status for each pool process.
+; Example: 
+;   http://www.foo.bar/status?full
+;   http://www.foo.bar/status?json&full
+;   http://www.foo.bar/status?html&full
+;   http://www.foo.bar/status?xml&full
+; The Full status returns for each process:
+;   pid                  - the PID of the process;
+;   state                - the state of the process (Idle, Running, ...);
+;   start time           - the date and time the process has started;
+;   start since          - the number of seconds since the process has started;
+;   requests             - the number of requests the process has served;
+;   request duration     - the duration in µs of the requests;
+;   request method       - the request method (GET, POST, ...);
+;   request URI          - the request URI with the query string;
+;   content length       - the content length of the request (only with POST);
+;   user                 - the user (PHP_AUTH_USER) (or '-' if not set);
+;   script               - the main script called (or '-' if not set);
+;   last request cpu     - the %cpu the last request consumed
+;                          it's always 0 if the process is not in Idle state
+;                          because CPU calculation is done when the request
+;                          processing has terminated;
+;   last request memory  - the max amount of memory the last request consumed
+;                          it's always 0 if the process is not in Idle state
+;                          because memory calculation is done when the request
+;                          processing has terminated;
+; If the process is in Idle state, then informations are related to the
+; last request the process has served. Otherwise informations are related to
+; the current request being served.
+; Example output:
+;   ************************
+;   pid:                  31330
+;   state:                Running
+;   start time:           01/Jul/2011:17:53:49 +0200
+;   start since:          63087
+;   requests:             12808
+;   request duration:     1250261
+;   request method:       GET
+;   request URI:          /test_mem.php?N=10000
+;   content length:       0
+;   user:                 -
+;   script:               /home/fat/web/docs/php/test_mem.php
+;   last request cpu:     0.00
+;   last request memory:  0
+;
+; Note: There is a real-time FPM status monitoring sample web page available
+;       It's available in: @EXPANDED_DATADIR@/fpm/status.html
+;
 ; Note: The value must start with a leading slash (/). The value can be
 ;       anything, but it may not be a good idea to use the .php extension or it
 ;       may conflict with a real PHP file.
diff --git a/sapi/fpm/status.html b/sapi/fpm/status.html
new file mode 100644 (file)
index 0000000..86492d7
--- /dev/null
@@ -0,0 +1,459 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+       $Id$
+       (c) 2011 Jerome Loyet
+       The PHP License, version 3.01
+       This is sample real-time status page for FPM. You can change it to better feet your needs.
+-->
+       <head> 
+               <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+               <style type="text/css"> 
+                       body {background-color: #ffffff; color: #000000;}
+                       body, td, th, h1, h2 {font-family: sans-serif;}
+                       pre {margin: 0px; font-family: monospace;}
+                       a:link {color: #000099; text-decoration: none; background-color: #ffffff;}
+                       a:hover {text-decoration: underline;}
+                       table {border-collapse: collapse;}
+                       .center {text-align: center;}
+                       .center table { margin-left: auto; margin-right: auto; text-align: left;}
+                       .center th { text-align: center !important; }
+                       td, th { border: 1px solid #000000; font-size: 75%; vertical-align: baseline;}
+                       h1 {font-size: 150%;}
+                       h2 {font-size: 125%;}
+                       .p {text-align: left;}
+                       .e {background-color: #ccccff; font-weight: bold; color: #000000;}
+                       .h {background-color: #9999cc; font-weight: bold; color: #000000;}
+
+                       .v {background-color: #cccccc; color: #000000;}
+                       .w {background-color: #ccccff; color: #000000;}
+
+                       .h th {
+                               cursor: pointer;
+                       }
+                       img {float: right; border: 0px;}
+                       hr {width: 600px; background-color: #cccccc; border: 0px; height: 1px; color: #000000;}
+               </style> 
+       <title>PHP-FPM status page</title>
+       <meta name="ROBOTS" content="NOINDEX,NOFOLLOW,NOARCHIVE" /></head> 
+       <body>
+               <div class="center">
+                       <table border="0" cellpadding="3" width="95%">
+                               <tr class="h">
+                                       <td> 
+                                               <a href="http://www.php.net/"><img border="0" src="https://static.php.net/www.php.net/images/php.gif" alt="PHP Logo" /></a><h1 class="p">PHP-FPM real-time status page</h1>
+                                       </td>
+                               </tr>
+                       </table>
+                       <br />
+                       <table border="0" cellpadding="3" width="95%">
+                               <tr><td class="e">Status URL</td><td class="v"><input type="text" id="url" size="45" /></td></tr> 
+                               <tr><td class="e">Ajax status</td><td class="v" id="status"></td></tr> 
+                               <tr><td class="e">Refresh Rate</td><td class="v"><input type="text" id="rate" value="1" /></td></tr> 
+                               <tr>
+                                       <td class="e">Actions</td>
+                                       <td class="v">
+                                               <button onclick="javascript:refresh();">Manual Refresh</button>
+                                               <button id="play" onclick="javascript:playpause();">Play</button>
+                                       </td>
+                               </tr> 
+                       </table>
+                       <h1>Pool Status</h1> 
+                       <table border="0" cellpadding="3" width="95%" id="short">
+                               <tr style="display: none;"><td>&nbsp;</td></tr>
+                       </table>
+                       <h1>Active Processes status</h1> 
+                       <table border="0" cellpadding="3" width="95%" id="active">
+                               <tr class="h"><th>PID&darr;</th><th>Start Time</th><th>Start Since</th><th>Requests Served</th><th>Request Duration</th><th>Request method</th><th>Request URI</th><th>Content Length</th><th>User</th><th>Script</th></tr>
+                       </table>
+                       <h1>Idle Processes status</h1> 
+                       <table border="0" cellpadding="3" width="95%" id="idle">
+                               <tr class="h"><th>PID&darr;</th><th>Start Time</th><th>Start Since</th><th>Requests Served</th><th>Request Duration</th><th>Request method</th><th>Request URI</th><th>Content Length</th><th>User</th><th>Script</th><th>Last Request %CPU</th><th>Last Request Memory</th></tr>
+                       </table>
+               </div>
+               <p>
+                       <a href="http://validator.w3.org/check?uri=referer">
+                               <img src="http://www.w3.org/Icons/valid-xhtml10" alt="Valid XHTML 1.0 Transitional" height="31" width="88" />
+                       </a>
+                </p>
+               <script type="text/javascript">
+<!--
+                       var xhr_object = null;
+                       var doc_url = document.getElementById("url");
+                       var doc_rate = document.getElementById("rate");
+                       var doc_status = document.getElementById("status");
+                       var doc_play = document.getElementById("play");
+                       var doc_short = document.getElementById("short");
+                       var doc_active = document.getElementById("active");
+                       var doc_idle = document.getElementById("idle");
+                       var rate = 0;
+                       var play=0;
+                       var delay = 1000;
+                       var order_active_index = 0;
+                       var order_active_reverse = 0;
+                       var order_idle_index = 0;
+                       var order_idle_reverse = 0;
+                       var sort_index;
+                       var sort_order;
+
+                       doc_url.value = location.protocol + '//' + location.host + "/status?json&full";
+
+                       ths = document.getElementsByTagName("th");
+                       for (var i=0; i<ths.length; i++) {
+                               var th = ths[i];
+                               if (th.parentNode.className == "h") {
+                                       th.onclick = function() { order(this); return false; };
+                               }
+                       }
+
+                       xhr_object = create_ajax();
+
+                       function create_ajax() {
+                               if (window.XMLHttpRequest) {
+                                       return new XMLHttpRequest();
+                               }
+                               var names = [
+                                       "Msxml2.XMLHTTP.6.0",
+                                       "Msxml2.XMLHTTP.3.0",
+                                       "Msxml2.XMLHTTP",
+                                       "Microsoft.XMLHTTP"
+                               ];
+                               for(var i in names)
+                               {
+                                       try {
+                                               return new ActiveXObject(names[i]);
+                                       }       catch(e){}
+                               }
+                               alert("Browser not compatible ...");
+                       }
+
+                       function order(cell) {
+                               var table;
+
+                               if (cell.constructor != HTMLTableCellElement && cell.constructor != HTMLTableHeaderCellElement) {
+                                       return;
+                               }
+
+                               table = cell.parentNode.parentNode.parentNode;
+
+                               if (table == doc_active) {
+                                       if (order_active_index == cell.cellIndex) {
+                                               if (order_active_reverse == 0) {
+                                                       cell.innerHTML = cell.innerHTML.replace(/.$/, "&uarr;");
+                                                       order_active_reverse = 1;
+                                               } else {
+                                                       cell.innerHTML = cell.innerHTML.replace(/.$/, "&darr;");
+                                                       order_active_reverse = 0;
+                                               }
+                                       } else {
+                                               var c = doc_active.rows[0].cells[order_active_index];
+                                               c.innerHTML = c.innerHTML.replace(/.$/, "");
+                                               cell.innerHTML = cell.innerHTML.replace(/$/, order_active_reverse == 0 ? "&darr;" : "&uarr;");
+                                               order_active_index = cell.cellIndex;
+                                       }
+                                       reorder(table, order_active_index, order_active_reverse);
+                                       return;
+                               }
+
+                               if (table == doc_idle) {
+                                       if (order_idle_index == cell.cellIndex) {
+                                               if (order_idle_reverse == 0) {
+                                                       cell.innerHTML = cell.innerHTML.replace(/.$/, "&uarr;");
+                                                       order_idle_reverse = 1;
+                                               } else {
+                                                       cell.innerHTML = cell.innerHTML.replace(/.$/, "&darr;");
+                                                       order_idle_reverse = 0;
+                                               }
+                                       } else {
+                                               var c = doc_idle.rows[0].cells[order_idle_index];
+                                               c.innerHTML = c.innerHTML.replace(/.$/, "");
+                                               cell.innerHTML = cell.innerHTML.replace(/$/, order_idle_reverse == 0 ? "&darr;" : "&uarr;");
+                                               order_idle_index = cell.cellIndex;
+                                       }
+                                       reorder(table, order_idle_index, order_idle_reverse);
+                                       return;
+                               }
+                       }
+
+                       function reorder(table, index, order) {
+                               var rows = [];
+                               while (table.rows.length > 1) {
+                                       rows.push(table.rows[1]);
+                                       table.deleteRow(1);
+                               }
+                               sort_index = index;
+                               sort_order = order;
+                               rows.sort(sort_table);
+                               for (var i in rows) {
+                                       table.appendChild(rows[i]);
+                               }
+                               var odd = 1;
+                               for (var i=1; i<table.rows.length; i++) {
+                                       table.rows[i].className = odd++ % 2 == 0 ? "v" : "w";
+                               }
+                               return;
+                       }
+
+                       function sort_table(a, b) {
+                               if (a.cells[0].tagName == "TH") return -1;
+                               if (b.cells[0].tagName == "TH") return 1;
+
+                               if (a.cells[sort_index].__search_t == 0) { /* integer */
+                                       if (!sort_order) return a.cells[sort_index].__search_v - b.cells[sort_index].__search_v;
+                                       return b.cells[sort_index].__search_v - a.cells[sort_index].__search_v;;
+                               }
+
+                               /* string */
+                               if (!sort_order) return a.cells[sort_index].__search_v.localeCompare(b.cells[sort_index].__search_v);
+                               else return b.cells[sort_index].__search_v.localeCompare(a.cells[sort_index].__search_v);
+                       }
+
+                       function playpause() {
+                               rate = 0;
+                               if (play) {
+                                       play = 0;
+                                       doc_play.innerHTML = "Play";
+                                       doc_rate.disabled = false;
+                               } else {
+                                       delay = parseInt(doc_rate.value);
+                                       if (!delay || delay < 1) {
+                                               doc_status.innerHTML = "Not valid 'refresh' value";
+                                               return;
+                                       }
+                                       play = 1;
+                                       doc_rate.disabled = true;
+                                       doc_play.innerHTML = "Pause";
+                                       setTimeout("callback()", delay * 1000);
+                               }
+                       }
+
+                       function refresh() {
+                               if (xhr_object == null) return;
+                               if (xhr_object.readyState > 0 && xhr_object.readyState < 4) {
+                                       return; /* request is running */
+                               }
+                               xhr_object.open("GET", doc_url.value, true);
+                               xhr_object.onreadystatechange = function() {
+                                       switch(xhr_object.readyState) {
+                                               case 0:
+                                                       doc_status.innerHTML = "uninitialized";
+                                                       break;
+                                               case 1:
+                                                       doc_status.innerHTML = "loading ...";
+                                                       break;
+                                               case 2:
+                                                       doc_status.innerHTML = "loaded";
+                                                       break;
+                                               case 3:
+                                                       doc_status.innerHTML = "interactive";
+                                                       break;
+                                               case 4:
+                                                       doc_status.innerHTML = "complete";
+                                                       if (xhr_object.status == 200) {
+                                                               fpm_status(xhr_object.responseText);
+                                                       } else {
+                                                               doc_status.innerHTML = "Error " + xhr_object.status;
+                                                       }
+                                                       break;
+                                       }
+                               }
+                               xhr_object.send();
+                       }
+
+                       function callback() {
+                               if (!play) return;
+                               refresh();
+                               setTimeout("callback()", delay * 1000);
+                       }
+
+                       function fpm_status(txt) {
+                               var json = null;
+
+                               while (doc_short.rows.length > 0) {
+                                       doc_short.deleteRow(0);
+                               }
+
+                               while (doc_active.rows.length > 1) {
+                                       doc_active.deleteRow(1);
+                               }
+
+                               while (doc_idle.rows.length > 1) {
+                                       doc_idle.deleteRow(1);
+                               }
+
+                               try {
+                                       json = JSON.parse(txt);
+                               } catch (e) {
+                                       doc_status.innerHTML =  "Error while parsing json: '" + e + "': <br /><pre>" + txt + "</pre>";
+                                       return;
+                               }
+
+                               for (var key in json) {
+                                       if (key == "processes") continue;
+                                       if (key == "state") continue;
+                                       var row = doc_short.insertRow(doc_short.rows.length);
+                                       var value = json[key];
+                                       if (key == "start time") {
+                                               value = new Date(value * 1000).toLocaleString();
+                                       }
+                                       if (key == "start since") {
+                                               value = time_s(value);
+                                       }
+                                       var cell = row.insertCell(row.cells.length);
+                                       cell.className = "e";
+                                       cell.innerHTML = key;
+
+                                       cell = row.insertCell(row.cells.length);
+                                       cell.className = "v";
+                                       cell.innerHTML = value;
+                               }
+
+                               if (json.processes) {
+                                       process_full(json.processes, doc_active, "Idle", 0, 0);
+                                       reorder(doc_active, order_active_index, order_active_reverse);
+
+                                       process_full(json.processes, doc_idle, "Idle", 1, 1);
+                                       reorder(doc_idle, order_idle_index, order_idle_reverse);
+                               }
+                       }
+
+                       function process_full(processes, table, state, equal, cpumem) {
+                               var odd = 1;
+
+                               for (var i in processes) {
+                                       var proc = processes[i];
+                                       if ((equal && proc.state == state) || (!equal && proc.state != state)) {
+                                               var c = odd++ % 2 == 0 ? "v" : "w";
+                                               var row = table.insertRow(-1);
+                                               row.className = c;
+                                               row.insertCell(-1).innerHTML = proc.pid;
+                                               row.cells[row.cells.length - 1].__search_v = proc.pid;
+                                               row.cells[row.cells.length - 1].__search_t = 0;
+
+                                               row.insertCell(-1).innerHTML = date(proc['start time'] * 1000);;
+                                               row.cells[row.cells.length - 1].__search_v = proc['start time'];
+                                               row.cells[row.cells.length - 1].__search_t = 0;
+
+                                               row.insertCell(-1).innerHTML = time_s(proc['start since']);
+                                               row.cells[row.cells.length - 1].__search_v = proc['start since'];
+                                               row.cells[row.cells.length - 1].__search_t = 0;
+
+                                               row.insertCell(-1).innerHTML = proc.requests;
+                                               row.cells[row.cells.length - 1].__search_v = proc.requests;
+                                               row.cells[row.cells.length - 1].__search_t = 0;
+
+                                               row.insertCell(-1).innerHTML = time_u(proc['request duration']);
+                                               row.cells[row.cells.length - 1].__search_v = proc['request duration'];
+                                               row.cells[row.cells.length - 1].__search_t = 0;
+
+                                               row.insertCell(-1).innerHTML = proc['request method'];
+                                               row.cells[row.cells.length - 1].__search_v = proc['request method'];
+                                               row.cells[row.cells.length - 1].__search_t = 1;
+
+                                               row.insertCell(-1).innerHTML = proc['request uri'];
+                                               row.cells[row.cells.length - 1].__search_v = proc['request uri'];
+                                               row.cells[row.cells.length - 1].__search_t = 1;
+
+                                               row.insertCell(-1).innerHTML = proc['content length'];
+                                               row.cells[row.cells.length - 1].__search_v = proc['content length'];
+                                               row.cells[row.cells.length - 1].__search_t = 0;
+
+                                               row.insertCell(-1).innerHTML = proc.user;
+                                               row.cells[row.cells.length - 1].__search_v = proc.user;
+                                               row.cells[row.cells.length - 1].__search_t = 1;
+
+                                               row.insertCell(-1).innerHTML = proc.script;
+                                               row.cells[row.cells.length - 1].__search_v = proc.script;
+                                               row.cells[row.cells.length - 1].__search_t = 1;
+
+                                               if (cpumem) {
+                                                       row.insertCell(-1).innerHTML = cpu(proc['last request cpu']);
+                                                       row.cells[row.cells.length - 1].__search_v = proc['last request cpu'];
+                                                       row.cells[row.cells.length - 1].__search_t = 0;
+
+                                                       row.insertCell(-1).innerHTML = memory(proc['last request memory']);
+                                                       row.cells[row.cells.length - 1].__search_v = proc['last request memory'];
+                                                       row.cells[row.cells.length - 1].__search_t = 0;
+                                               }
+                                       }
+                               }
+                       }
+
+                       function date(d) {
+                               var t = new Date(d);
+                               var r = "";
+
+                               r += (t.getDate() < 10 ? '0' : '') + t.getDate();
+                               r += '/';
+                               r += (t.getMonth() + 1 < 10 ? '0' : '') + (t.getMonth() + 1);
+                               r += '/';
+                               r += t.getFullYear();
+                               r += ' ';
+                               r += (t.getHours() < 10 ? '0' : '') + t.getHours();
+                               r += ':';
+                               r += (t.getMinutes() < 10 ? '0' : '') + t.getMinutes();
+                               r += ':';
+                               r += (t.getSeconds() < 10 ? '0' : '') + t.getSeconds();
+
+
+                               return r;
+                       }
+
+                       function cpu(c) {
+                               if (c == 0) return 0;
+                               return c + "%";
+                       }
+
+                       function memory(mem) {
+                               if (mem == 0) return 0;
+                               if (mem < 1024) {
+                                       return mem + "B";
+                               }
+                               if (mem < 1024 * 1024) {
+                                       return mem/1024 + "KB";
+                               }
+                               if (mem < 1024*1024*1024) {
+                                       return mem/1024/1024 + "MB";
+                               }
+                       }
+
+                       function time_s(t) {
+                               var r = "";
+                               if (t < 60) {
+                                       return t + 's';
+                               }
+
+                               r = (t % 60) + 's';
+                               t = Math.floor(t / 60);
+                               if (t < 60) {
+                                       return t + 'm ' + r;
+                               }
+
+                               r = (t % 60) + 'm ' + r;
+                               t = Math.floor(t/60);
+
+                               if (t < 24) {
+                                       return t + 'h ' + r;
+                               }
+
+                               return Math.floor(t/24) + 'd ' + (t % 24) + 'h ' + t;
+                       }
+
+                       function time_u(t) {
+                               var r = "";
+                               if (t < 1000) {
+                                       return t + '&micro;s'
+                               }
+
+                               r = (t % 1000) + '&micro;s';
+                               t = Math.floor(t / 1000);
+                               if (t < 1000) {
+                                       return t + 'ms ' + r;
+                               }
+
+                               return time_s(Math.floor(t/1000)) + ' ' + (t%1000) + 'ms ' + r;
+                       }
+-->
+               </script>
+       </body>
+</html>