diff --git a/src/modules/mod_status.c b/src/modules/mod_status.c new file mode 100644 index 0000000..64dded1 --- /dev/null +++ b/src/modules/mod_status.c @@ -0,0 +1,516 @@ +/* + * 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 "base.h" +#include "collect.h" + + +/* 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; + time_t 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) { + UNUSED(fdata); + mod_status_wrk_data *sd = g_slice_new(mod_status_wrk_data); + 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) { + UNUSED(fdata); + vrequest *vr = cbdata; + + VR_TRACE(vr, "finished collecting data: %s", complete ? "complete" : "not complete"); + vr->response.http_status = 200; + + 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); + + /* 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(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); + } else { + /* something went wrong, issue error page */ + CON_ERROR(vr->con, "%s", "collect request didn't end up complete"); + vrequest_error(vr); + } + + vrequest_joblist_append(vr); +} + +static handler_t status_page_handle(vrequest *vr, gpointer param) { + UNUSED(param); + + if (vr->state == VRS_HANDLE_REQUEST_HEADERS) { + VR_TRACE(vr, "%s", "collecting stats..."); + /* abuse fdata as pointer to plugin */ + collect_start(vr->con->wrk, status_collect_func, param, NULL, status_collect_cb, vr); + return HANDLER_WAIT_FOR_EVENT; + } + + 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, 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; +} + + +LI_API 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; +} + +LI_API gboolean mod_status_free(modules *mods, module *mod) { + UNUSED(mods); UNUSED(mod); + + if (mod->config) + plugin_free(mods->main, mod->config); + + return TRUE; +} diff --git a/src/wscript b/src/wscript index 0d04a5a..672195a 100644 --- a/src/wscript +++ b/src/wscript @@ -149,6 +149,8 @@ def build(bld): #lighty_mod(bld, 'mod_deflate', 'mod_deflate.c', uselib = 'bzip zlib') #lighty_mod(bld, 'mod_webdav', 'mod_webdav.c', uselib = 'sqlite3 xml uuid') lighty_mod(bld, 'mod_fortune', 'modules/mod_fortune.c') + #lighty_mod(bld, 'mod_track', 'modules/mod_track.c') + lighty_mod(bld, 'mod_status', 'modules/mod_status.c') #tests = bld.new_task_gen('cc', 'program') #tests.inst_var = 0