/* * mod_status - display server status * * Description: * mod_status can display a page with statistics like requests, traffic and active connections. * It can be customized with different stylesheets (css) * * Setups: * none * Options: * status.css - set the stylesheet to use, optional * type: string; values: "default", "blue" or a url to an external css file * Actions: * status.info - returns the status info page to the client * status.info "short" - returns only "non-sensitive" data; no connection details, no runtime section * * The status page accepts parameters in the query-string: * - mode=runtimes : show runtime information * - format=plain : returns "short" information in plain text format, easy to parse * - auto : returns "legacy" plain text format, for 1.5 migration or apache_ munin plugins * * Example config: * req.path == "/srv-status" { * status.css = "http://mydomain/status.css"; * status.info; * } * * Todo: * - * * Author: * Copyright (c) 2008-2010 Thomas Porzelt * License: * MIT, see COPYING file in the lighttpd 2 tree */ #include #include #include #include #include #ifdef HAVE_SYS_RESOURCE_H # include #endif LI_API gboolean mod_status_init(liModules *mods, liModule *mod); LI_API gboolean mod_status_free(liModules *mods, liModule *mod); static GString *status_info_full(liVRequest *vr, liPlugin *p, gboolean short_info, GPtrArray *result, guint uptime, liStatistics *totals, guint total_connections, guint *connection_count); static GString *status_info_plain(liVRequest *vr, guint uptime, liStatistics *totals, guint total_connections, guint *connection_count); static GString *status_info_auto(liVRequest *vr, guint uptime, liStatistics *totals, guint *connection_count); static liHandlerResult status_info_runtime(liVRequest *vr, liPlugin *p); static gint str_comp(gconstpointer a, gconstpointer b); /* auto format constants */ static gchar liConnectionState_short[LI_CON_STATE_LAST+2] = "_cKqrhwu"; /* html snippet constants */ static const gchar html_header[] = "\n" "\n" "\n" " \n" " Lighttpd Status\n"; static const gchar html_js[] = " \n"; static const gchar html_top[] = "
Lighttpd Server Status | \n" " main - runtime" "
\n" "
\n" " Memory Usage: %s" " Uptime: %s\n" " Started at: %s\n" " Version: " PACKAGE_VERSION " (" __DATE__ " " __TIME__ ")\n" "
\n"; static const gchar html_top_short[] = "
Lighttpd Server Status
\n" "
\n" " Memory Usage: %s" " Uptime: %s\n" " Started at: %s\n" "
\n"; static const gchar html_worker_th[] = " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n"; static const gchar html_worker_th_avg[] = "
RequestsTraffic inTraffic outActive connections
\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n"; static const gchar html_worker_row[] = " \n" " \n" " \n" " \n" " \n" " \n" " \n"; static const gchar html_worker_row_avg[] = " \n" " \n" " \n" " \n" " \n" " \n" " \n"; static const gchar html_connections_sum[] = "
Requests / sTraffic in / sTraffic out / sActive connections
%s%s (%" G_GUINT64_FORMAT "%%)%s (%" G_GUINT64_FORMAT "%%)%s (%" G_GUINT64_FORMAT "%%)%u (%u%%)
%s%s%s%s%u
\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "
inactiverequest startread headerhandle requestwrite responsekeep-aliveupgraded
%u%u%u%u%u%u%u
\n"; static const gchar html_status_codes[] = " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "
1xx (info)2xx (success)3xx (redirect)4xx (client error)5xx (server error)
%" G_GUINT64_FORMAT "%" G_GUINT64_FORMAT "%" G_GUINT64_FORMAT "%" G_GUINT64_FORMAT "%" G_GUINT64_FORMAT "
\n"; static const gchar html_connections_th[] = " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n"; static const gchar html_connections_row[] = " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n"; static const gchar html_server_info[] = "
ClientStateHostPath+QuerystringDurationTimeoutTraffic in/outTraffic in/out / sMethodRequest SizeResponse Size
%s%s%s%s%s%s%s%s%s / %s%s / %s%s%s%s
\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "
Hostname%s
User%s (%u)
Event handler%s
File descriptor limit%s
Connection limit%u
Core size limit%s
IO Timeout%"G_GUINT64_FORMAT" seconds
\n"; static const gchar html_libinfo_th[] = " \n" " \n" " \n" " \n" " \n" " \n"; static const gchar html_libinfo_row[] = " \n" " \n" " \n" " \n" " \n"; static const gchar html_libev_th[] = "
linkedheaders
%s%s%s
\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n"; static const gchar html_libev_row[] = " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n"; static const gchar css_default[] = " \n"; /* blue theme by nitrox */ static const gchar css_blue[] = " \n"; static guint64 mod_status_response_codes[] = { 0, /*1xx*/ 0, /*2xx*/ 0, /*3xx*/ 0, /*4xx*/ 0, /*5xx*/ }; typedef struct mod_status_param mod_status_param; struct mod_status_param { liPlugin *p; gboolean short_info; /* no connection details */ }; typedef struct mod_status_wrk_data mod_status_wrk_data; typedef struct mod_status_con_data mod_status_con_data; typedef struct mod_status_job mod_status_job; struct mod_status_con_data { guint worker_ndx; liConnectionState state; GString *remote_addr_str, *local_addr_str; gboolean is_ssl, keep_alive; GString *host, *path, *query; liHttpMethod method; goffset request_size; goffset response_size; guint64 ts_started; gint64 ts_timeout; guint64 bytes_in; guint64 bytes_out; guint64 bytes_in_5s_diff; guint64 bytes_out_5s_diff; }; struct mod_status_wrk_data { guint worker_ndx; liStatistics stats; GArray *connections; guint connection_count[LI_CON_STATE_LAST+1]; }; struct mod_status_job { liVRequest *vr; gpointer *context; liPlugin *p; gboolean short_info; }; /* the CollectFunc */ static gpointer status_collect_func(liWorker *wrk, gpointer fdata) { mod_status_wrk_data *sd = g_slice_new0(mod_status_wrk_data); li_tstamp now = li_cur_ts(wrk); UNUSED(fdata); sd->stats = wrk->stats; sd->worker_ndx = wrk->ndx; /* gather connection info */ sd->connections = g_array_sized_new(FALSE, TRUE, sizeof(mod_status_con_data), wrk->connections_active); g_array_set_size(sd->connections, wrk->connections_active); for (guint i = 0; i < wrk->connections_active; i++) { liConnection *c = g_array_index(wrk->connections, liConnection*, i); mod_status_con_data *cd = &g_array_index(sd->connections, mod_status_con_data, i); cd->is_ssl = c->info.is_ssl; cd->keep_alive = c->info.keep_alive; cd->remote_addr_str = g_string_new_len(GSTR_LEN(c->info.remote_addr_str)); cd->local_addr_str = g_string_new_len(GSTR_LEN(c->info.local_addr_str)); cd->host = g_string_new_len(GSTR_LEN(c->mainvr->request.uri.host)); cd->path = g_string_new_len(GSTR_LEN(c->mainvr->request.uri.path)); cd->query = g_string_new_len(GSTR_LEN(c->mainvr->request.uri.query)); cd->method = c->mainvr->request.http_method; cd->request_size = c->mainvr->request.content_length; cd->response_size = (NULL != c->mainvr->backend_source) ? c->mainvr->backend_source->out->bytes_out : 0; cd->state = c->state; cd->bytes_in = c->info.stats.bytes_in; cd->bytes_out = c->info.stats.bytes_out; cd->bytes_in_5s_diff = c->info.stats.bytes_in_5s_diff; cd->bytes_out_5s_diff = c->info.stats.bytes_out_5s_diff; cd->ts_started = (guint64)(now - c->ts_started); if (c->io_timeout_elem.queued) { cd->ts_timeout = MAX(0, wrk->srv->io_timeout - (now - c->io_timeout_elem.ts)); } else if (LI_CON_STATE_KEEP_ALIVE == c->state) { cd->ts_timeout = MAX(0, c->keep_alive_data.timeout - now); } else { cd->ts_timeout = -1; } sd->connection_count[c->state]++; } return sd; } /* the CollectCallback */ static void status_collect_cb(gpointer cbdata, gpointer fdata, GPtrArray *result, gboolean complete) { guint i, j; mod_status_job *job = fdata; liVRequest *vr = job->vr; liPlugin *p = job->p; gboolean short_info = job->short_info; UNUSED(cbdata); if (!complete) { /* someone called li_collect_break, so we don't need any vrequest handling here. just free the result data */ for (i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); for (j = 0; j < sd->connections->len; j++) { mod_status_con_data *cd = &g_array_index(sd->connections, mod_status_con_data, j); g_string_free(cd->remote_addr_str, TRUE); g_string_free(cd->local_addr_str, TRUE); g_string_free(cd->host, TRUE); g_string_free(cd->path, TRUE); g_string_free(cd->query, TRUE); } g_array_free(sd->connections, TRUE); g_slice_free(mod_status_wrk_data, sd); } g_slice_free(mod_status_job, job); return; } else { GString *html; gchar *val; guint uptime, len; guint total_connections = 0; guint connection_count[LI_CON_STATE_LAST+1] = {0}; liStatistics totals = { G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), 0, 0, {G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0)}, G_GUINT64_CONSTANT(0), 0, 0 }; /* clear context so it doesn't get cleaned up anymore */ *(job->context) = NULL; g_slice_free(mod_status_job, job); uptime = li_cur_ts(vr->wrk) - vr->wrk->srv->started; if (!uptime) uptime = 1; if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "finished collecting data: %s", complete ? "complete" : "not complete"); } /* calculate total stats over all workers */ for (i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); totals.bytes_out += sd->stats.bytes_out; totals.bytes_in += sd->stats.bytes_in; totals.requests += sd->stats.requests; totals.actions_executed += sd->stats.actions_executed; total_connections += sd->connections->len; totals.requests_5s_diff += sd->stats.requests_5s_diff; totals.bytes_in_5s_diff += sd->stats.bytes_in_5s_diff; totals.bytes_out_5s_diff += sd->stats.bytes_out_5s_diff; totals.active_cons_cum += sd->stats.active_cons_cum; totals.active_cons_5s += sd->stats.active_cons_5s; totals.peak.bytes_out += sd->stats.peak.bytes_out; totals.peak.bytes_in += sd->stats.peak.bytes_in; totals.peak.requests += sd->stats.peak.requests; totals.peak.active_cons += sd->stats.peak.active_cons; for (j = 0; j <= LI_CON_STATE_LAST; ++j) { connection_count[j] += sd->connection_count[j]; } } if (li_querystring_find(vr->request.uri.query, CONST_STR_LEN("format"), &val, &len) && strncmp(val, "plain", len) == 0) { /* show plain text page */ html = status_info_plain(vr, uptime, &totals, total_connections, &connection_count[0]); } else if (li_strncase_equal(vr->request.uri.query, CONST_STR_LEN("auto"))) { /* show auto text page */ html = status_info_auto(vr, uptime, &totals, &connection_count[0]); } else { /* show full html page */ html = status_info_full(vr, p, short_info, result, uptime, &totals, total_connections, &connection_count[0]); } li_vrequest_handle_direct(vr); vr->response.http_status = 200; li_chunkqueue_append_string(vr->direct_out, html); li_vrequest_joblist_append(vr); /* free stats */ for (i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); for (j = 0; j < sd->connections->len; j++) { mod_status_con_data *cd = &g_array_index(sd->connections, mod_status_con_data, j); g_string_free(cd->remote_addr_str, TRUE); g_string_free(cd->local_addr_str, TRUE); g_string_free(cd->host, TRUE); g_string_free(cd->path, TRUE); g_string_free(cd->query, TRUE); } g_array_free(sd->connections, TRUE); g_slice_free(mod_status_wrk_data, sd); } } } static GString *status_info_full(liVRequest *vr, liPlugin *p, gboolean short_info, GPtrArray *result, guint uptime, liStatistics *totals, guint total_connections, guint *connection_count) { GString *html, *css, *count_req, *count_bin, *count_bout, *count_mem, *tmpstr; gchar *val; guint i, j, len; gchar c; gsize mem; html = g_string_sized_new(8 * 1024 - 1); count_req = g_string_sized_new(10); count_bin = g_string_sized_new(10); count_bout = g_string_sized_new(10); count_mem = g_string_sized_new(10); tmpstr = vr->wrk->tmp_str; g_string_append_len(html, CONST_STR_LEN(html_header)); g_string_append_len(html, CONST_STR_LEN(html_js)); /* auto refresh */ if (li_querystring_find(vr->request.uri.query, CONST_STR_LEN("refresh"), &val, &len)) { g_string_append_len(html, CONST_STR_LEN("\n")); } /* css */ css = _OPTIONPTR(vr, p, 0).string; if (!css || !css->len) /* default css */ g_string_append_len(html, CONST_STR_LEN(css_default)); else if (g_str_equal(css->str, "blue")) /* blue css */ g_string_append_len(html, CONST_STR_LEN(css_blue)); else /* external css */ g_string_append_printf(html, " \n", css->str); g_string_append_len(html, CONST_STR_LEN( " \n" " \n" )); mem = li_memory_usage(); if (mem) li_counter_format(mem, COUNTER_BYTES, count_mem); li_counter_format((guint64)(li_cur_ts(vr->wrk) - vr->wrk->srv->started), COUNTER_TIME, tmpstr); g_string_append_printf(html, short_info ? html_top_short : html_top, mem ? count_mem->str : "N/A", tmpstr->str, vr->wrk->srv->started_str->str ); /* worker information, absolute values */ g_string_append_len(html, CONST_STR_LEN("
Absolute stats
\n")); g_string_append_len(html, CONST_STR_LEN(html_worker_th)); #define PERCENTAGE(x, y) (y ? (x * 100 / y) : 0) for (i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); li_counter_format(sd->stats.requests, COUNTER_UNITS, count_req); li_counter_format(sd->stats.bytes_in, COUNTER_BYTES, count_bin); li_counter_format(sd->stats.bytes_out, COUNTER_BYTES, count_bout); g_string_printf(tmpstr, "Worker #%u", i+1); g_string_append_printf(html, html_worker_row, "", tmpstr->str, count_req->str, PERCENTAGE(sd->stats.requests, totals->requests), count_bin->str, PERCENTAGE(sd->stats.bytes_in, totals->bytes_in), count_bout->str, PERCENTAGE(sd->stats.bytes_out, totals->bytes_out), sd->connections->len, PERCENTAGE(sd->connections->len, total_connections)); } #undef PERCENTAGE li_counter_format(totals->requests, COUNTER_UNITS, count_req); li_counter_format(totals->bytes_in, COUNTER_BYTES, count_bin); li_counter_format(totals->bytes_out, COUNTER_BYTES, count_bout); g_string_append_printf(html, html_worker_row, "totals", "Total", count_req->str, G_GUINT64_CONSTANT(100), count_bin->str, G_GUINT64_CONSTANT(100), count_bout->str, G_GUINT64_CONSTANT(100), total_connections, 100); g_string_append_len(html, CONST_STR_LEN("
selectpollepollkqueue/dev/pollsolaris event port
%s%s%s%s%s%s%s
\n")); /* worker information, avg values */ g_string_append_len(html, CONST_STR_LEN("
Average stats (since start)
\n")); g_string_append_len(html, CONST_STR_LEN(html_worker_th_avg)); #define PERCENTAGE(x) (sd->stat ## x ? (sd->stat ## x * 100 / total ## x) : 0) for (i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); li_counter_format(sd->stats.requests / uptime, COUNTER_UNITS, count_req); li_counter_format(sd->stats.bytes_in / uptime, COUNTER_BYTES, count_bin); li_counter_format(sd->stats.bytes_out / uptime, COUNTER_BYTES, count_bout); g_string_printf(tmpstr, "Worker #%u", i+1); g_string_append_printf(html, html_worker_row_avg, "", tmpstr->str, count_req->str, count_bin->str, count_bout->str, (guint)(sd->stats.active_cons_cum / uptime) ); } #undef PERCENTAGE li_counter_format(totals->requests / uptime, COUNTER_UNITS, count_req); li_counter_format(totals->bytes_in / uptime, COUNTER_BYTES, count_bin); li_counter_format(totals->bytes_out / uptime, COUNTER_BYTES, count_bout); g_string_append_printf(html, html_worker_row_avg, "totals", "Total", count_req->str, count_bin->str, count_bout->str, (guint)(totals->active_cons_cum / uptime) ); g_string_append_len(html, CONST_STR_LEN(" \n")); /* worker information, 5 seconds avg values */ g_string_append_len(html, CONST_STR_LEN("
Average stats (5 seconds)
\n")); g_string_append_len(html, CONST_STR_LEN(html_worker_th_avg)); #define PERCENTAGE(x) (sd->stat ## x ? (sd->stat ## x * 100 / total ## x) : 0) for (i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); li_counter_format(sd->stats.requests_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_UNITS, count_req); li_counter_format(sd->stats.bytes_in_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_BYTES, count_bin); li_counter_format(sd->stats.bytes_out_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_BYTES, count_bout); g_string_printf(tmpstr, "Worker #%u", i+1); g_string_append_printf(html, html_worker_row_avg, "", tmpstr->str, count_req->str, count_bin->str, count_bout->str, sd->stats.active_cons_5s ); } #undef PERCENTAGE li_counter_format(totals->requests_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_UNITS, count_req); li_counter_format(totals->bytes_in_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_BYTES, count_bin); li_counter_format(totals->bytes_out_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_BYTES, count_bout); g_string_append_printf(html, html_worker_row_avg, "totals", "Total", count_req->str, count_bin->str, count_bout->str, totals->active_cons_5s ); g_string_append_len(html, CONST_STR_LEN(" \n")); /* worker information, peak values */ g_string_append_len(html, CONST_STR_LEN("
Peak stats
\n")); g_string_append_len(html, CONST_STR_LEN(html_worker_th_avg)); for (i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); li_counter_format(sd->stats.peak.requests, COUNTER_UNITS, count_req); li_counter_format(sd->stats.peak.bytes_in, COUNTER_BYTES, count_bin); li_counter_format(sd->stats.peak.bytes_out, COUNTER_BYTES, count_bout); g_string_printf(tmpstr, "Worker #%u", i+1); g_string_append_printf(html, html_worker_row_avg, "", tmpstr->str, count_req->str, count_bin->str, count_bout->str, sd->stats.peak.active_cons ); } li_counter_format(totals->peak.requests, COUNTER_UNITS, count_req); li_counter_format(totals->peak.bytes_in, COUNTER_BYTES, count_bin); li_counter_format(totals->peak.bytes_out, COUNTER_BYTES, count_bout); g_string_append_printf(html, html_worker_row_avg, "totals", "Total", count_req->str, count_bin->str, count_bout->str, totals->peak.active_cons ); g_string_append_len(html, CONST_STR_LEN(" \n")); /* connection counts */ g_string_append_len(html, CONST_STR_LEN("
Connections (states, sum)
\n")); g_string_append_printf(html, html_connections_sum, connection_count[LI_CON_STATE_DEAD] + connection_count[LI_CON_STATE_CLOSE], connection_count[LI_CON_STATE_REQUEST_START], connection_count[LI_CON_STATE_READ_REQUEST_HEADER], connection_count[LI_CON_STATE_HANDLE_MAINVR], connection_count[LI_CON_STATE_WRITE], connection_count[LI_CON_STATE_KEEP_ALIVE], connection_count[LI_CON_STATE_UPGRADED] ); /* response status codes */ g_string_append_len(html, CONST_STR_LEN("
HTTP Status codes (sum)
\n")); g_string_append_printf(html, html_status_codes, mod_status_response_codes[0], mod_status_response_codes[1], mod_status_response_codes[2], mod_status_response_codes[3], mod_status_response_codes[4] ); /* list connections */ if (!short_info) { GString *ts_started, *ts_timeout, *bytes_in, *bytes_out, *bytes_in_5s, *bytes_out_5s; GString *req_len, *resp_len; ts_started = g_string_sized_new(15); ts_timeout = g_string_sized_new(15); bytes_in = g_string_sized_new(10); bytes_out = g_string_sized_new(10); bytes_in_5s = g_string_sized_new(10); bytes_out_5s = g_string_sized_new(10); req_len = g_string_sized_new(10); resp_len = g_string_sized_new(10); g_string_append_len(html, CONST_STR_LEN("
Active connections
\n")); g_string_append_len(html, CONST_STR_LEN(html_connections_th)); for (i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); for (j = 0; j < sd->connections->len; j++) { mod_status_con_data *cd = &g_array_index(sd->connections, mod_status_con_data, j); li_counter_format(cd->ts_started, COUNTER_TIME, ts_started); li_counter_format(cd->ts_timeout, COUNTER_TIME, ts_timeout); li_counter_format(cd->bytes_in, COUNTER_BYTES, bytes_in); li_counter_format(cd->bytes_in_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_BYTES, bytes_in_5s); li_counter_format(cd->bytes_out, COUNTER_BYTES, bytes_out); li_counter_format(cd->bytes_out_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_BYTES, bytes_out_5s); li_counter_format(cd->request_size, COUNTER_BYTES, req_len); li_counter_format(cd->response_size, COUNTER_BYTES, resp_len); g_string_append_printf(html, html_connections_row, cd->remote_addr_str->str, li_connection_state_str(cd->state), cd->host->str, cd->path->str, cd->query->len ? "?":"", cd->query->len ? cd->query->str : "", cd->ts_started, ts_started->str, cd->ts_timeout, cd->ts_timeout < 0 ? "" : ts_timeout->str, cd->bytes_in, bytes_in->str, cd->bytes_out, bytes_out->str, cd->bytes_in_5s_diff / G_GUINT64_CONSTANT(5), bytes_in_5s->str, cd->bytes_out_5s_diff / G_GUINT64_CONSTANT(5), bytes_out_5s->str, (cd->state >= LI_CON_STATE_HANDLE_MAINVR) ? li_http_method_string(cd->method, &len) : "", cd->request_size != -1 ? cd->request_size : 0, (cd->state >= LI_CON_STATE_HANDLE_MAINVR && cd->request_size != -1) ? req_len->str : "", cd->response_size, (cd->state >= LI_CON_STATE_HANDLE_MAINVR) ? resp_len->str : "" ); } } g_string_append_len(html, CONST_STR_LEN(" \n")); g_string_free(ts_started, TRUE); g_string_free(ts_timeout, TRUE); g_string_free(bytes_in, TRUE); g_string_free(bytes_in_5s, TRUE); g_string_free(bytes_out, TRUE); g_string_free(bytes_out_5s, TRUE); g_string_free(req_len, TRUE); g_string_free(resp_len, TRUE); } g_string_append_len(html, CONST_STR_LEN( " \n" "\n" )); li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html; charset=utf-8")); g_string_free(count_req, TRUE); g_string_free(count_bin, TRUE); g_string_free(count_bout, TRUE); g_string_free(count_mem, TRUE); return html; } static GString *status_info_plain(liVRequest *vr, guint uptime, liStatistics *totals, guint total_connections, guint *connection_count) { GString *html; html = g_string_sized_new(1024 - 1); /* absolute values */ g_string_append_len(html, CONST_STR_LEN("# Absolute Values\nuptime: ")); li_string_append_int(html, (gint64)uptime); g_string_append_len(html, CONST_STR_LEN("\nmemory_usage: ")); li_string_append_int(html, li_memory_usage()); g_string_append_len(html, CONST_STR_LEN("\nrequests_abs: ")); li_string_append_int(html, totals->requests); g_string_append_len(html, CONST_STR_LEN("\ntraffic_out_abs: ")); li_string_append_int(html, totals->bytes_out); g_string_append_len(html, CONST_STR_LEN("\ntraffic_in_abs: ")); li_string_append_int(html, totals->bytes_in); g_string_append_len(html, CONST_STR_LEN("\nconnections_abs: ")); li_string_append_int(html, total_connections); /* average since start */ g_string_append_len(html, CONST_STR_LEN("\n\n# Average Values (since start)\nrequests_avg: ")); li_string_append_int(html, totals->requests / uptime); g_string_append_len(html, CONST_STR_LEN("\ntraffic_out_avg: ")); li_string_append_int(html, totals->bytes_out / uptime); g_string_append_len(html, CONST_STR_LEN("\ntraffic_in_avg: ")); li_string_append_int(html, totals->bytes_in / uptime); g_string_append_len(html, CONST_STR_LEN("\nconnections_avg: ")); li_string_append_int(html, totals->active_cons_cum / uptime); /* average last 5 seconds */ g_string_append_len(html, CONST_STR_LEN("\n\n# Average Values (5 seconds)\nrequests_avg_5sec: ")); li_string_append_int(html, totals->requests_5s_diff / 5); g_string_append_len(html, CONST_STR_LEN("\ntraffic_out_avg_5sec: ")); li_string_append_int(html, totals->bytes_out_5s_diff / 5); g_string_append_len(html, CONST_STR_LEN("\ntraffic_in_avg_5sec: ")); li_string_append_int(html, totals->bytes_in_5s_diff / 5); g_string_append_len(html, CONST_STR_LEN("\nconnections_avg_5sec: ")); li_string_append_int(html, totals->active_cons_5s / 5); /* connection states */ g_string_append_len(html, CONST_STR_LEN("\n\n# Connection States\nconnection_state_start: ")); li_string_append_int(html, connection_count[LI_CON_STATE_REQUEST_START]); g_string_append_len(html, CONST_STR_LEN("\nconnection_state_read_header: ")); li_string_append_int(html, connection_count[LI_CON_STATE_READ_REQUEST_HEADER]); g_string_append_len(html, CONST_STR_LEN("\nconnection_state_handle_request: ")); li_string_append_int(html, connection_count[LI_CON_STATE_HANDLE_MAINVR]); g_string_append_len(html, CONST_STR_LEN("\nconnection_state_write_response: ")); li_string_append_int(html, connection_count[LI_CON_STATE_WRITE]); g_string_append_len(html, CONST_STR_LEN("\nconnection_state_keep_alive: ")); li_string_append_int(html, connection_count[LI_CON_STATE_KEEP_ALIVE]); g_string_append_len(html, CONST_STR_LEN("\nconnection_state_upgraded: ")); li_string_append_int(html, connection_count[LI_CON_STATE_UPGRADED]); /* status cpdes */ g_string_append_len(html, CONST_STR_LEN("\n\n# Status Codes (since start)\nstatus_1xx: ")); li_string_append_int(html, mod_status_response_codes[0]); g_string_append_len(html, CONST_STR_LEN("\nstatus_2xx: ")); li_string_append_int(html, mod_status_response_codes[1]); g_string_append_len(html, CONST_STR_LEN("\nstatus_3xx: ")); li_string_append_int(html, mod_status_response_codes[2]); g_string_append_len(html, CONST_STR_LEN("\nstatus_4xx: ")); li_string_append_int(html, mod_status_response_codes[3]); g_string_append_len(html, CONST_STR_LEN("\nstatus_5xx: ")); li_string_append_int(html, mod_status_response_codes[4]); li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain")); return html; } static GString *status_info_auto(liVRequest *vr, guint uptime, liStatistics *totals, guint *connection_count) { GString *html; guint i, j; html = g_string_sized_new(1024 - 1); /* absolute values */ g_string_append_len(html, CONST_STR_LEN("Total Accesses: ")); li_string_append_int(html, totals->requests); g_string_append_len(html, CONST_STR_LEN("\nTotal kBytes: ")); li_string_append_int(html, totals->bytes_out / 1024); g_string_append_len(html, CONST_STR_LEN("\nUptime: ")); li_string_append_int(html, (gint64)uptime); /* connection states */ g_string_append_len(html, CONST_STR_LEN("\nBusyServers: ")); li_string_append_int(html, connection_count[3]+connection_count[4]+connection_count[5]+connection_count[6]+connection_count[7]); g_string_append_len(html, CONST_STR_LEN("\nIdleServers: ")); li_string_append_int(html, connection_count[0]+connection_count[1]+connection_count[2]); /* average since start */ g_string_append_len(html, CONST_STR_LEN("\nTraffic: ")); li_string_append_int(html, totals->bytes_out / uptime); /* average last 5 seconds */ g_string_append_len(html, CONST_STR_LEN("\nTraffic5s: ")); li_string_append_int(html, totals->bytes_out_5s_diff / 5); /* output scoreboard */ g_string_append_len(html, CONST_STR_LEN("\nScoreboard: ")); for (i = 0; i <= LI_CON_STATE_LAST; i++) { for (j = 0; j < connection_count[i]; j++) { g_string_append_c(html, liConnectionState_short[i]); } } g_string_append_len(html, CONST_STR_LEN("\n")); li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain")); return html; } static liHandlerResult status_info(liVRequest *vr, gpointer _param, gpointer *context) { gchar *val; guint len; gboolean have_mode; mod_status_param *param = _param; if (*context) return LI_HANDLER_WAIT_FOR_EVENT; switch (vr->request.http_method) { case LI_HTTP_METHOD_GET: case LI_HTTP_METHOD_HEAD: break; default: return LI_HANDLER_GO_ON; } if (li_vrequest_is_handled(vr)) return LI_HANDLER_GO_ON; have_mode = li_querystring_find(vr->request.uri.query, CONST_STR_LEN("mode"), &val, &len); if (!have_mode) { /* no 'mode' query parameter given */ liCollectInfo *ci; mod_status_job *j = g_slice_new(mod_status_job); j->vr = vr; j->context = context; j->p = param->p; j->short_info = param->short_info; if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "collecting stats..."); } ci = li_collect_start(vr->wrk, status_collect_func, j, status_collect_cb, NULL); *context = ci; /* may be NULL */ return ci ? LI_HANDLER_WAIT_FOR_EVENT : LI_HANDLER_GO_ON; } else { /* 'mode' parameter given */ if (!param->short_info && strncmp(val, "runtime", len) == 0) { return status_info_runtime(vr, param->p); } else { vr->response.http_status = 403; li_vrequest_handle_direct(vr); } } return LI_HANDLER_GO_ON; } static liHandlerResult status_info_cleanup(liVRequest *vr, gpointer param, gpointer context) { liCollectInfo *ci = (liCollectInfo*) context; UNUSED(vr); UNUSED(param); li_collect_break(ci); return LI_HANDLER_GO_ON; } static void status_info_free(liServer *srv, gpointer param) { UNUSED(srv); g_slice_free(mod_status_param, param); } static liAction* status_info_create(liServer *srv, liWorker *wrk, liPlugin* p, liValue *val, gpointer userdata) { mod_status_param *param = g_slice_new0(mod_status_param); UNUSED(wrk); UNUSED(userdata); param->p = p; param->short_info = FALSE; if (val) { if (val->type != LI_VALUE_STRING) { ERROR(srv, "%s", "status.info expects either a string or nothing as parameter"); goto error_free_param; } if (0 == strcmp(val->data.string->str, "short")) { param->short_info = TRUE; } else { ERROR(srv, "status.info: unexpected parameter '%s'", val->data.string->str); goto error_free_param; } } return li_action_new_function(status_info, status_info_cleanup, status_info_free, param); error_free_param: g_slice_free(mod_status_param, param); return NULL; } static gint str_comp(gconstpointer a, gconstpointer b) { return strcmp(*(const gchar**)a, *(const gchar**)b); } static liHandlerResult status_info_runtime(liVRequest *vr, liPlugin *p) { GString *html, *tmp_str; gsize mem; html = g_string_sized_new(8*1024-1); tmp_str = g_string_sized_new(10); g_string_append_len(html, CONST_STR_LEN(html_header)); /* auto refresh */ { gchar *val; guint len; gchar c; if (li_querystring_find(vr->request.uri.query, CONST_STR_LEN("refresh"), &val, &len)) { g_string_append_len(html, CONST_STR_LEN("\n")); } } /* css */ { GString* css = _OPTIONPTR(vr, p, 0).string; if (!css || !css->len) /* default css */ g_string_append_len(html, CONST_STR_LEN(css_default)); else if (g_str_equal(css->str, "blue")) /* blue css */ g_string_append_len(html, CONST_STR_LEN(css_blue)); else /* external css */ g_string_append_printf(html, " \n", css->str); } g_string_append_len(html, CONST_STR_LEN( " \n" " \n" )); mem = li_memory_usage(); if (mem) li_counter_format(mem, COUNTER_BYTES, tmp_str); li_counter_format((guint64)(li_cur_ts(vr->wrk) - vr->wrk->srv->started), COUNTER_TIME, vr->wrk->tmp_str); g_string_append_printf(html, html_top, mem ? tmp_str->str : "N/A", vr->wrk->tmp_str->str, vr->wrk->srv->started_str->str ); /* general info */ { const gchar *slim_fd = NULL, *slim_core = NULL; #ifdef HAVE_GETRLIMIT GString *tmps = g_string_sized_new(15); { struct rlimit rlim; getrlimit(RLIMIT_NOFILE, &rlim); if (RLIM_INFINITY == rlim.rlim_cur || (guint64) rlim.rlim_cur > G_MAXUINT64) { slim_fd = "unlimited"; } else { g_string_printf(tmps, "%"G_GUINT64_FORMAT, (guint64) rlim.rlim_cur); slim_fd = tmps->str; } getrlimit(RLIMIT_CORE, &rlim); if (RLIM_INFINITY == rlim.rlim_cur || (guint64) rlim.rlim_cur > G_MAXUINT64) { slim_core = "unlimited"; } else if (0 == rlim.rlim_cur) { slim_core = "disabled"; } else { li_counter_format(rlim.rlim_cur, COUNTER_BYTES, vr->wrk->tmp_str); slim_core = vr->wrk->tmp_str->str; } } #else slim_fd = slim_core = "unknown"; #endif g_string_append_len(html, CONST_STR_LEN("
Server info
\n")); g_string_append_printf(html, html_server_info, g_get_host_name(), g_get_user_name(), getuid(), li_event_loop_backend_string(&vr->wrk->loop), slim_fd, g_atomic_int_get(&vr->wrk->srv->max_connections), slim_core, (guint64) vr->wrk->srv->io_timeout ); #ifdef HAVE_GETRLIMIT g_string_free(tmps, TRUE); #endif } /* library info */ { g_string_append_len(html, CONST_STR_LEN("
Libraries
\n")); g_string_append_len(html, CONST_STR_LEN(html_libinfo_th)); g_string_truncate(tmp_str, 0); g_string_append_printf(tmp_str, "%u.%u.%u", glib_major_version, glib_minor_version, glib_micro_version); g_string_truncate(vr->wrk->tmp_str, 0); g_string_append_printf(vr->wrk->tmp_str, "%u.%u.%u", GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION); g_string_append_printf(html, html_libinfo_row, "GLib", tmp_str->str, vr->wrk->tmp_str->str ); g_string_truncate(tmp_str, 0); g_string_append_printf(tmp_str, "%u.%u", ev_version_major(), ev_version_minor()); g_string_truncate(vr->wrk->tmp_str, 0); g_string_append_printf(vr->wrk->tmp_str, "%u.%u", EV_VERSION_MAJOR, EV_VERSION_MINOR); g_string_append_printf(html, html_libinfo_row, "libev", tmp_str->str, vr->wrk->tmp_str->str ); g_string_append_len(html, CONST_STR_LEN(" \n")); } /* libev info */ { guint i; g_string_append_len(html, CONST_STR_LEN("
libev backends
\n")); g_string_append_len(html, CONST_STR_LEN(html_libev_th)); /* supported backend, aka compiled in */ i = ev_supported_backends(); g_string_append_printf(html, html_libev_row, "supported", i & EVBACKEND_SELECT ? "yes" : "no", i & EVBACKEND_POLL ? "yes" : "no", i & EVBACKEND_EPOLL ? "yes" : "no", i & EVBACKEND_KQUEUE ? "yes" : "no", i & EVBACKEND_DEVPOLL ? "yes" : "no", i & EVBACKEND_PORT ? "yes" : "no" ); /* recommended backends */ i = ev_recommended_backends(); g_string_append_printf(html, html_libev_row, "recommended", i & EVBACKEND_SELECT ? "yes" : "no", i & EVBACKEND_POLL ? "yes" : "no", i & EVBACKEND_EPOLL ? "yes" : "no", i & EVBACKEND_KQUEUE ? "yes" : "no", i & EVBACKEND_DEVPOLL ? "yes" : "no", i & EVBACKEND_PORT ? "yes" : "no" ); g_string_append_len(html, CONST_STR_LEN(" \n")); } /* list modules */ { guint i, col; gpointer k, v; GHashTableIter iter; GArray *list = g_array_sized_new(FALSE, FALSE, sizeof(gchar*), g_hash_table_size(vr->wrk->srv->plugins)); g_string_append_len(html, CONST_STR_LEN("
Loaded modules
\n")); g_string_append_len(html, CONST_STR_LEN(" \n")); g_hash_table_iter_init(&iter, vr->wrk->srv->plugins); while (g_hash_table_iter_next(&iter, &k, &v)) g_array_append_val(list, k); g_array_sort(list, str_comp); col = 0; for (i = 1; i < list->len; i++) { if (col == 0) { g_string_append_len(html, CONST_STR_LEN(" \n")); col++; } else if (col == 5) { g_string_append_len(html, CONST_STR_LEN(" \n")); col = 0; continue; } else { col++; } g_string_append_printf(html, " \n", g_array_index(list, gchar*, i)); } if (col) { for (i = 5 - col; i; i--) g_string_append_len(html, CONST_STR_LEN(" \n")); g_string_append_len(html, CONST_STR_LEN(" \n")); } g_string_append_len(html, CONST_STR_LEN("
%s
\n")); g_array_free(list, TRUE); } /* list environment vars */ { gchar **e; gchar **env = g_listenv(); g_string_append_len(html, CONST_STR_LEN("
Process environment
\n")); g_string_append_len(html, CONST_STR_LEN(" \n")); for (e = env; *e; e++) { g_string_append_printf(html, " \n", *e, getenv(*e)); } g_string_append_len(html, CONST_STR_LEN("
%s%s
\n")); g_strfreev(env); } g_string_append_len(html, CONST_STR_LEN( " \n" "\n" )); li_vrequest_handle_direct(vr); li_chunkqueue_append_string(vr->direct_out, html); li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html; charset=utf-8")); vr->response.http_status = 200; g_string_free(tmp_str, TRUE); return LI_HANDLER_GO_ON; } static void status_handle_vrclose(liVRequest *vr, liPlugin *p) { gint http_status = vr->response.http_status; UNUSED(p); if ((http_status < 100 && http_status != 0) || http_status > 599) { VR_ERROR(vr, "unknown status code: %d", http_status); return; } mod_status_response_codes[(http_status / 100)-1]++; } static const liPluginOption options[] = { { NULL, 0, 0, NULL } }; static const liPluginOptionPtr optionptrs[] = { { "status.css", LI_VALUE_STRING, NULL, NULL, NULL }, { NULL, 0, NULL, NULL, NULL } }; static const liPluginAction actions[] = { { "status.info", status_info_create, NULL }, { NULL, NULL, NULL } }; static const liPluginSetup setups[] = { { NULL, NULL, NULL } }; static void plugin_status_init(liServer *srv, liPlugin *p, gpointer userdata) { UNUSED(srv); UNUSED(userdata); p->options = options; p->optionptrs = optionptrs; p->actions = actions; p->setups = setups; p->handle_vrclose = status_handle_vrclose; } gboolean mod_status_init(liModules *mods, liModule *mod) { UNUSED(mod); MODULE_VERSION_CHECK(mods); mod->config = li_plugin_register(mods->main, "mod_status", plugin_status_init, NULL); return mod->config != NULL; } gboolean mod_status_free(liModules *mods, liModule *mod) { UNUSED(mods); UNUSED(mod); if (mod->config) li_plugin_free(mods->main, mod->config); return TRUE; }