/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* Status Module. Display lots of internal data about how Apache is * performing and the state of all children processes. * * To enable this, add the following lines into any config file: * * * SetHandler server-status * * * You may want to protect this location by password or domain so no one * else can look at it. Then you can access the statistics with a URL like: * * http://your_server_name/server-status * * /server-status - Returns page using tables * /server-status?notable - Returns page for browsers without table support * /server-status?refresh - Returns page with 1 second refresh * /server-status?refresh=6 - Returns page with refresh every 6 seconds * /server-status?auto - Returns page with data for automatic parsing * * Mark Cox, mark@ukweb.com, November 1995 * * 12.11.95 Initial version for www.telescope.org * 13.3.96 Updated to remove rprintf's [Mark] * 18.3.96 Added CPU usage, process information, and tidied [Ben Laurie] * 18.3.96 Make extra Scoreboard variables #definable * 25.3.96 Make short report have full precision [Ben Laurie suggested] * 25.3.96 Show uptime better [Mark/Ben Laurie] * 29.3.96 Better HTML and explanation [Mark/Rob Hartill suggested] * 09.4.96 Added message for non-STATUS compiled version * 18.4.96 Added per child and per slot counters [Jim Jagielski] * 01.5.96 Table format, cleanup, even more spiffy data [Chuck Murcko/Jim J.] * 18.5.96 Adapted to use new rprintf() routine, incidentally fixing a missing * piece in short reports [Ben Laurie] * 21.5.96 Additional Status codes (DNS and LOGGING only enabled if * extended STATUS is enabled) [George Burgyan/Jim J.] * 10.8.98 Allow for extended status info at runtime (no more STATUS) * [Jim J.] */ #define CORE_PRIVATE #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_protocol.h" #include "http_main.h" #include "ap_mpm.h" #include "util_script.h" #include #include "scoreboard.h" #include "http_log.h" #include "mod_status.h" #if APR_HAVE_UNISTD_H #include #endif #define APR_WANT_STRFUNC #include "apr_want.h" #include "apr_strings.h" #ifdef NEXT #if (NX_CURRENT_COMPILER_RELEASE == 410) #ifdef m68k #define HZ 64 #else #define HZ 100 #endif #else #include #endif #endif /* NEXT */ #define STATUS_MAXLINE 64 #define KBYTE 1024 #define MBYTE 1048576L #define GBYTE 1073741824L #ifndef DEFAULT_TIME_FORMAT #define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z" #endif #define STATUS_MAGIC_TYPE "application/x-httpd-status" module AP_MODULE_DECLARE_DATA status_module; int server_limit, thread_limit; #ifdef HAVE_TIMES /* ugh... need to know if we're running with a pthread implementation * such as linuxthreads that treats individual threads as distinct * processes; that affects how we add up CPU time in a process */ static pid_t child_pid; #endif /* Implement 'ap_run_status_hook'. */ APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ap, STATUS, int, status_hook, (request_rec *r, int flags), (r, flags), OK, DECLINED) /* * command-related code. This is here to prevent use of ExtendedStatus * without status_module included. */ static const char *set_extended_status(cmd_parms *cmd, void *dummy, int arg) { const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } ap_extended_status = arg; return NULL; } static const command_rec status_module_cmds[] = { AP_INIT_FLAG("ExtendedStatus", set_extended_status, NULL, RSRC_CONF, "\"On\" to enable extended status information, \"Off\" to disable"), {NULL} }; /* Format the number of bytes nicely */ static void format_byte_out(request_rec *r, apr_off_t bytes) { if (bytes < (5 * KBYTE)) ap_rprintf(r, "%d B", (int) bytes); else if (bytes < (MBYTE / 2)) ap_rprintf(r, "%.1f kB", (float) bytes / KBYTE); else if (bytes < (GBYTE / 2)) ap_rprintf(r, "%.1f MB", (float) bytes / MBYTE); else ap_rprintf(r, "%.1f GB", (float) bytes / GBYTE); } static void format_kbyte_out(request_rec *r, apr_off_t kbytes) { if (kbytes < KBYTE) ap_rprintf(r, "%d kB", (int) kbytes); else if (kbytes < MBYTE) ap_rprintf(r, "%.1f MB", (float) kbytes / KBYTE); else ap_rprintf(r, "%.1f GB", (float) kbytes / MBYTE); } static void show_time(request_rec *r, apr_interval_time_t tsecs) { int days, hrs, mins, secs; secs = (int)(tsecs % 60); tsecs /= 60; mins = (int)(tsecs % 60); tsecs /= 60; hrs = (int)(tsecs % 24); days = (int)(tsecs / 24); if (days) ap_rprintf(r, " %d day%s", days, days == 1 ? "" : "s"); if (hrs) ap_rprintf(r, " %d hour%s", hrs, hrs == 1 ? "" : "s"); if (mins) ap_rprintf(r, " %d minute%s", mins, mins == 1 ? "" : "s"); if (secs) ap_rprintf(r, " %d second%s", secs, secs == 1 ? "" : "s"); } /* Main handler for x-httpd-status requests */ /* ID values for command table */ #define STAT_OPT_END -1 #define STAT_OPT_REFRESH 0 #define STAT_OPT_NOTABLE 1 #define STAT_OPT_AUTO 2 struct stat_opt { int id; const char *form_data_str; const char *hdr_out_str; }; static const struct stat_opt status_options[] = /* see #defines above */ { {STAT_OPT_REFRESH, "refresh", "Refresh"}, {STAT_OPT_NOTABLE, "notable", NULL}, {STAT_OPT_AUTO, "auto", NULL}, {STAT_OPT_END, NULL, NULL} }; static char status_flags[SERVER_NUM_STATUS]; static int status_handler(request_rec *r) { const char *loc; apr_time_t nowtime; apr_interval_time_t up_time; int j, i, res; int ready; int busy; unsigned long count; unsigned long lres, my_lres, conn_lres; apr_off_t bytes, my_bytes, conn_bytes; apr_off_t bcount, kbcount; long req_time; #ifdef HAVE_TIMES float tick; int times_per_thread = getpid() != child_pid; #endif int short_report; int no_table_report; worker_score *ws_record; process_score *ps_record; char *stat_buffer; pid_t *pid_buffer; clock_t tu, ts, tcu, tcs; if (strcmp(r->handler, STATUS_MAGIC_TYPE) && strcmp(r->handler, "server-status")) { return DECLINED; } #ifdef HAVE_TIMES #ifdef _SC_CLK_TCK tick = sysconf(_SC_CLK_TCK); #else tick = HZ; #endif #endif ready = 0; busy = 0; count = 0; bcount = 0; kbcount = 0; short_report = 0; no_table_report = 0; pid_buffer = apr_palloc(r->pool, server_limit * sizeof(pid_t)); stat_buffer = apr_palloc(r->pool, server_limit * thread_limit * sizeof(char)); nowtime = apr_time_now(); tu = ts = tcu = tcs = 0; if (!ap_exists_scoreboard_image()) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Server status unavailable in inetd mode"); return HTTP_INTERNAL_SERVER_ERROR; } r->allowed = (AP_METHOD_BIT << M_GET); if (r->method_number != M_GET) return DECLINED; ap_set_content_type(r, "text/html; charset=ISO-8859-1"); /* * Simple table-driven form data set parser that lets you alter the header */ if (r->args) { i = 0; while (status_options[i].id != STAT_OPT_END) { if ((loc = ap_strstr_c(r->args, status_options[i].form_data_str)) != NULL) { switch (status_options[i].id) { case STAT_OPT_REFRESH: { apr_size_t len = strlen(status_options[i].form_data_str); long t = 0; if (*(loc + len ) == '=') { t = atol(loc + len + 1); } apr_table_set(r->headers_out, status_options[i].hdr_out_str, apr_ltoa(r->pool, t < 1 ? 10 : t)); break; } case STAT_OPT_NOTABLE: no_table_report = 1; break; case STAT_OPT_AUTO: ap_set_content_type(r, "text/plain; charset=ISO-8859-1"); short_report = 1; break; } } i++; } } for (i = 0; i < server_limit; ++i) { #ifdef HAVE_TIMES clock_t proc_tu = 0, proc_ts = 0, proc_tcu = 0, proc_tcs = 0; clock_t tmp_tu, tmp_ts, tmp_tcu, tmp_tcs; #endif ps_record = ap_get_scoreboard_process(i); for (j = 0; j < thread_limit; ++j) { int indx = (i * thread_limit) + j; ws_record = ap_get_scoreboard_worker(i, j); res = ws_record->status; stat_buffer[indx] = status_flags[res]; if (!ps_record->quiescing && ps_record->pid) { if (res == SERVER_READY && ps_record->generation == ap_my_generation) ready++; else if (res != SERVER_DEAD && res != SERVER_STARTING && res != SERVER_IDLE_KILL) busy++; } /* XXX what about the counters for quiescing/seg faulted * processes? should they be counted or not? GLA */ if (ap_extended_status) { lres = ws_record->access_count; bytes = ws_record->bytes_served; if (lres != 0 || (res != SERVER_READY && res != SERVER_DEAD)) { #ifdef HAVE_TIMES tmp_tu = ws_record->times.tms_utime; tmp_ts = ws_record->times.tms_stime; tmp_tcu = ws_record->times.tms_cutime; tmp_tcs = ws_record->times.tms_cstime; if (times_per_thread) { proc_tu += tmp_tu; proc_ts += tmp_ts; proc_tcu += tmp_tcu; proc_tcs += proc_tcs; } else { if (tmp_tu > proc_tu || tmp_ts > proc_ts || tmp_tcu > proc_tcu || tmp_tcs > proc_tcs) { proc_tu = tmp_tu; proc_ts = tmp_ts; proc_tcu = tmp_tcu; proc_tcs = proc_tcs; } } #endif /* HAVE_TIMES */ count += lres; bcount += bytes; if (bcount >= KBYTE) { kbcount += (bcount >> 10); bcount = bcount & 0x3ff; } } } } #ifdef HAVE_TIMES tu += proc_tu; ts += proc_ts; tcu += proc_tcu; tcs += proc_tcs; #endif pid_buffer[i] = ps_record->pid; } /* up_time in seconds */ up_time = (apr_uint32_t) apr_time_sec(nowtime - ap_scoreboard_image->global->restart_time); if (!short_report) { ap_rputs(DOCTYPE_HTML_3_2 "\nApache Status\n\n", r); ap_rputs("

Apache Server Status for ", r); ap_rvputs(r, ap_get_server_name(r), "

\n\n", NULL); ap_rvputs(r, "
Server Version: ", ap_get_server_version(), "
\n", NULL); ap_rvputs(r, "
Server Built: ", ap_get_server_built(), "\n

\n", NULL); ap_rvputs(r, "
Current Time: ", ap_ht_time(r->pool, nowtime, DEFAULT_TIME_FORMAT, 0), "
\n", NULL); ap_rvputs(r, "
Restart Time: ", ap_ht_time(r->pool, ap_scoreboard_image->global->restart_time, DEFAULT_TIME_FORMAT, 0), "
\n", NULL); ap_rprintf(r, "
Parent Server Generation: %d
\n", (int)ap_my_generation); ap_rputs("
Server uptime: ", r); show_time(r, up_time); ap_rputs("
\n", r); } if (ap_extended_status) { if (short_report) { ap_rprintf(r, "Total Accesses: %lu\nTotal kBytes: %" APR_OFF_T_FMT "\n", count, kbcount); #ifdef HAVE_TIMES /* Allow for OS/2 not having CPU stats */ if (ts || tu || tcu || tcs) ap_rprintf(r, "CPULoad: %g\n", (tu + ts + tcu + tcs) / tick / up_time * 100.); #endif ap_rprintf(r, "Uptime: %ld\n", (long) (up_time)); if (up_time > 0) ap_rprintf(r, "ReqPerSec: %g\n", (float) count / (float) up_time); if (up_time > 0) ap_rprintf(r, "BytesPerSec: %g\n", KBYTE * (float) kbcount / (float) up_time); if (count > 0) ap_rprintf(r, "BytesPerReq: %g\n", KBYTE * (float) kbcount / (float) count); } else { /* !short_report */ ap_rprintf(r, "
Total accesses: %lu - Total Traffic: ", count); format_kbyte_out(r, kbcount); ap_rputs("
\n", r); #ifdef HAVE_TIMES /* Allow for OS/2 not having CPU stats */ ap_rprintf(r, "
CPU Usage: u%g s%g cu%g cs%g", tu / tick, ts / tick, tcu / tick, tcs / tick); if (ts || tu || tcu || tcs) ap_rprintf(r, " - %.3g%% CPU load
\n", (tu + ts + tcu + tcs) / tick / up_time * 100.); #endif if (up_time > 0) ap_rprintf(r, "
%.3g requests/sec - ", (float) count / (float) up_time); if (up_time > 0) { format_byte_out(r, (unsigned long)(KBYTE * (float) kbcount / (float) up_time)); ap_rputs("/second - ", r); } if (count > 0) { format_byte_out(r, (unsigned long)(KBYTE * (float) kbcount / (float) count)); ap_rputs("/request", r); } ap_rputs("
\n", r); } /* short_report */ } /* ap_extended_status */ if (!short_report) ap_rprintf(r, "
%d requests currently being processed, " "%d idle workers
\n", busy, ready); else ap_rprintf(r, "BusyWorkers: %d\nIdleWorkers: %d\n", busy, ready); /* send the scoreboard 'table' out */ if (!short_report) ap_rputs("
", r);
    else
        ap_rputs("Scoreboard: ", r);

    for (i = 0; i < server_limit; ++i) {
        for (j = 0; j < thread_limit; ++j) {
            int indx = (i * thread_limit) + j;
            ap_rputc(stat_buffer[indx], r);
            if ((indx % STATUS_MAXLINE == (STATUS_MAXLINE - 1))
                && !short_report)
                ap_rputs("\n", r);
        }
    }

    if (short_report)
        ap_rputs("\n", r);
    else {
        ap_rputs("
\n", r); ap_rputs("

Scoreboard Key:
\n", r); ap_rputs("\"_\" Waiting for Connection, \n", r); ap_rputs("\"S\" Starting up, \n", r); ap_rputs("\"R\" Reading Request,
\n", r); ap_rputs("\"W\" Sending Reply, \n", r); ap_rputs("\"K\" Keepalive (read), \n", r); ap_rputs("\"D\" DNS Lookup,
\n", r); ap_rputs("\"C\" Closing connection, \n", r); ap_rputs("\"L\" Logging, \n", r); ap_rputs("\"G\" Gracefully finishing,
\n", r); ap_rputs("\"I\" Idle cleanup of worker, \n", r); ap_rputs("\".\" Open slot with no current process

\n", r); ap_rputs("

\n", r); if (!ap_extended_status) { int j; int k = 0; ap_rputs("PID Key:
\n", r); ap_rputs("

\n", r);
            for (i = 0; i < server_limit; ++i) {
                for (j = 0; j < thread_limit; ++j) {
                    int indx = (i * thread_limit) + j;

                    if (stat_buffer[indx] != '.') {
                        ap_rprintf(r, "   %" APR_PID_T_FMT
                                   " in state: %c ", pid_buffer[i],
                                   stat_buffer[indx]);

                        if (++k >= 3) {
                            ap_rputs("\n", r);
                            k = 0;
                        } else
                            ap_rputs(",", r);
                    }
                }
            }

            ap_rputs("\n", r);
            ap_rputs("
\n", r); } } if (ap_extended_status && !short_report) { if (no_table_report) ap_rputs("

Server Details

\n\n", r); else ap_rputs("\n\n" "" "" #ifdef HAVE_TIMES "" #endif "" "" "" "\n\n", r); for (i = 0; i < server_limit; ++i) { for (j = 0; j < thread_limit; ++j) { ws_record = ap_get_scoreboard_worker(i, j); if (ws_record->access_count == 0 && (ws_record->status == SERVER_READY || ws_record->status == SERVER_DEAD)) { continue; } ps_record = ap_get_scoreboard_process(i); if (ws_record->start_time == 0L) req_time = 0L; else req_time = (long) ((ws_record->stop_time - ws_record->start_time) / 1000); if (req_time < 0L) req_time = 0L; lres = ws_record->access_count; my_lres = ws_record->my_access_count; conn_lres = ws_record->conn_count; bytes = ws_record->bytes_served; my_bytes = ws_record->my_bytes_served; conn_bytes = ws_record->conn_bytes; if (no_table_report) { if (ws_record->status == SERVER_DEAD) ap_rprintf(r, "Server %d-%d (-): %d|%lu|%lu [", i, (int)ps_record->generation, (int)conn_lres, my_lres, lres); else ap_rprintf(r, "Server %d-%d (%" APR_PID_T_FMT "): %d|%lu|%lu [", i, (int) ps_record->generation, ps_record->pid, (int)conn_lres, my_lres, lres); switch (ws_record->status) { case SERVER_READY: ap_rputs("Ready", r); break; case SERVER_STARTING: ap_rputs("Starting", r); break; case SERVER_BUSY_READ: ap_rputs("Read", r); break; case SERVER_BUSY_WRITE: ap_rputs("Write", r); break; case SERVER_BUSY_KEEPALIVE: ap_rputs("Keepalive", r); break; case SERVER_BUSY_LOG: ap_rputs("Logging", r); break; case SERVER_BUSY_DNS: ap_rputs("DNS lookup", r); break; case SERVER_CLOSING: ap_rputs("Closing", r); break; case SERVER_DEAD: ap_rputs("Dead", r); break; case SERVER_GRACEFUL: ap_rputs("Graceful", r); break; case SERVER_IDLE_KILL: ap_rputs("Dying", r); break; default: ap_rputs("?STATE?", r); break; } ap_rprintf(r, "] " #ifdef HAVE_TIMES "u%g s%g cu%g cs%g" #endif "\n %ld %ld (", #ifdef HAVE_TIMES ws_record->times.tms_utime / tick, ws_record->times.tms_stime / tick, ws_record->times.tms_cutime / tick, ws_record->times.tms_cstime / tick, #endif (long)apr_time_sec(nowtime - ws_record->last_used), (long) req_time); format_byte_out(r, conn_bytes); ap_rputs("|", r); format_byte_out(r, my_bytes); ap_rputs("|", r); format_byte_out(r, bytes); ap_rputs(")\n", r); ap_rprintf(r, " %s {%s}[%s]
\n\n", ap_escape_html(r->pool, ws_record->client), ap_escape_html(r->pool, ap_escape_logitem(r->pool, ws_record->request)), ap_escape_html(r->pool, ws_record->vhost)); } else { /* !no_table_report */ if (ws_record->status == SERVER_DEAD) ap_rprintf(r, "" #ifdef HAVE_TIMES "" #endif "\n\n"); else ap_rprintf(r, "\n\n", ap_escape_html(r->pool, ws_record->client), ap_escape_html(r->pool, ws_record->vhost), ap_escape_html(r->pool, ap_escape_logitem(r->pool, ws_record->request))); } /* no_table_report */ } /* for (j...) */ } /* for (i...) */ if (!no_table_report) { ap_rputs("
SrvPIDAccMCPU\nSSReqConnChildSlotClientVHostRequest
%d-%d-%d/%lu/%lu", i, (int)ps_record->generation, (int)conn_lres, my_lres, lres); else ap_rprintf(r, "
%d-%d%" APR_PID_T_FMT "%d/%lu/%lu", i, (int)ps_record->generation, ps_record->pid, (int)conn_lres, my_lres, lres); switch (ws_record->status) { case SERVER_READY: ap_rputs("_", r); break; case SERVER_STARTING: ap_rputs("S", r); break; case SERVER_BUSY_READ: ap_rputs("R", r); break; case SERVER_BUSY_WRITE: ap_rputs("W", r); break; case SERVER_BUSY_KEEPALIVE: ap_rputs("K", r); break; case SERVER_BUSY_LOG: ap_rputs("L", r); break; case SERVER_BUSY_DNS: ap_rputs("D", r); break; case SERVER_CLOSING: ap_rputs("C", r); break; case SERVER_DEAD: ap_rputs(".", r); break; case SERVER_GRACEFUL: ap_rputs("G", r); break; case SERVER_IDLE_KILL: ap_rputs("I", r); break; default: ap_rputs("?", r); break; } ap_rprintf(r, "\n%.2f%ld%ld", #ifdef HAVE_TIMES (ws_record->times.tms_utime + ws_record->times.tms_stime + ws_record->times.tms_cutime + ws_record->times.tms_cstime) / tick, #endif (long)apr_time_sec(nowtime - ws_record->last_used), (long)req_time); ap_rprintf(r, "%-1.1f%-2.2f%-2.2f\n", (float)conn_bytes / KBYTE, (float) my_bytes / MBYTE, (float)bytes / MBYTE); if (ws_record->status == SERVER_BUSY_READ) ap_rprintf(r, "??..reading..
%s%s%s
\n \
\ \n \ \n \ \n \ \n \ \n" #ifdef HAVE_TIMES "\n" #endif "\n \ \n \ \n \ \n \ \n \
SrvChild Server number - generation
PIDOS process ID
AccNumber of accesses this connection / this child / this slot
MMode of operation
CPUCPU usage, number of seconds
SSSeconds since beginning of most recent request
ReqMilliseconds required to process most recent request
ConnKilobytes transferred this connection
ChildMegabytes transferred this child
SlotTotal megabytes transferred this slot
\n", r); } } /* if (ap_extended_status && !short_report) */ else { if (!short_report) { ap_rputs("
To obtain a full report with current status " "information you need to use the " "ExtendedStatus On directive.\n", r); } } { /* Run extension hooks to insert extra content. */ int flags = (short_report ? AP_STATUS_SHORT : 0) | (no_table_report ? AP_STATUS_NOTABLE : 0) | (ap_extended_status ? AP_STATUS_EXTENDED : 0); ap_run_status_hook(r, flags); } if (!short_report) { ap_rputs(ap_psignature("
\n",r), r); ap_rputs("\n", r); } return 0; } static int status_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { status_flags[SERVER_DEAD] = '.'; /* We don't want to assume these are in */ status_flags[SERVER_READY] = '_'; /* any particular order in scoreboard.h */ status_flags[SERVER_STARTING] = 'S'; status_flags[SERVER_BUSY_READ] = 'R'; status_flags[SERVER_BUSY_WRITE] = 'W'; status_flags[SERVER_BUSY_KEEPALIVE] = 'K'; status_flags[SERVER_BUSY_LOG] = 'L'; status_flags[SERVER_BUSY_DNS] = 'D'; status_flags[SERVER_CLOSING] = 'C'; status_flags[SERVER_GRACEFUL] = 'G'; status_flags[SERVER_IDLE_KILL] = 'I'; ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit); ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit); return OK; } #ifdef HAVE_TIMES static void status_child_init(apr_pool_t *p, server_rec *s) { child_pid = getpid(); } #endif static void register_hooks(apr_pool_t *p) { ap_hook_handler(status_handler, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_post_config(status_init, NULL, NULL, APR_HOOK_MIDDLE); #ifdef HAVE_TIMES ap_hook_child_init(status_child_init, NULL, NULL, APR_HOOK_MIDDLE); #endif } module AP_MODULE_DECLARE_DATA status_module = { STANDARD20_MODULE_STUFF, NULL, /* dir config creater */ NULL, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ status_module_cmds, /* command table */ register_hooks /* register_hooks */ };