[core] $HTTP["remoteip"] must handle IPv6 w/o []
[lighttpd.git] / src / mod_status.c
bloba37b6cd95d4d7e8ce0b64209fc9144618dd58adf
1 #include "first.h"
3 #include "server.h"
4 #include "connections.h"
5 #include "response.h"
6 #include "connections.h"
7 #include "log.h"
9 #include "plugin.h"
11 #include "inet_ntop_cache.h"
13 #include <sys/types.h>
15 #include <fcntl.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
19 #include <errno.h>
20 #include <time.h>
21 #include <stdio.h>
23 typedef struct {
24 buffer *config_url;
25 buffer *status_url;
26 buffer *statistics_url;
28 int sort;
29 } plugin_config;
31 typedef struct {
32 PLUGIN_DATA;
34 double traffic_out;
35 double requests;
37 double mod_5s_traffic_out[5];
38 double mod_5s_requests[5];
39 size_t mod_5s_ndx;
41 double rel_traffic_out;
42 double rel_requests;
44 double abs_traffic_out;
45 double abs_requests;
47 double bytes_written;
49 buffer *module_list;
51 plugin_config **config_storage;
53 plugin_config conf;
54 } plugin_data;
56 INIT_FUNC(mod_status_init) {
57 plugin_data *p;
58 size_t i;
60 p = calloc(1, sizeof(*p));
62 p->traffic_out = p->requests = 0;
63 p->rel_traffic_out = p->rel_requests = 0;
64 p->abs_traffic_out = p->abs_requests = 0;
65 p->bytes_written = 0;
66 p->module_list = buffer_init();
68 for (i = 0; i < 5; i++) {
69 p->mod_5s_traffic_out[i] = p->mod_5s_requests[i] = 0;
72 return p;
75 FREE_FUNC(mod_status_free) {
76 plugin_data *p = p_d;
78 UNUSED(srv);
80 if (!p) return HANDLER_GO_ON;
82 buffer_free(p->module_list);
84 if (p->config_storage) {
85 size_t i;
86 for (i = 0; i < srv->config_context->used; i++) {
87 plugin_config *s = p->config_storage[i];
89 buffer_free(s->status_url);
90 buffer_free(s->statistics_url);
91 buffer_free(s->config_url);
93 free(s);
95 free(p->config_storage);
99 free(p);
101 return HANDLER_GO_ON;
104 SETDEFAULTS_FUNC(mod_status_set_defaults) {
105 plugin_data *p = p_d;
106 size_t i;
108 config_values_t cv[] = {
109 { "status.status-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
110 { "status.config-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
111 { "status.enable-sort", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
112 { "status.statistics-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
113 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
116 if (!p) return HANDLER_ERROR;
118 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
120 for (i = 0; i < srv->config_context->used; i++) {
121 data_config const* config = (data_config const*)srv->config_context->data[i];
122 plugin_config *s;
124 s = calloc(1, sizeof(plugin_config));
125 s->config_url = buffer_init();
126 s->status_url = buffer_init();
127 s->sort = 1;
128 s->statistics_url = buffer_init();
130 cv[0].destination = s->status_url;
131 cv[1].destination = s->config_url;
132 cv[2].destination = &(s->sort);
133 cv[3].destination = s->statistics_url;
135 p->config_storage[i] = s;
137 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
138 return HANDLER_ERROR;
142 return HANDLER_GO_ON;
147 static int mod_status_row_append(buffer *b, const char *key, const char *value) {
148 buffer_append_string_len(b, CONST_STR_LEN(" <tr>\n"));
149 buffer_append_string_len(b, CONST_STR_LEN(" <td><b>"));
150 buffer_append_string(b, key);
151 buffer_append_string_len(b, CONST_STR_LEN("</b></td>\n"));
152 buffer_append_string_len(b, CONST_STR_LEN(" <td>"));
153 buffer_append_string(b, value);
154 buffer_append_string_len(b, CONST_STR_LEN("</td>\n"));
155 buffer_append_string_len(b, CONST_STR_LEN(" </tr>\n"));
157 return 0;
160 static int mod_status_header_append(buffer *b, const char *key) {
161 buffer_append_string_len(b, CONST_STR_LEN(" <tr>\n"));
162 buffer_append_string_len(b, CONST_STR_LEN(" <th colspan=\"2\">"));
163 buffer_append_string(b, key);
164 buffer_append_string_len(b, CONST_STR_LEN("</th>\n"));
165 buffer_append_string_len(b, CONST_STR_LEN(" </tr>\n"));
167 return 0;
170 static int mod_status_header_append_sort(buffer *b, void *p_d, const char* key) {
171 plugin_data *p = p_d;
173 if (p->conf.sort) {
174 buffer_append_string_len(b, CONST_STR_LEN("<th class=\"status\"><a href=\"#\" class=\"sortheader\" onclick=\"resort(this);return false;\">"));
175 buffer_append_string(b, key);
176 buffer_append_string_len(b, CONST_STR_LEN("<span class=\"sortarrow\">:</span></a></th>\n"));
177 } else {
178 buffer_append_string_len(b, CONST_STR_LEN("<th class=\"status\">"));
179 buffer_append_string(b, key);
180 buffer_append_string_len(b, CONST_STR_LEN("</th>\n"));
183 return 0;
186 static int mod_status_get_multiplier(double *avg, char *multiplier, int size) {
187 *multiplier = ' ';
189 if (*avg > size) { *avg /= size; *multiplier = 'k'; }
190 if (*avg > size) { *avg /= size; *multiplier = 'M'; }
191 if (*avg > size) { *avg /= size; *multiplier = 'G'; }
192 if (*avg > size) { *avg /= size; *multiplier = 'T'; }
193 if (*avg > size) { *avg /= size; *multiplier = 'P'; }
194 if (*avg > size) { *avg /= size; *multiplier = 'E'; }
195 if (*avg > size) { *avg /= size; *multiplier = 'Z'; }
196 if (*avg > size) { *avg /= size; *multiplier = 'Y'; }
198 return 0;
201 static handler_t mod_status_handle_server_status_html(server *srv, connection *con, void *p_d) {
202 plugin_data *p = p_d;
203 buffer *b = buffer_init();
204 size_t j;
205 double avg;
206 char multiplier = '\0';
207 char buf[32];
208 time_t ts;
210 int days, hours, mins, seconds;
212 /*(CON_STATE_CLOSE must be last state in enum connection_state_t)*/
213 int cstates[CON_STATE_CLOSE+3];
214 memset(cstates, 0, sizeof(cstates));
216 buffer_copy_string_len(b, CONST_STR_LEN(
217 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
218 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
219 " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
220 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
221 " <head>\n"
222 " <title>Status</title>\n"
224 " <style type=\"text/css\">\n"
225 " table.status { border: black solid thin; }\n"
226 " td { white-space: nowrap; }\n"
227 " td.int { background-color: #f0f0f0; text-align: right }\n"
228 " td.string { background-color: #f0f0f0; text-align: left }\n"
229 " th.status { background-color: black; color: white; font-weight: bold; }\n"
230 " a.sortheader { background-color: black; color: white; font-weight: bold; text-decoration: none; display: block; }\n"
231 " span.sortarrow { color: white; text-decoration: none; }\n"
232 " </style>\n"));
234 if (!buffer_string_is_empty(con->uri.query) && 0 == memcmp(con->uri.query->ptr, CONST_STR_LEN("refresh="))) {
235 /* Note: Refresh is an historical, but non-standard HTTP header
236 * References (meta http-equiv="refresh" use is deprecated):
237 * https://www.w3.org/TR/WCAG10-HTML-TECHS/#meta-element
238 * https://www.w3.org/TR/WCAG10-CORE-TECHS/#auto-page-refresh
239 * https://www.w3.org/QA/Tips/reback
241 const long refresh = strtol(con->uri.query->ptr+sizeof("refresh=")-1, NULL, 10);
242 if (refresh > 0) {
243 buffer_append_string_len(b, CONST_STR_LEN("<meta http-equiv=\"refresh\" content=\""));
244 buffer_append_int(b, refresh < 604800 ? refresh : 604800);
245 buffer_append_string_len(b, CONST_STR_LEN("\">\n"));
249 if (p->conf.sort) {
250 buffer_append_string_len(b, CONST_STR_LEN(
251 "<script type=\"text/javascript\">\n"
252 "// <!--\n"
253 "var sort_column;\n"
254 "var prev_span = null;\n"
256 "function get_inner_text(el) {\n"
257 " if((typeof el == 'string')||(typeof el == 'undefined'))\n"
258 " return el;\n"
259 " if(el.innerText)\n"
260 " return el.innerText;\n"
261 " else {\n"
262 " var str = \"\";\n"
263 " var cs = el.childNodes;\n"
264 " var l = cs.length;\n"
265 " for (i=0;i<l;i++) {\n"
266 " if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n"
267 " else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n"
268 " }\n"
269 " }\n"
270 " return str;\n"
271 "}\n"
273 "function sortfn(a,b) {\n"
274 " var at = get_inner_text(a.cells[sort_column]);\n"
275 " var bt = get_inner_text(b.cells[sort_column]);\n"
276 " if (a.cells[sort_column].className == 'int') {\n"
277 " return parseInt(at)-parseInt(bt);\n"
278 " } else {\n"
279 " aa = at.toLowerCase();\n"
280 " bb = bt.toLowerCase();\n"
281 " if (aa==bb) return 0;\n"
282 " else if (aa<bb) return -1;\n"
283 " else return 1;\n"
284 " }\n"
285 "}\n"
287 "function resort(lnk) {\n"
288 " var span = lnk.childNodes[1];\n"
289 " var table = lnk.parentNode.parentNode.parentNode.parentNode;\n"
290 " var rows = new Array();\n"
291 " for (j=1;j<table.rows.length;j++)\n"
292 " rows[j-1] = table.rows[j];\n"
293 " sort_column = lnk.parentNode.cellIndex;\n"
294 " rows.sort(sortfn);\n"
296 " if (prev_span != null) prev_span.innerHTML = '';\n"
297 " if (span.getAttribute('sortdir')=='down') {\n"
298 " span.innerHTML = '&uarr;';\n"
299 " span.setAttribute('sortdir','up');\n"
300 " rows.reverse();\n"
301 " } else {\n"
302 " span.innerHTML = '&darr;';\n"
303 " span.setAttribute('sortdir','down');\n"
304 " }\n"
305 " for (i=0;i<rows.length;i++)\n"
306 " table.tBodies[0].appendChild(rows[i]);\n"
307 " prev_span = span;\n"
308 "}\n"
309 "// -->\n"
310 "</script>\n"));
313 buffer_append_string_len(b, CONST_STR_LEN(
314 " </head>\n"
315 " <body>\n"));
319 /* connection listing */
320 buffer_append_string_len(b, CONST_STR_LEN("<h1>Server-Status ("));
321 buffer_append_string_buffer(b, con->conf.server_tag);
322 buffer_append_string_len(b, CONST_STR_LEN(")</h1>"));
324 buffer_append_string_len(b, CONST_STR_LEN("<table summary=\"status\" class=\"status\">"));
325 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Hostname</td><td class=\"string\">"));
326 buffer_append_string_buffer(b, con->uri.authority);
327 buffer_append_string_len(b, CONST_STR_LEN(" ("));
328 buffer_append_string_buffer(b, con->server_name);
329 buffer_append_string_len(b, CONST_STR_LEN(")</td></tr>\n"));
330 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Uptime</td><td class=\"string\">"));
332 ts = srv->cur_ts - srv->startup_ts;
334 days = ts / (60 * 60 * 24);
335 ts %= (60 * 60 * 24);
337 hours = ts / (60 * 60);
338 ts %= (60 * 60);
340 mins = ts / (60);
341 ts %= (60);
343 seconds = ts;
345 if (days) {
346 buffer_append_int(b, days);
347 buffer_append_string_len(b, CONST_STR_LEN(" days "));
350 if (hours) {
351 buffer_append_int(b, hours);
352 buffer_append_string_len(b, CONST_STR_LEN(" hours "));
355 if (mins) {
356 buffer_append_int(b, mins);
357 buffer_append_string_len(b, CONST_STR_LEN(" min "));
360 buffer_append_int(b, seconds);
361 buffer_append_string_len(b, CONST_STR_LEN(" s"));
363 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
364 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Started at</td><td class=\"string\">"));
366 ts = srv->startup_ts;
368 strftime(buf, sizeof(buf) - 1, "%Y-%m-%d %H:%M:%S", localtime(&ts));
369 buffer_append_string(b, buf);
370 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
373 buffer_append_string_len(b, CONST_STR_LEN("<tr><th colspan=\"2\">absolute (since start)</th></tr>\n"));
375 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Requests</td><td class=\"string\">"));
376 avg = p->abs_requests;
378 mod_status_get_multiplier(&avg, &multiplier, 1000);
380 buffer_append_int(b, avg);
381 buffer_append_string_len(b, CONST_STR_LEN(" "));
382 if (multiplier) buffer_append_string_len(b, &multiplier, 1);
383 buffer_append_string_len(b, CONST_STR_LEN("req</td></tr>\n"));
385 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Traffic</td><td class=\"string\">"));
386 avg = p->abs_traffic_out;
388 mod_status_get_multiplier(&avg, &multiplier, 1024);
390 snprintf(buf, sizeof(buf), "%.2f", avg);
391 buffer_append_string(b, buf);
392 buffer_append_string_len(b, CONST_STR_LEN(" "));
393 if (multiplier) buffer_append_string_len(b, &multiplier, 1);
394 buffer_append_string_len(b, CONST_STR_LEN("byte</td></tr>\n"));
398 buffer_append_string_len(b, CONST_STR_LEN("<tr><th colspan=\"2\">average (since start)</th></tr>\n"));
400 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Requests</td><td class=\"string\">"));
401 avg = p->abs_requests / (srv->cur_ts - srv->startup_ts);
403 mod_status_get_multiplier(&avg, &multiplier, 1000);
405 buffer_append_int(b, avg);
406 buffer_append_string_len(b, CONST_STR_LEN(" "));
407 if (multiplier) buffer_append_string_len(b, &multiplier, 1);
408 buffer_append_string_len(b, CONST_STR_LEN("req/s</td></tr>\n"));
410 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Traffic</td><td class=\"string\">"));
411 avg = p->abs_traffic_out / (srv->cur_ts - srv->startup_ts);
413 mod_status_get_multiplier(&avg, &multiplier, 1024);
415 snprintf(buf, sizeof(buf), "%.2f", avg);
416 buffer_append_string(b, buf);
417 buffer_append_string_len(b, CONST_STR_LEN(" "));
418 if (multiplier) buffer_append_string_len(b, &multiplier, 1);
419 buffer_append_string_len(b, CONST_STR_LEN("byte/s</td></tr>\n"));
423 buffer_append_string_len(b, CONST_STR_LEN("<tr><th colspan=\"2\">average (5s sliding average)</th></tr>\n"));
424 for (j = 0, avg = 0; j < 5; j++) {
425 avg += p->mod_5s_requests[j];
428 avg /= 5;
430 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Requests</td><td class=\"string\">"));
432 mod_status_get_multiplier(&avg, &multiplier, 1000);
434 buffer_append_int(b, avg);
435 buffer_append_string_len(b, CONST_STR_LEN(" "));
436 if (multiplier) buffer_append_string_len(b, &multiplier, 1);
438 buffer_append_string_len(b, CONST_STR_LEN("req/s</td></tr>\n"));
440 for (j = 0, avg = 0; j < 5; j++) {
441 avg += p->mod_5s_traffic_out[j];
444 avg /= 5;
446 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Traffic</td><td class=\"string\">"));
448 mod_status_get_multiplier(&avg, &multiplier, 1024);
450 snprintf(buf, sizeof(buf), "%.2f", avg);
451 buffer_append_string(b, buf);
452 buffer_append_string_len(b, CONST_STR_LEN(" "));
453 if (multiplier) buffer_append_string_len(b, &multiplier, 1);
454 buffer_append_string_len(b, CONST_STR_LEN("byte/s</td></tr>\n"));
456 buffer_append_string_len(b, CONST_STR_LEN("</table>\n"));
458 buffer_append_string_len(b, CONST_STR_LEN("<hr />\n<pre>\n"));
460 buffer_append_string_len(b, CONST_STR_LEN("<b>"));
461 buffer_append_int(b, srv->conns->used);
462 buffer_append_string_len(b, CONST_STR_LEN(" connections</b>\n"));
464 for (j = 0; j < srv->conns->used; j++) {
465 connection *c = srv->conns->ptr[j];
466 const char *state;
468 if (CON_STATE_READ == c->state && !buffer_string_is_empty(c->request.orig_uri)) {
469 state = "k";
470 ++cstates[CON_STATE_CLOSE+2];
471 } else {
472 state = connection_get_short_state(c->state);
473 ++cstates[(c->state <= CON_STATE_CLOSE ? c->state : CON_STATE_CLOSE+1)];
476 buffer_append_string_len(b, state, 1);
478 if (((j + 1) % 50) == 0) {
479 buffer_append_string_len(b, CONST_STR_LEN("\n"));
482 buffer_append_string_len(b, CONST_STR_LEN("\n\n<table>\n"));
483 buffer_append_string_len(b, CONST_STR_LEN("<tr><td style=\"text-align:right\">"));
484 buffer_append_int(b, cstates[CON_STATE_CLOSE+2]);
485 buffer_append_string_len(b, CONST_STR_LEN("<td>&nbsp;&nbsp;k = keep-alive</td></tr>\n"));
486 for (j = 0; j < CON_STATE_CLOSE+2; ++j) {
487 /*(skip "unknown" state if there are none; there should not be any unknown)*/
488 if (0 == cstates[j] && j == CON_STATE_CLOSE+1) continue;
489 buffer_append_string_len(b, CONST_STR_LEN("<tr><td style=\"text-align:right\">"));
490 buffer_append_int(b, cstates[j]);
491 buffer_append_string_len(b, CONST_STR_LEN("</td><td>&nbsp;&nbsp;"));
492 buffer_append_string_len(b, connection_get_short_state(j), 1);
493 buffer_append_string_len(b, CONST_STR_LEN(" = "));
494 buffer_append_string(b, connection_get_state(j));
495 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
497 buffer_append_string_len(b, CONST_STR_LEN("</table>"));
499 buffer_append_string_len(b, CONST_STR_LEN("\n</pre><hr />\n<h2>Connections</h2>\n"));
501 buffer_append_string_len(b, CONST_STR_LEN("<table summary=\"status\" class=\"status\">\n"));
502 buffer_append_string_len(b, CONST_STR_LEN("<tr>"));
503 mod_status_header_append_sort(b, p_d, "Client IP");
504 mod_status_header_append_sort(b, p_d, "Read");
505 mod_status_header_append_sort(b, p_d, "Written");
506 mod_status_header_append_sort(b, p_d, "State");
507 mod_status_header_append_sort(b, p_d, "Time");
508 mod_status_header_append_sort(b, p_d, "Host");
509 mod_status_header_append_sort(b, p_d, "URI");
510 mod_status_header_append_sort(b, p_d, "File");
511 buffer_append_string_len(b, CONST_STR_LEN("</tr>\n"));
513 for (j = 0; j < srv->conns->used; j++) {
514 connection *c = srv->conns->ptr[j];
516 buffer_append_string_len(b, CONST_STR_LEN("<tr><td class=\"string\">"));
518 buffer_append_string(b, inet_ntop_cache_get_ip(srv, &(c->dst_addr)));
520 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">"));
522 if (c->request.content_length) {
523 buffer_append_int(b, c->request_content_queue->bytes_in);
524 buffer_append_string_len(b, CONST_STR_LEN("/"));
525 buffer_append_int(b, c->request.content_length);
526 } else {
527 buffer_append_string_len(b, CONST_STR_LEN("0/0"));
530 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">"));
532 buffer_append_int(b, c->write_queue->bytes_out);
533 buffer_append_string_len(b, CONST_STR_LEN("/"));
534 buffer_append_int(b, c->write_queue->bytes_out + chunkqueue_length(c->write_queue));
536 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
538 if (CON_STATE_READ == c->state && !buffer_string_is_empty(c->request.orig_uri)) {
539 buffer_append_string_len(b, CONST_STR_LEN("keep-alive"));
540 } else {
541 buffer_append_string(b, connection_get_state(c->state));
544 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">"));
546 buffer_append_int(b, srv->cur_ts - c->request_start);
548 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
550 if (buffer_string_is_empty(c->server_name)) {
551 buffer_append_string_buffer(b, c->uri.authority);
553 else {
554 buffer_append_string_buffer(b, c->server_name);
557 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
559 if (!buffer_string_is_empty(c->uri.path)) {
560 buffer_append_string_encoded(b, CONST_BUF_LEN(c->uri.path), ENCODING_HTML);
563 if (!buffer_string_is_empty(c->uri.query)) {
564 buffer_append_string_len(b, CONST_STR_LEN("?"));
565 buffer_append_string_encoded(b, CONST_BUF_LEN(c->uri.query), ENCODING_HTML);
568 if (!buffer_string_is_empty(c->request.orig_uri)) {
569 buffer_append_string_len(b, CONST_STR_LEN(" ("));
570 buffer_append_string_encoded(b, CONST_BUF_LEN(c->request.orig_uri), ENCODING_HTML);
571 buffer_append_string_len(b, CONST_STR_LEN(")"));
573 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
575 buffer_append_string_buffer(b, c->physical.path);
577 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
581 buffer_append_string_len(b, CONST_STR_LEN(
582 "</table>\n"));
585 buffer_append_string_len(b, CONST_STR_LEN(
586 " </body>\n"
587 "</html>\n"
590 chunkqueue_append_buffer(con->write_queue, b);
591 buffer_free(b);
593 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
595 return 0;
599 static handler_t mod_status_handle_server_status_text(server *srv, connection *con, void *p_d) {
600 plugin_data *p = p_d;
601 buffer *b = buffer_init();
602 double avg;
603 time_t ts;
604 char buf[32];
605 unsigned int k;
606 unsigned int l;
608 /* output total number of requests */
609 buffer_append_string_len(b, CONST_STR_LEN("Total Accesses: "));
610 avg = p->abs_requests;
611 snprintf(buf, sizeof(buf) - 1, "%.0f", avg);
612 buffer_append_string(b, buf);
613 buffer_append_string_len(b, CONST_STR_LEN("\n"));
615 /* output total traffic out in kbytes */
616 buffer_append_string_len(b, CONST_STR_LEN("Total kBytes: "));
617 avg = p->abs_traffic_out / 1024;
618 snprintf(buf, sizeof(buf) - 1, "%.0f", avg);
619 buffer_append_string(b, buf);
620 buffer_append_string_len(b, CONST_STR_LEN("\n"));
622 /* output uptime */
623 buffer_append_string_len(b, CONST_STR_LEN("Uptime: "));
624 ts = srv->cur_ts - srv->startup_ts;
625 buffer_append_int(b, ts);
626 buffer_append_string_len(b, CONST_STR_LEN("\n"));
628 /* output busy servers */
629 buffer_append_string_len(b, CONST_STR_LEN("BusyServers: "));
630 buffer_append_int(b, srv->conns->used);
631 buffer_append_string_len(b, CONST_STR_LEN("\n"));
633 buffer_append_string_len(b, CONST_STR_LEN("IdleServers: "));
634 buffer_append_int(b, srv->conns->size - srv->conns->used);
635 buffer_append_string_len(b, CONST_STR_LEN("\n"));
637 /* output scoreboard */
638 buffer_append_string_len(b, CONST_STR_LEN("Scoreboard: "));
639 for (k = 0; k < srv->conns->used; k++) {
640 connection *c = srv->conns->ptr[k];
641 const char *state = connection_get_short_state(c->state);
642 buffer_append_string_len(b, state, 1);
644 for (l = 0; l < srv->conns->size - srv->conns->used; l++) {
645 buffer_append_string_len(b, CONST_STR_LEN("_"));
647 buffer_append_string_len(b, CONST_STR_LEN("\n"));
649 chunkqueue_append_buffer(con->write_queue, b);
650 buffer_free(b);
652 /* set text/plain output */
653 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain"));
655 return 0;
659 static handler_t mod_status_handle_server_status_json(server *srv, connection *con, void *p_d) {
660 plugin_data *p = p_d;
661 buffer *b = buffer_init();
662 double avg;
663 time_t ts;
664 char buf[32];
665 size_t j;
666 unsigned int jsonp = 0;
668 if (buffer_string_length(con->uri.query) >= sizeof("jsonp=")-1
669 && 0 == memcmp(con->uri.query->ptr, CONST_STR_LEN("jsonp="))) {
670 /* not a full parse of query string for multiple parameters,
671 * not URL-decoding param and not XML-encoding (XSS protection),
672 * so simply ensure that json function name isalnum() or '_' */
673 const char *f = con->uri.query->ptr + sizeof("jsonp=")-1;
674 int len = 0;
675 while (light_isalnum(f[len]) || f[len] == '_') ++len;
676 if (0 != len && light_isalpha(f[0]) && f[len] == '\0') {
677 buffer_append_string_len(b, f, len);
678 buffer_append_string_len(b, CONST_STR_LEN("("));
679 jsonp = 1;
683 /* output total number of requests */
684 buffer_append_string_len(b, CONST_STR_LEN("{\n\t\"RequestsTotal\": "));
685 avg = p->abs_requests;
686 snprintf(buf, sizeof(buf) - 1, "%.0f", avg);
687 buffer_append_string(b, buf);
688 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
690 /* output total traffic out in kbytes */
691 buffer_append_string_len(b, CONST_STR_LEN("\t\"TrafficTotal\": "));
692 avg = p->abs_traffic_out / 1024;
693 snprintf(buf, sizeof(buf) - 1, "%.0f", avg);
694 buffer_append_string(b, buf);
695 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
697 /* output uptime */
698 buffer_append_string_len(b, CONST_STR_LEN("\t\"Uptime\": "));
699 ts = srv->cur_ts - srv->startup_ts;
700 buffer_append_int(b, ts);
701 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
703 /* output busy servers */
704 buffer_append_string_len(b, CONST_STR_LEN("\t\"BusyServers\": "));
705 buffer_append_int(b, srv->conns->used);
706 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
708 buffer_append_string_len(b, CONST_STR_LEN("\t\"IdleServers\": "));
709 buffer_append_int(b, srv->conns->size - srv->conns->used);
710 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
712 for (j = 0, avg = 0; j < 5; j++) {
713 avg += p->mod_5s_requests[j];
716 avg /= 5;
718 buffer_append_string_len(b, CONST_STR_LEN("\t\"RequestAverage5s\":"));
719 buffer_append_int(b, avg);
720 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
722 for (j = 0, avg = 0; j < 5; j++) {
723 avg += p->mod_5s_traffic_out[j];
726 avg /= 5;
728 buffer_append_string_len(b, CONST_STR_LEN("\t\"TrafficAverage5s\":"));
729 buffer_append_int(b, avg / 1024); /* kbps */
730 buffer_append_string_len(b, CONST_STR_LEN("\n}"));
732 if (jsonp) buffer_append_string_len(b, CONST_STR_LEN(");"));
734 chunkqueue_append_buffer(con->write_queue, b);
735 buffer_free(b);
737 /* set text/plain output */
738 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/javascript"));
740 return 0;
744 static handler_t mod_status_handle_server_statistics(server *srv, connection *con, void *p_d) {
745 buffer *b;
746 size_t i;
747 array *st = srv->status;
748 UNUSED(p_d);
750 if (0 == st->used) {
751 /* we have nothing to send */
752 con->http_status = 204;
753 con->file_finished = 1;
755 return HANDLER_FINISHED;
758 b = buffer_init();
759 for (i = 0; i < st->used; i++) {
760 size_t ndx = st->sorted[i];
762 buffer_append_string_buffer(b, st->data[ndx]->key);
763 buffer_append_string_len(b, CONST_STR_LEN(": "));
764 buffer_append_int(b, ((data_integer *)(st->data[ndx]))->value);
765 buffer_append_string_len(b, CONST_STR_LEN("\n"));
768 chunkqueue_append_buffer(con->write_queue, b);
769 buffer_free(b);
771 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain"));
773 con->http_status = 200;
774 con->file_finished = 1;
776 return HANDLER_FINISHED;
780 static handler_t mod_status_handle_server_status(server *srv, connection *con, void *p_d) {
782 if (buffer_is_equal_string(con->uri.query, CONST_STR_LEN("auto"))) {
783 mod_status_handle_server_status_text(srv, con, p_d);
784 } else if (buffer_string_length(con->uri.query) >= sizeof("json")-1
785 && 0 == memcmp(con->uri.query->ptr, CONST_STR_LEN("json"))) {
786 mod_status_handle_server_status_json(srv, con, p_d);
787 } else {
788 mod_status_handle_server_status_html(srv, con, p_d);
791 con->http_status = 200;
792 con->file_finished = 1;
794 return HANDLER_FINISHED;
798 static handler_t mod_status_handle_server_config(server *srv, connection *con, void *p_d) {
799 plugin_data *p = p_d;
800 buffer *b = buffer_init();
801 buffer *m = p->module_list;
802 size_t i;
804 struct ev_map { fdevent_handler_t et; const char *name; } event_handlers[] =
806 /* - epoll is most reliable
807 * - select works everywhere
809 #ifdef USE_LINUX_EPOLL
810 { FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll" },
811 #endif
812 #ifdef USE_POLL
813 { FDEVENT_HANDLER_POLL, "poll" },
814 #endif
815 #ifdef USE_SELECT
816 { FDEVENT_HANDLER_SELECT, "select" },
817 #endif
818 #ifdef USE_LIBEV
819 { FDEVENT_HANDLER_LIBEV, "libev" },
820 #endif
821 #ifdef USE_SOLARIS_DEVPOLL
822 { FDEVENT_HANDLER_SOLARIS_DEVPOLL,"solaris-devpoll" },
823 #endif
824 #ifdef USE_SOLARIS_PORT
825 { FDEVENT_HANDLER_SOLARIS_PORT, "solaris-eventports" },
826 #endif
827 #ifdef USE_FREEBSD_KQUEUE
828 { FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue" },
829 #endif
830 { FDEVENT_HANDLER_UNSET, NULL }
833 buffer_copy_string_len(b, CONST_STR_LEN(
834 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
835 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
836 " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
837 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
838 " <head>\n"
839 " <title>Status</title>\n"
840 " </head>\n"
841 " <body>\n"
842 " <h1>"));
843 buffer_append_string_buffer(b, con->conf.server_tag);
844 buffer_append_string_len(b, CONST_STR_LEN(
845 "</h1>\n"
846 " <table summary=\"status\" border=\"1\">\n"));
848 mod_status_header_append(b, "Server-Features");
849 #ifdef HAVE_PCRE_H
850 mod_status_row_append(b, "RegEx Conditionals", "enabled");
851 #else
852 mod_status_row_append(b, "RegEx Conditionals", "disabled - pcre missing");
853 #endif
854 mod_status_header_append(b, "Network Engine");
856 for (i = 0; event_handlers[i].name; i++) {
857 if (event_handlers[i].et == srv->event_handler) {
858 mod_status_row_append(b, "fd-Event-Handler", event_handlers[i].name);
859 break;
863 mod_status_header_append(b, "Config-File-Settings");
865 for (i = 0; i < srv->plugins.used; i++) {
866 plugin **ps = srv->plugins.ptr;
868 plugin *pl = ps[i];
870 if (i == 0) {
871 buffer_copy_buffer(m, pl->name);
872 } else {
873 buffer_append_string_len(m, CONST_STR_LEN("<br />"));
874 buffer_append_string_buffer(m, pl->name);
878 mod_status_row_append(b, "Loaded Modules", m->ptr);
880 buffer_append_string_len(b, CONST_STR_LEN(" </table>\n"));
882 buffer_append_string_len(b, CONST_STR_LEN(
883 " </body>\n"
884 "</html>\n"
887 chunkqueue_append_buffer(con->write_queue, b);
888 buffer_free(b);
890 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
892 con->http_status = 200;
893 con->file_finished = 1;
895 return HANDLER_FINISHED;
898 #define PATCH(x) \
899 p->conf.x = s->x;
900 static int mod_status_patch_connection(server *srv, connection *con, plugin_data *p) {
901 size_t i, j;
902 plugin_config *s = p->config_storage[0];
904 PATCH(status_url);
905 PATCH(config_url);
906 PATCH(sort);
907 PATCH(statistics_url);
909 /* skip the first, the global context */
910 for (i = 1; i < srv->config_context->used; i++) {
911 data_config *dc = (data_config *)srv->config_context->data[i];
912 s = p->config_storage[i];
914 /* condition didn't match */
915 if (!config_check_cond(srv, con, dc)) continue;
917 /* merge config */
918 for (j = 0; j < dc->value->used; j++) {
919 data_unset *du = dc->value->data[j];
921 if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.status-url"))) {
922 PATCH(status_url);
923 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.config-url"))) {
924 PATCH(config_url);
925 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.enable-sort"))) {
926 PATCH(sort);
927 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.statistics-url"))) {
928 PATCH(statistics_url);
933 return 0;
936 static handler_t mod_status_handler(server *srv, connection *con, void *p_d) {
937 plugin_data *p = p_d;
939 if (con->mode != DIRECT) return HANDLER_GO_ON;
941 mod_status_patch_connection(srv, con, p);
943 if (!buffer_string_is_empty(p->conf.status_url) &&
944 buffer_is_equal(p->conf.status_url, con->uri.path)) {
945 return mod_status_handle_server_status(srv, con, p_d);
946 } else if (!buffer_string_is_empty(p->conf.config_url) &&
947 buffer_is_equal(p->conf.config_url, con->uri.path)) {
948 return mod_status_handle_server_config(srv, con, p_d);
949 } else if (!buffer_string_is_empty(p->conf.statistics_url) &&
950 buffer_is_equal(p->conf.statistics_url, con->uri.path)) {
951 return mod_status_handle_server_statistics(srv, con, p_d);
954 return HANDLER_GO_ON;
957 TRIGGER_FUNC(mod_status_trigger) {
958 plugin_data *p = p_d;
959 size_t i;
961 /* check all connections */
962 for (i = 0; i < srv->conns->used; i++) {
963 connection *c = srv->conns->ptr[i];
965 p->bytes_written += c->bytes_written_cur_second;
968 /* a sliding average */
969 p->mod_5s_traffic_out[p->mod_5s_ndx] = p->bytes_written;
970 p->mod_5s_requests [p->mod_5s_ndx] = p->requests;
972 p->mod_5s_ndx = (p->mod_5s_ndx+1) % 5;
974 p->abs_traffic_out += p->bytes_written;
975 p->rel_traffic_out += p->bytes_written;
977 p->bytes_written = 0;
979 /* reset storage - second */
980 p->traffic_out = 0;
981 p->requests = 0;
983 return HANDLER_GO_ON;
986 REQUESTDONE_FUNC(mod_status_account) {
987 plugin_data *p = p_d;
989 UNUSED(srv);
991 p->requests++;
992 p->rel_requests++;
993 p->abs_requests++;
995 p->bytes_written += con->bytes_written_cur_second;
997 return HANDLER_GO_ON;
1000 int mod_status_plugin_init(plugin *p);
1001 int mod_status_plugin_init(plugin *p) {
1002 p->version = LIGHTTPD_VERSION_ID;
1003 p->name = buffer_init_string("status");
1005 p->init = mod_status_init;
1006 p->cleanup = mod_status_free;
1007 p->set_defaults= mod_status_set_defaults;
1009 p->handle_uri_clean = mod_status_handler;
1010 p->handle_trigger = mod_status_trigger;
1011 p->handle_request_done = mod_status_account;
1013 p->data = NULL;
1015 return 0;