/* * 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 * type: string; values: "default", "blue" or a url to an external css file * Actions: * status.page - returns the status page to the client * * Example config: * req.path == "/status" { * status.css = "http://mydomain/status.css"; * status.page; * } * * Todo: * - handle race condition when connection is gone while collecting data (needs per connection plugin data) * * Author: * Copyright (c) 2008 Thomas Porzelt * License: * MIT, see COPYING file in the lighttpd 2 tree */ #include #include LI_API gboolean mod_status_init(modules *mods, module *mod); LI_API gboolean mod_status_free(modules *mods, module *mod); /* html snippet constants */ static const gchar header[] = "\n" "\n" "\n" " \n" " Lighttpd Status\n"; static const gchar html_top[] = "
Lighttpd Server Status
\n" "
\n" " Hostname: %s" " Uptime: %s\n" " Started at: %s\n" " Version: " PACKAGE_VERSION " (" __DATE__ " " __TIME__ ")\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_th[] = "
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"; static const gchar html_connections_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"; struct mod_status_wrk_data; typedef struct mod_status_wrk_data mod_status_wrk_data; struct mod_status_con_data; typedef struct mod_status_con_data mod_status_con_data; struct mod_status_con_data { guint worker_ndx; connection_state_t state; GString *remote_addr_str, *local_addr_str; gboolean is_ssl, keep_alive; GString *host, *path; ev_tstamp ts; guint64 bytes_in; guint64 bytes_out; guint64 bytes_in_5s_diff; guint64 bytes_out_5s_diff; }; struct mod_status_wrk_data { guint worker_ndx; statistics_t stats; GArray *connections; }; /* the CollectFunc */ static gpointer status_collect_func(worker *wrk, gpointer fdata) { mod_status_wrk_data *sd = g_slice_new(mod_status_wrk_data); 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++) { connection *c = g_array_index(wrk->connections, connection*, i); mod_status_con_data *cd = &g_array_index(sd->connections, mod_status_con_data, i); cd->is_ssl = c->is_ssl; cd->keep_alive = c->keep_alive; cd->remote_addr_str = g_string_new_len(GSTR_LEN(c->remote_addr_str)); cd->local_addr_str = g_string_new_len(GSTR_LEN(c->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->state = c->state; cd->ts = c->ts; cd->bytes_in = c->stats.bytes_in; cd->bytes_out = c->stats.bytes_out; cd->bytes_in_5s_diff = c->stats.bytes_in_5s_diff; cd->bytes_out_5s_diff = c->stats.bytes_out_5s_diff; } return sd; } /* the CollectCallback */ static void status_collect_cb(gpointer cbdata, gpointer fdata, GPtrArray *result, gboolean complete) { vrequest *vr = cbdata; UNUSED(fdata); if (complete) { GString *css; GString *tmpstr; guint total_connections = 0; /* we got everything */ statistics_t 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), 0, 0 }; GString *html = g_string_sized_new(8 * 1024); VR_DEBUG(vr, "finished collecting data: %s", complete ? "complete" : "not complete"); vr->response.http_status = 200; /* calculate total stats over all workers */ for (guint 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; } g_string_append_len(html, header, sizeof(header)-1); /* css */ css = _OPTION(vr, ((plugin*)fdata), 0).string; if (!css || !css->len) /* default css */ g_string_append_len(html, css_default, sizeof(css_default)-1); else if (g_str_equal(css->str, "blue")) /* blue css */ g_string_append_len(html, css_blue, sizeof(css_blue)-1); else /* external css */ g_string_append_printf(html, " \n", css->str); g_string_append_len(html, CONST_STR_LEN( " \n" " \n" )); tmpstr = counter_format2((guint64)(CUR_TS(vr->con->wrk) - vr->con->srv->started), COUNTER_TIME, -1); g_string_append_printf(html, html_top, vr->request.uri.host->str, tmpstr->str, vr->con->srv->started_str->str ); /* worker information, absolute values */ { GString *count_req, *count_bin, *count_bout; g_string_append_len(html, CONST_STR_LEN("
Absolute stats (since start)
\n")); g_string_append_len(html, html_worker_th, sizeof(html_worker_th)-1); #define PERCENTAGE(x, y) (y ? (x * 100 / y) : 0) for (guint i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); count_req = counter_format2(sd->stats.requests, COUNTER_UNITS, -1); count_bin = counter_format2(sd->stats.bytes_in, COUNTER_BYTES, 2); count_bout = counter_format2(sd->stats.bytes_out, COUNTER_BYTES, 2); 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)); g_string_free(count_req, TRUE); g_string_free(count_bin, TRUE); g_string_free(count_bout, TRUE); } #undef PERCENTAGE count_req = counter_format2(totals.requests, COUNTER_UNITS, -1); count_bin = counter_format2(totals.bytes_in, COUNTER_BYTES, 2); count_bout = counter_format2(totals.bytes_out, COUNTER_BYTES, 2); 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("
ClientStateHostPathDurationTraffic in/outTraffic in/out / s
%s%s%s%s%s%s / %s%s / %s
\n")); g_string_free(count_req, TRUE); g_string_free(count_bin, TRUE); g_string_free(count_bout, TRUE); } /* worker information, avg values */ { GString *count_req, *count_bin, *count_bout; guint uptime = CUR_TS(vr->con->wrk) - vr->con->srv->started; if (!uptime) uptime = 1; g_string_append_len(html, CONST_STR_LEN("
Average stats (since start)
\n")); g_string_append_len(html, html_worker_th_avg, sizeof(html_worker_th_avg)-1); #define PERCENTAGE(x) (sd->stat ## x ? (sd->stat ## x * 100 / total ## x) : 0) for (guint i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); count_req = counter_format2(sd->stats.requests / uptime, COUNTER_UNITS, -1); count_bin = counter_format2(sd->stats.bytes_in / uptime, COUNTER_BYTES, 2); count_bout = counter_format2(sd->stats.bytes_out / uptime, COUNTER_BYTES, 2); 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) ); g_string_free(count_req, TRUE); g_string_free(count_bin, TRUE); g_string_free(count_bout, TRUE); } #undef PERCENTAGE count_req = counter_format2(totals.requests / uptime, COUNTER_UNITS, -1); count_bin = counter_format2(totals.bytes_in / uptime, COUNTER_BYTES, 2); count_bout = counter_format2(totals.bytes_out / uptime, COUNTER_BYTES, 2); 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")); g_string_free(count_req, TRUE); g_string_free(count_bin, TRUE); g_string_free(count_bout, TRUE); } /* worker information, 5 seconds avg values */ { GString *count_req, *count_bin, *count_bout; time_t uptime = CUR_TS(vr->con->wrk) - vr->con->srv->started; if (!uptime) uptime = 1; g_string_append_len(html, CONST_STR_LEN("
Average stats (5 seconds)
\n")); g_string_append_len(html, html_worker_th_avg, sizeof(html_worker_th_avg)-1); #define PERCENTAGE(x) (sd->stat ## x ? (sd->stat ## x * 100 / total ## x) : 0) for (guint i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); count_req = counter_format2(sd->stats.requests_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_UNITS, -1); count_bin = counter_format2(sd->stats.bytes_in_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_BYTES, 2); count_bout = counter_format2(sd->stats.bytes_out_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_BYTES, 2); 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 ); g_string_free(count_req, TRUE); g_string_free(count_bin, TRUE); g_string_free(count_bout, TRUE); } #undef PERCENTAGE count_req = counter_format2(totals.requests_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_UNITS, -1); count_bin = counter_format2(totals.bytes_in_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_BYTES, 2); count_bout = counter_format2(totals.bytes_out_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_BYTES, 2); 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")); g_string_free(count_req, TRUE); g_string_free(count_bin, TRUE); g_string_free(count_bout, TRUE); } /* list connections */ { GString *ts, *bytes_in, *bytes_out, *bytes_in_5s, *bytes_out_5s; g_string_append_len(html, CONST_STR_LEN("
Active connections
\n")); g_string_append_len(html, html_connections_th, sizeof(html_connections_th)-1); for (guint i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); for (guint j = 0; j < sd->connections->len; j++) { mod_status_con_data *cd = &g_array_index(sd->connections, mod_status_con_data, j); ts = counter_format2((guint64)(CUR_TS(vr->con->wrk) - cd->ts), COUNTER_TIME, -1); bytes_in = counter_format2(cd->bytes_in, COUNTER_BYTES, 1); bytes_in_5s = counter_format2(cd->bytes_in_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_BYTES, 1); bytes_out = counter_format2(cd->bytes_out, COUNTER_BYTES, 1); bytes_out_5s = counter_format2(cd->bytes_out_5s_diff / G_GUINT64_CONSTANT(5), COUNTER_BYTES, 1); g_string_append_printf(html, html_connections_row, cd->remote_addr_str->str, connection_state_str(cd->state), cd->host->str, cd->path->str, ts->str, bytes_in->str, bytes_out->str, bytes_in_5s->str, bytes_out_5s->str ); 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(ts, 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_array_free(sd->connections, TRUE); } g_string_append_len(html, CONST_STR_LEN(" \n")); } /* free stats */ for (guint i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); g_slice_free(mod_status_wrk_data, sd); } g_string_append_len(html, CONST_STR_LEN( " \n" "\n" )); chunkqueue_append_string(vr->con->out, html); http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); g_string_free(tmpstr, TRUE); vrequest_handle_direct(vr); vrequest_joblist_append(vr); } else { /* something went wrong, client may have dropped the connection */ VR_ERROR(vr, "%s", "collect request didn't end up complete"); vrequest_error(vr); } } static handler_t status_page_handle(vrequest *vr, gpointer param, gpointer *context) { UNUSED(param); if (vr->state == VRS_HANDLE_REQUEST_HEADERS) { collect_info *ci; VR_DEBUG(vr, "%s", "collecting stats..."); /* abuse fdata as pointer to plugin */ ci = collect_start(vr->con->wrk, status_collect_func, param, NULL, status_collect_cb, vr); *context = ci; return HANDLER_WAIT_FOR_EVENT; } return HANDLER_GO_ON; } static handler_t status_page_cleanup(vrequest *vr, gpointer param, gpointer context) { collect_info *ci = context; UNUSED(vr); UNUSED(param); collect_break(ci); return HANDLER_GO_ON; } static action* status_page(server *srv, plugin* p, value *val) { UNUSED(srv); UNUSED(p); UNUSED(val); return action_new_function(status_page_handle, status_page_cleanup, NULL, p); } static const plugin_option options[] = { { "status.css", VALUE_STRING, NULL, NULL, NULL }, { NULL, 0, NULL, NULL, NULL } }; static const plugin_action actions[] = { { "status.page", status_page }, { NULL, NULL } }; static const plugin_setup setups[] = { { NULL, NULL } }; static void plugin_status_init(server *srv, plugin *p) { UNUSED(srv); p->options = options; p->actions = actions; p->setups = setups; } gboolean mod_status_init(modules *mods, module *mod) { UNUSED(mod); MODULE_VERSION_CHECK(mods); mod->config = plugin_register(mods->main, "mod_status", plugin_status_init); return mod->config != NULL; } gboolean mod_status_free(modules *mods, module *mod) { UNUSED(mods); UNUSED(mod); if (mod->config) plugin_free(mods->main, mod->config); return TRUE; }