/*
 * mod_status - display server status
 *
 * Author:
 *     Copyright (c) 2008-2010 Thomas Porzelt
 * License:
 *     MIT, see COPYING file in the lighttpd 2 tree
 */

#include <lighttpd/base.h>
#include <lighttpd/collect.h>
#include <lighttpd/encoding.h>

#include <lighttpd/plugin_core.h>

#include <lighttpd/lighttpd-glue.h>

#ifdef HAVE_SYS_RESOURCE_H
# include <sys/resource.h>
#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[] =
	"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
	"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
	"         \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
	"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
	"	<head>\n"
	"		<title>Lighttpd Status</title>\n";
static const gchar html_js[] =
	"		<script type=\"text/javascript\">\n"
	"			// <!--\n"
	"			var sort_dir = 'asc';\n"
	"			var prev_arrow = null;\n"
	"			var prev_elem = null;\n"
	"			function sort(elem, index) {\n"
	"				var table = elem.parentNode.parentNode.parentNode.parentNode;\n"
	"				var arrow = elem.parentNode.childNodes[elem.parentNode.childNodes.length - 1];\n"
	"				var column = elem.parentNode.cellIndex;\n"
	"				var rows = new Array();\n"
	"				for (i=1; i < table.rows.length; i++)\n"
	"					rows.push(table.rows[i]);\n"
	"				if (elem.className.indexOf('string') != -1) {\n"
	"					rows.sort(function (a, b) {\n"
	"						var text_a = a.cells[column].childNodes[index].innerHTML.toLowerCase();\n"
	"						var text_b = b.cells[column].childNodes[index].innerHTML.toLowerCase();\n"
	"						if (text_a == text_b) return 0;\n"
	"						return (text_a < text_b) ? -1 : 1;\n"
	"					});\n"
	"				} else {\n"
	"					rows.sort(function (a, b) {\n"
	"						var int_a = parseInt(a.cells[column].childNodes[index].getAttribute('value'));\n"
	"						var int_b = parseInt(b.cells[column].childNodes[index].getAttribute('value'));\n"
	"						return int_a - int_b;\n"
	"					});\n"
	"				}\n"
	"				if (prev_elem == elem) {\n"
	"					sort_dir = (sort_dir == 'asc') ? 'desc' : 'asc';\n"
	"					arrow.innerHTML = (sort_dir == 'asc') ? '&darr;' : '&uarr;';\n"
	"				} else {\n"
	"					elem.style.fontWeight = 'bold';\n"
	"					if (prev_elem != null) {\n"
	"						prev_arrow.innerHTML = '';\n"
	"						prev_elem.style.fontWeight = 'normal';\n"
	"					}\n"
	"					if (elem.className.indexOf('string') != -1) {\n"
	"						sort_dir = 'asc';\n"
	"						arrow.innerHTML = '&darr;';\n"
	"					} else {\n"
	"						sort_dir = 'desc';\n"
	"						arrow.innerHTML = '&uarr;';\n"
	"					}\n"
	"				}\n"
	"				if (sort_dir == 'desc')\n"
	"					rows.reverse();\n"
	"				for (i=0; i < rows.length; i++)\n"
	"					table.tBodies[0].appendChild(rows[i]);\n"
	"				prev_arrow = arrow;\n"
	"				prev_elem = elem;\n"
	"			}\n"
	"			// -->\n"
	"		</script>\n";

static const gchar html_top[] =
	"		<div class=\"header\">Lighttpd Server Status | \n"
	"			<span style=\"font-size: 12px;\"><a href=\"?\">main</a></strong> - <a href=\"?mode=runtime\">runtime</a></span>"
	"		</div>\n"
	"		<div class=\"spacer\">\n"
	"			<strong>Memory Usage</strong>: <span>%s</span>"
	"			<strong>Uptime</strong>: <span>%s</span>\n"
	"			<strong>Started at</strong>: <span>%s</span>\n"
	"			<strong>Version</strong>: <span>" PACKAGE_VERSION "(" PACKAGE_BUILD_DATE ")</span>\n"
	"		</div>\n";
static const gchar html_top_short[] =
	"		<div class=\"header\">Lighttpd Server Status</div>\n"
	"		<div class=\"spacer\">\n"
	"			<strong>Memory Usage</strong>: <span>%s</span>"
	"			<strong>Uptime</strong>: <span>%s</span>\n"
	"			<strong>Started at</strong>: <span>%s</span>\n"
	"		</div>\n";
static const gchar html_worker_th[] =
	"		<table cellspacing=\"0\">\n"
	"			<tr>\n"
	"				<th style=\"width: 100px;\"></th>\n"
	"				<th style=\"width: 175px;\">Requests</th>\n"
	"				<th style=\"width: 175px;\">Traffic in</th>\n"
	"				<th style=\"width: 175px;\">Traffic out</th>\n"
	"				<th style=\"width: 175px;\">Active connections</th>\n"
	"			</tr>\n";
static const gchar html_worker_th_avg[] =
	"		<table cellspacing=\"0\">\n"
	"			<tr>\n"
	"				<th style=\"width: 100px;\"></th>\n"
	"				<th style=\"width: 175px;\">Requests / s</th>\n"
	"				<th style=\"width: 175px;\">Traffic in / s</th>\n"
	"				<th style=\"width: 175px;\">Traffic out / s</th>\n"
	"				<th style=\"width: 175px;\">Active connections</th>\n"
	"			</tr>\n";
static const gchar html_worker_row[] =
	"			<tr class=\"%s\">\n"
	"				<td class=\"left\">%s</td>\n"
	"				<td>%s (%" G_GUINT64_FORMAT "%%)</td>\n"
	"				<td>%s (%" G_GUINT64_FORMAT "%%)</td>\n"
	"				<td>%s (%" G_GUINT64_FORMAT "%%)</td>\n"
	"				<td>%u (%u%%)</td>\n"
	"			</tr>\n";
static const gchar html_worker_row_avg[] =
	"			<tr class=\"%s\">\n"
	"				<td class=\"left\">%s</td>\n"
	"				<td>%s</td>\n"
	"				<td>%s</td>\n"
	"				<td>%s</td>\n"
	"				<td>%u</td>\n"
	"			</tr>\n";
static const gchar html_connections_sum[] =
	"		<table cellspacing=\"0\">\n"
	"			<tr>\n"
	"				<th style=\"width: 100px;\">inactive</th>\n"
	"				<th style=\"width: 100px;\">request start</th>\n"
	"				<th style=\"width: 100px;\">read header</th>\n"
	"				<th style=\"width: 100px;\">handle request</th>\n"
	"				<th style=\"width: 100px;\">write response</th>\n"
	"				<th style=\"width: 100px;\">keep-alive</th>\n"
	"				<th style=\"width: 100px;\">upgraded</th>\n"
	"			</tr>\n"
	"			<tr>\n"
	"				<td>%u</td>\n"
	"				<td>%u</td>\n"
	"				<td>%u</td>\n"
	"				<td>%u</td>\n"
	"				<td>%u</td>\n"
	"				<td>%u</td>\n"
	"				<td>%u</td>\n"
	"			</tr>\n"
	"		</table>\n";
static const gchar html_status_codes[] =
	"		<table cellspacing=\"0\">\n"
	"			<tr>\n"
	"				<th style=\"width: 100px;\">1xx (info)</th>\n"
	"				<th style=\"width: 175px;\">2xx (success)</th>\n"
	"				<th style=\"width: 175px;\">3xx (redirect)</th>\n"
	"				<th style=\"width: 175px;\">4xx (client error)</th>\n"
	"				<th style=\"width: 175px;\">5xx (server error)</th>\n"
	"			</tr>\n"
	"			<tr>\n"
	"				<td>%" G_GUINT64_FORMAT "</td>\n"
	"				<td>%" G_GUINT64_FORMAT "</td>\n"
	"				<td>%" G_GUINT64_FORMAT "</td>\n"
	"				<td>%" G_GUINT64_FORMAT "</td>\n"
	"				<td>%" G_GUINT64_FORMAT "</td>\n"
	"			</tr>\n"
	"		</table>\n";

static const gchar html_connections_th[] =
	"		<table cellspacing=\"0\">\n"
	"			<tr>\n"
	"				<th class=\"left\"><span class=\"string\" onclick=\"sort(this, 0); return false;\">Client</span><span></span></th>\n"
	"				<th style=\"width: 140px;\"><span class=\"string\" onclick=\"sort(this, 0); return false;\">State</span><span></span></th>\n"
	"				<th style=\"width: 170px;\"><span class=\"string\" onclick=\"sort(this, 0); return false;\">Host</span><span></span></th>\n"
	"				<th><span class=\"string\" onclick=\"sort(this, 0); return false;\">Path+Querystring</span><span></span></th>\n"
	"				<th><span class=\"int\" onclick=\"sort(this, 0); return false;\">Duration</span><span></span></th>\n"
	"				<th><span class=\"int\" onclick=\"sort(this, 0); return false;\">Timeout</span><span></span></th>\n"
	"				<th>Traffic <span class=\"int\" onclick=\"sort(this, 0); return false;\">in</span><span>/</span><span class=\"int\" onclick=\"sort(this, 2); return false;\">out</span><span></span></th>\n"
	"				<th>Traffic <span class=\"int\" onclick=\"sort(this, 0); return false;\">in</span><span>/</span><span class=\"int\" onclick=\"sort(this, 2); return false;\">out</span><span> / s</span><span></span></th>\n"
	"				<th><span class=\"string\" onclick=\"sort(this, 0); return false;\">Method</span><span></span></th>\n"
	"				<th><span class=\"int\" onclick=\"sort(this, 0); return false;\">Request Size</span><span></span></th>\n"
	"				<th><span class=\"int\" onclick=\"sort(this, 0); return false;\">Response Size</span><span></span></th>\n"
	"			</tr>\n";
static const gchar html_connections_row[] =
	"			<tr>\n"
	"				<td class=\"left\"><span>%s</span></td>\n"
	"				<td><span>%s</span></td>\n"
	"				<td><span>%s</span></td>\n"
	"				<td class=\"left\"><span>%s%s%s</span></td>\n"
	"				<td><span value=\"%"G_GUINT64_FORMAT"\">%s</span></td>\n"
	"				<td><span value=\"%"G_GUINT64_FORMAT"\">%s</span></td>\n"
	"				<td><span value=\"%"G_GUINT64_FORMAT"\">%s</span><span> / </span><span value=\"%"G_GUINT64_FORMAT"\">%s</span></td>\n"
	"				<td><span value=\"%"G_GUINT64_FORMAT"\">%s</span><span> / </span><span value=\"%"G_GUINT64_FORMAT"\">%s</span></td>\n"
	"				<td><span>%s</span></td>\n"
	"				<td><span value=\"%"G_GUINT64_FORMAT"\">%s</span></td>\n"
	"				<td><span value=\"%"G_GUINT64_FORMAT"\">%s</span></td>\n"
	"			</tr>\n";


static const gchar html_server_info[] =
	"		<table cellspacing=\"0\">\n"
	"			<tr>\n"
	"				<td  class=\"left\" style=\"width: 125px;\">Hostname</td>\n"
	"				<td class=\"left\">%s</td>\n"
	"			</tr>\n"
	"			<tr>\n"
	"				<td class=\"left\">User</td>\n"
	"				<td class=\"left\">%s (%u)</td>\n"
	"			</tr>\n"
	"			<tr>\n"
	"				<td class=\"left\">Event handler</td>\n"
	"				<td class=\"left\">%s</td>\n"
	"			</tr>\n"
	"			<tr>\n"
	"				<td class=\"left\">File descriptor limit</td>\n"
	"				<td class=\"left\">%s</td>\n"
	"			</tr>\n"
	"			<tr>\n"
	"				<td class=\"left\">Connection limit</td>\n"
	"				<td class=\"left\">%u</td>\n"
	"			</tr>\n"
	"			<tr>\n"
	"				<td class=\"left\">Core size limit</td>\n"
	"				<td class=\"left\">%s</td>\n"
	"			</tr>\n"
	"			<tr>\n"
	"				<td class=\"left\">IO Timeout</td>\n"
	"				<td class=\"left\">%"G_GUINT64_FORMAT" seconds</td>\n"
	"			</tr>\n"
	"		</table>\n";
static const gchar html_libinfo_th[] =
	"		<table cellspacing=\"0\">\n"
	"			<tr>\n"
	"				<th style=\"width: 100px;\"></th>\n"
	"				<th style=\"width: 100px;\">linked</th>\n"
	"				<th style=\"width: 100px;\">headers</th>\n"
	"			</tr>\n";
static const gchar html_libinfo_row[] =
	"			<tr>\n"
	"				<td class=\"left\">%s</td>\n"
	"				<td style=\"text-align: center;\">%s</td>\n"
	"				<td style=\"text-align: center;\">%s</td>\n"
	"			</tr>\n";
static const gchar html_libev_th[] =
	"		<table cellspacing=\"0\">\n"
	"			<tr>\n"
	"				<th style=\"width: 100px;\"></th>\n"
	"				<th style=\"width: 100px;\">select</th>\n"
	"				<th style=\"width: 100px;\">poll</th>\n"
	"				<th style=\"width: 100px;\">epoll</th>\n"
	"				<th style=\"width: 100px;\">kqueue</th>\n"
	"				<th style=\"width: 100px;\">/dev/poll</th>\n"
	"				<th style=\"width: 125px;\">solaris event port</th>\n"
	"			</tr>\n";
static const gchar html_libev_row[] =
	"			<tr>\n"
	"				<td class=\"left\">%s</td>\n"
	"				<td style=\"text-align: center;\">%s</td>\n"
	"				<td style=\"text-align: center;\">%s</td>\n"
	"				<td style=\"text-align: center;\">%s</td>\n"
	"				<td style=\"text-align: center;\">%s</td>\n"
	"				<td style=\"text-align: center;\">%s</td>\n"
	"				<td style=\"text-align: center;\">%s</td>\n"
	"			</tr>\n";


static const gchar css_default[] =
	"		<style type=\"text/css\">\n"
	"			body { margin: 0; padding: 0; font-family: \"lucida grande\",tahoma,verdana,arial,sans-serif; font-size: 12px; }\n"
	"			.header { padding: 5px; background-color: #6D84B4; font-size: 16px; color: white; border: 1px solid #3B5998; font-weight: bold; }\n"
	"			.header a { color: #F2F2F2; }\n"
	"			.spacer { background-color: #F2F2F2; border-bottom: 1px solid #CCC; padding: 5px; }\n"
	"			.spacer span { padding-right: 25px; }\n"
	"			.title { margin-left: 6px; margin-top: 20px; margin-bottom: 5px; }\n"
	"			.text { margin-left: 6px; margin-bottom: 5px; }\n"
	"			table { margin-left: 5px; border: 1px solid #CCC; }\n"
	"			th { font-weight: normal; padding: 3px; background-color: #E0E0E0;\n"
	"			border-bottom: 1px solid #BABABA; border-right: 1px solid #BABABA; border-top: 1px solid #FEFEFE; }\n"
	"			td { text-align: right; padding: 3px; border-bottom: 1px solid #F0F0F0; border-right: 1px solid #F8F8F8; }\n"
	"			.left { text-align: left; }\n"
	"			.totals td { border-top: 1px solid #DDDDDD; }\n"
	"		</style>\n";
/* blue theme by nitrox */
static const gchar css_blue[] =
	"		<style type=\"text/css\">\n"
	"			body { margin: 0; padding: 0; font-family: \"lucida grande\",tahoma,verdana,arial,sans-serif; font-size: 12px; background-color: #6d84b4; }\n"
	"			.header { padding: 5px; background-color: #6D84B4; font-size: 16px; color: white; border: 1px solid #3B5998; font-weight: bold; }\n"
	"			.header a { color: #F2F2F2; }\n"
	"			.spacer { background-color: #F2F2F2; border-bottom: 1px solid #CCC; padding: 5px; }\n"
	"			.spacer span { padding-right: 25px; }\n"
	"			.title { margin-left: 6px; margin-top: 20px; margin-bottom: 5px; }\n"
	"			.text { margin-left: 6px; margin-bottom: 5px; }\n"
	"			table { margin-left: 5px; border: 1px solid #CCC; }\n"
	"			th { font-weight: normal; padding: 3px; background-color: #E0E0E0;\n"
	"			border-bottom: 1px solid #BABABA; border-right: 1px solid #BABABA; border-top: 1px solid #FEFEFE; }\n"
	"			td { text-align: right; padding: 3px; border-bottom: 1px solid #F0F0F0; border-right: 1px solid #F8F8F8; }\n"
	"			.left { text-align: left; }\n"
	"			.totals td { border-top: 1px solid #DDDDDD; }\n"
	"		</style>\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(liWorker *wrk, 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(wrk);
	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_FORCE_ASSERT(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;

	li_g_string_append_len(html, CONST_STR_LEN(html_header));
	li_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)) {
		li_g_string_append_len(html, CONST_STR_LEN("<meta http-equiv=\"refresh\" content=\""));
		/* temp char swap */
		c = val[len]; val[len] = '\0';
		li_string_encode_append(val, html, LI_ENCODING_HTML);
		val[len] = c;
		li_g_string_append_len(html, CONST_STR_LEN("\">\n"));
	}

	/* css */
	css = _OPTIONPTR(vr, p, 0).string;

	if (!css || !css->len) /* default css */
		li_g_string_append_len(html, CONST_STR_LEN(css_default));
	else if (g_str_equal(css->str, "blue")) /* blue css */
		li_g_string_append_len(html, CONST_STR_LEN(css_blue));
	else /* external css */
		g_string_append_printf(html, "		<link rel=\"stylesheet\" rev=\"stylesheet\" href=\"%s\" media=\"screen\" />\n", css->str);

	li_g_string_append_len(html, CONST_STR_LEN(
		"	</head>\n"
		"	<body>\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 */
	li_g_string_append_len(html, CONST_STR_LEN("		<div class=\"title\"><strong>Absolute stats</strong></div>\n"));
	li_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);
	li_g_string_append_len(html, CONST_STR_LEN("		</table>\n"));

	/* worker information, avg values */
	li_g_string_append_len(html, CONST_STR_LEN("<div class=\"title\"><strong>Average stats</strong> (since start)</div>\n"));

	li_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)
	);
	li_g_string_append_len(html, CONST_STR_LEN("		</table>\n"));


	/* worker information, 5 seconds avg values */
	li_g_string_append_len(html, CONST_STR_LEN("<div class=\"title\"><strong>Average stats</strong> (5 seconds)</div>\n"));
	li_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
	);
	li_g_string_append_len(html, CONST_STR_LEN("		</table>\n"));


	/* worker information, peak values */
	li_g_string_append_len(html, CONST_STR_LEN("<div class=\"title\"><strong>Peak stats</strong></div>\n"));
	li_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
	);
	li_g_string_append_len(html, CONST_STR_LEN("		</table>\n"));


	/* connection counts */
	li_g_string_append_len(html, CONST_STR_LEN("<div class=\"title\"><strong>Connections</strong> (states, sum)</div>\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 */
	li_g_string_append_len(html, CONST_STR_LEN("<div class=\"title\"><strong>HTTP Status codes</strong> (sum)</div>\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);

		li_g_string_append_len(html, CONST_STR_LEN("<div class=\"title\"><strong>Active connections</strong></div>\n"));
		li_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 : ""
				);
			}
		}

		li_g_string_append_len(html, CONST_STR_LEN("		</table>\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);
	}

	li_g_string_append_len(html, CONST_STR_LEN(
		" </body>\n"
		"</html>\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 */
	li_g_string_append_len(html, CONST_STR_LEN("# Absolute Values\nuptime: "));
	li_string_append_int(html, (gint64)uptime);
	li_g_string_append_len(html, CONST_STR_LEN("\nmemory_usage: "));
	li_string_append_int(html, li_memory_usage());
	li_g_string_append_len(html, CONST_STR_LEN("\nrequests_abs: "));
	li_string_append_int(html, totals->requests);
	li_g_string_append_len(html, CONST_STR_LEN("\ntraffic_out_abs: "));
	li_string_append_int(html, totals->bytes_out);
	li_g_string_append_len(html, CONST_STR_LEN("\ntraffic_in_abs: "));
	li_string_append_int(html, totals->bytes_in);
	li_g_string_append_len(html, CONST_STR_LEN("\nconnections_abs: "));
	li_string_append_int(html, total_connections);
	/* average since start */
	li_g_string_append_len(html, CONST_STR_LEN("\n\n# Average Values (since start)\nrequests_avg: "));
	li_string_append_int(html, totals->requests / uptime);
	li_g_string_append_len(html, CONST_STR_LEN("\ntraffic_out_avg: "));
	li_string_append_int(html, totals->bytes_out / uptime);
	li_g_string_append_len(html, CONST_STR_LEN("\ntraffic_in_avg: "));
	li_string_append_int(html, totals->bytes_in / uptime);
	li_g_string_append_len(html, CONST_STR_LEN("\nconnections_avg: "));
	li_string_append_int(html, totals->active_cons_cum / uptime);
	/* average last 5 seconds */
	li_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);
	li_g_string_append_len(html, CONST_STR_LEN("\ntraffic_out_avg_5sec: "));
	li_string_append_int(html, totals->bytes_out_5s_diff / 5);
	li_g_string_append_len(html, CONST_STR_LEN("\ntraffic_in_avg_5sec: "));
	li_string_append_int(html, totals->bytes_in_5s_diff / 5);
	li_g_string_append_len(html, CONST_STR_LEN("\nconnections_avg_5sec: "));
	li_string_append_int(html, totals->active_cons_5s / 5);
	/* connection states */
	li_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]);
	li_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]);
	li_g_string_append_len(html, CONST_STR_LEN("\nconnection_state_handle_request: "));
	li_string_append_int(html, connection_count[LI_CON_STATE_HANDLE_MAINVR]);
	li_g_string_append_len(html, CONST_STR_LEN("\nconnection_state_write_response: "));
	li_string_append_int(html, connection_count[LI_CON_STATE_WRITE]);
	li_g_string_append_len(html, CONST_STR_LEN("\nconnection_state_keep_alive: "));
	li_string_append_int(html, connection_count[LI_CON_STATE_KEEP_ALIVE]);
	li_g_string_append_len(html, CONST_STR_LEN("\nconnection_state_upgraded: "));
	li_string_append_int(html, connection_count[LI_CON_STATE_UPGRADED]);
	/* status cpdes */
	li_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]);
	li_g_string_append_len(html, CONST_STR_LEN("\nstatus_2xx: "));
	li_string_append_int(html, mod_status_response_codes[1]);
	li_g_string_append_len(html, CONST_STR_LEN("\nstatus_3xx: "));
	li_string_append_int(html, mod_status_response_codes[2]);
	li_g_string_append_len(html, CONST_STR_LEN("\nstatus_4xx: "));
	li_string_append_int(html, mod_status_response_codes[3]);
	li_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 */
	li_g_string_append_len(html, CONST_STR_LEN("Total Accesses: "));
	li_string_append_int(html, totals->requests);
	li_g_string_append_len(html, CONST_STR_LEN("\nTotal kBytes: "));
	li_string_append_int(html, totals->bytes_out / 1024);
	li_g_string_append_len(html, CONST_STR_LEN("\nUptime: "));
	li_string_append_int(html, (gint64)uptime);
	/* connection states */
	li_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]);
	li_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 */
	li_g_string_append_len(html, CONST_STR_LEN("\nTraffic: "));
	li_string_append_int(html, totals->bytes_out / uptime);
	/* average last 5 seconds */
	li_g_string_append_len(html, CONST_STR_LEN("\nTraffic5s: "));
	li_string_append_int(html, totals->bytes_out_5s_diff / 5);
	/* output scoreboard */
	li_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]);
		}
	}
	li_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_FORCE_ASSERT(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);

	val = li_value_get_single_argument(val);

	param->p = p;
	param->short_info = FALSE;

	if (LI_VALUE_STRING == li_value_type(val)) {
		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;
		}
	} else if (!li_value_is_nothing(val)) {
		ERROR(srv, "%s", "status.info expects either a string or nothing as parameter");
		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);

	li_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)) {
			li_g_string_append_len(html, CONST_STR_LEN("<meta http-equiv=\"refresh\" content=\""));
			/* temp char swap */
			c = val[len]; val[len] = '\0';
			li_string_encode_append(val, html, LI_ENCODING_HTML);
			val[len] = c;
			li_g_string_append_len(html, CONST_STR_LEN("\">\n"));
		}
	}

	/* css */
	{
		GString* css = _OPTIONPTR(vr, p, 0).string;

		if (!css || !css->len) /* default css */
			li_g_string_append_len(html, CONST_STR_LEN(css_default));
		else if (g_str_equal(css->str, "blue")) /* blue css */
			li_g_string_append_len(html, CONST_STR_LEN(css_blue));
		else /* external css */
			g_string_append_printf(html, "		<link rel=\"stylesheet\" rev=\"stylesheet\" href=\"%s\" media=\"screen\" />\n", css->str);
	}

	li_g_string_append_len(html, CONST_STR_LEN(
		"	</head>\n"
		"	<body>\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

		li_g_string_append_len(html, CONST_STR_LEN("		<div class=\"title\"><strong>Server info</strong></div>\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 */
	{
		li_g_string_append_len(html, CONST_STR_LEN("		<div class=\"title\"><strong>Libraries</strong></div>\n"));
		li_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
		);

		li_g_string_append_len(html, CONST_STR_LEN("		</table>\n"));
	}

	/* libev info */
	{
		guint i;

		li_g_string_append_len(html, CONST_STR_LEN("		<div class=\"title\"><strong>libev backends</strong></div>\n"));
		li_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"
		);

		li_g_string_append_len(html, CONST_STR_LEN("		</table>\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));

		li_g_string_append_len(html, CONST_STR_LEN("		<div class=\"title\"><strong>Loaded modules</strong></div>\n"));
		li_g_string_append_len(html, CONST_STR_LEN("		<table cellspacing=\"0\">\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) {
				li_g_string_append_len(html, CONST_STR_LEN("			<tr>\n"));
				col++;
			}
			else if (col == 5) {
				li_g_string_append_len(html, CONST_STR_LEN("			</tr>\n"));
				col = 0;
				continue;
			} else {
				col++;
			}

			g_string_append_printf(html, "				<td class=\"left\">%s</td>\n", g_array_index(list, gchar*, i));
		}

		if (col) {
			for (i = 5 - col; i; i--)
				li_g_string_append_len(html, CONST_STR_LEN("				<td></td>\n"));

			li_g_string_append_len(html, CONST_STR_LEN("			</tr>\n"));
		}

		li_g_string_append_len(html, CONST_STR_LEN("		</table>\n"));

		g_array_free(list, TRUE);
	}

	/* list environment vars */
	{
		gchar **e;
		gchar **env = g_listenv();

		li_g_string_append_len(html, CONST_STR_LEN("		<div class=\"title\"><strong>Process environment</strong></div>\n"));
		li_g_string_append_len(html, CONST_STR_LEN("		<table cellspacing=\"0\">\n"));

		for (e = env; *e; e++) {
			g_string_append_printf(html, "			<tr><td class=\"left\">%s</td><td class=\"left\">%s</td></tr>\n", *e, getenv(*e));
		}

		li_g_string_append_len(html, CONST_STR_LEN("		</table>\n"));

		g_strfreev(env);
	}

	li_g_string_append_len(html, CONST_STR_LEN(
		" </body>\n"
		"</html>\n"
	));

	LI_FORCE_ASSERT(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;
}
