[mod_status] add JSON output option (fixed #2432)
[lighttpd.git] / src / mod_status.c
blob499405bf1458b2a35665365f6aac6c8128745a63
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 #include "version.h"
25 typedef struct {
26 buffer *config_url;
27 buffer *status_url;
28 buffer *statistics_url;
30 int sort;
31 } plugin_config;
33 typedef struct {
34 PLUGIN_DATA;
36 double traffic_out;
37 double requests;
39 double mod_5s_traffic_out[5];
40 double mod_5s_requests[5];
41 size_t mod_5s_ndx;
43 double rel_traffic_out;
44 double rel_requests;
46 double abs_traffic_out;
47 double abs_requests;
49 double bytes_written;
51 buffer *module_list;
53 plugin_config **config_storage;
55 plugin_config conf;
56 } plugin_data;
58 INIT_FUNC(mod_status_init) {
59 plugin_data *p;
60 size_t i;
62 p = calloc(1, sizeof(*p));
64 p->traffic_out = p->requests = 0;
65 p->rel_traffic_out = p->rel_requests = 0;
66 p->abs_traffic_out = p->abs_requests = 0;
67 p->bytes_written = 0;
68 p->module_list = buffer_init();
70 for (i = 0; i < 5; i++) {
71 p->mod_5s_traffic_out[i] = p->mod_5s_requests[i] = 0;
74 return p;
77 FREE_FUNC(mod_status_free) {
78 plugin_data *p = p_d;
80 UNUSED(srv);
82 if (!p) return HANDLER_GO_ON;
84 buffer_free(p->module_list);
86 if (p->config_storage) {
87 size_t i;
88 for (i = 0; i < srv->config_context->used; i++) {
89 plugin_config *s = p->config_storage[i];
91 buffer_free(s->status_url);
92 buffer_free(s->statistics_url);
93 buffer_free(s->config_url);
95 free(s);
97 free(p->config_storage);
101 free(p);
103 return HANDLER_GO_ON;
106 SETDEFAULTS_FUNC(mod_status_set_defaults) {
107 plugin_data *p = p_d;
108 size_t i;
110 config_values_t cv[] = {
111 { "status.status-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
112 { "status.config-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
113 { "status.enable-sort", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
114 { "status.statistics-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
115 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
118 if (!p) return HANDLER_ERROR;
120 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
122 for (i = 0; i < srv->config_context->used; i++) {
123 data_config const* config = (data_config const*)srv->config_context->data[i];
124 plugin_config *s;
126 s = calloc(1, sizeof(plugin_config));
127 s->config_url = buffer_init();
128 s->status_url = buffer_init();
129 s->sort = 1;
130 s->statistics_url = buffer_init();
132 cv[0].destination = s->status_url;
133 cv[1].destination = s->config_url;
134 cv[2].destination = &(s->sort);
135 cv[3].destination = s->statistics_url;
137 p->config_storage[i] = s;
139 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
140 return HANDLER_ERROR;
144 return HANDLER_GO_ON;
149 static int mod_status_row_append(buffer *b, const char *key, const char *value) {
150 buffer_append_string_len(b, CONST_STR_LEN(" <tr>\n"));
151 buffer_append_string_len(b, CONST_STR_LEN(" <td><b>"));
152 buffer_append_string(b, key);
153 buffer_append_string_len(b, CONST_STR_LEN("</b></td>\n"));
154 buffer_append_string_len(b, CONST_STR_LEN(" <td>"));
155 buffer_append_string(b, value);
156 buffer_append_string_len(b, CONST_STR_LEN("</td>\n"));
157 buffer_append_string_len(b, CONST_STR_LEN(" </tr>\n"));
159 return 0;
162 static int mod_status_header_append(buffer *b, const char *key) {
163 buffer_append_string_len(b, CONST_STR_LEN(" <tr>\n"));
164 buffer_append_string_len(b, CONST_STR_LEN(" <th colspan=\"2\">"));
165 buffer_append_string(b, key);
166 buffer_append_string_len(b, CONST_STR_LEN("</th>\n"));
167 buffer_append_string_len(b, CONST_STR_LEN(" </tr>\n"));
169 return 0;
172 static int mod_status_header_append_sort(buffer *b, void *p_d, const char* key) {
173 plugin_data *p = p_d;
175 if (p->conf.sort) {
176 buffer_append_string_len(b, CONST_STR_LEN("<th class=\"status\"><a href=\"#\" class=\"sortheader\" onclick=\"resort(this);return false;\">"));
177 buffer_append_string(b, key);
178 buffer_append_string_len(b, CONST_STR_LEN("<span class=\"sortarrow\">:</span></a></th>\n"));
179 } else {
180 buffer_append_string_len(b, CONST_STR_LEN("<th class=\"status\">"));
181 buffer_append_string(b, key);
182 buffer_append_string_len(b, CONST_STR_LEN("</th>\n"));
185 return 0;
188 static int mod_status_get_multiplier(double *avg, char *multiplier, int size) {
189 *multiplier = ' ';
191 if (*avg > size) { *avg /= size; *multiplier = 'k'; }
192 if (*avg > size) { *avg /= size; *multiplier = 'M'; }
193 if (*avg > size) { *avg /= size; *multiplier = 'G'; }
194 if (*avg > size) { *avg /= size; *multiplier = 'T'; }
195 if (*avg > size) { *avg /= size; *multiplier = 'P'; }
196 if (*avg > size) { *avg /= size; *multiplier = 'E'; }
197 if (*avg > size) { *avg /= size; *multiplier = 'Z'; }
198 if (*avg > size) { *avg /= size; *multiplier = 'Y'; }
200 return 0;
203 static handler_t mod_status_handle_server_status_html(server *srv, connection *con, void *p_d) {
204 plugin_data *p = p_d;
205 buffer *b = buffer_init();
206 size_t j;
207 double avg;
208 char multiplier = '\0';
209 char buf[32];
210 time_t ts;
212 int days, hours, mins, seconds;
214 /*(CON_STATE_CLOSE must be last state in enum connection_state_t)*/
215 int cstates[CON_STATE_CLOSE+3];
216 memset(cstates, 0, sizeof(cstates));
218 buffer_copy_string_len(b, CONST_STR_LEN(
219 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
220 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
221 " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
222 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
223 " <head>\n"
224 " <title>Status</title>\n"
226 " <style type=\"text/css\">\n"
227 " table.status { border: black solid thin; }\n"
228 " td { white-space: nowrap; }\n"
229 " td.int { background-color: #f0f0f0; text-align: right }\n"
230 " td.string { background-color: #f0f0f0; text-align: left }\n"
231 " th.status { background-color: black; color: white; font-weight: bold; }\n"
232 " a.sortheader { background-color: black; color: white; font-weight: bold; text-decoration: none; display: block; }\n"
233 " span.sortarrow { color: white; text-decoration: none; }\n"
234 " </style>\n"));
236 if (!buffer_string_is_empty(con->uri.query) && 0 == memcmp(con->uri.query->ptr, CONST_STR_LEN("refresh="))) {
237 /* Note: Refresh is an historical, but non-standard HTTP header
238 * References (meta http-equiv="refresh" use is deprecated):
239 * https://www.w3.org/TR/WCAG10-HTML-TECHS/#meta-element
240 * https://www.w3.org/TR/WCAG10-CORE-TECHS/#auto-page-refresh
241 * https://www.w3.org/QA/Tips/reback
243 const long refresh = strtol(con->uri.query->ptr+sizeof("refresh=")-1, NULL, 10);
244 if (refresh > 0) {
245 buffer_append_string_len(b, CONST_STR_LEN("<meta http-equiv=\"refresh\" content=\""));
246 buffer_append_int(b, refresh < 604800 ? refresh : 604800);
247 buffer_append_string_len(b, CONST_STR_LEN("\">\n"));
251 if (p->conf.sort) {
252 buffer_append_string_len(b, CONST_STR_LEN(
253 "<script type=\"text/javascript\">\n"
254 "// <!--\n"
255 "var sort_column;\n"
256 "var prev_span = null;\n"
258 "function get_inner_text(el) {\n"
259 " if((typeof el == 'string')||(typeof el == 'undefined'))\n"
260 " return el;\n"
261 " if(el.innerText)\n"
262 " return el.innerText;\n"
263 " else {\n"
264 " var str = \"\";\n"
265 " var cs = el.childNodes;\n"
266 " var l = cs.length;\n"
267 " for (i=0;i<l;i++) {\n"
268 " if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n"
269 " else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n"
270 " }\n"
271 " }\n"
272 " return str;\n"
273 "}\n"
275 "function sortfn(a,b) {\n"
276 " var at = get_inner_text(a.cells[sort_column]);\n"
277 " var bt = get_inner_text(b.cells[sort_column]);\n"
278 " if (a.cells[sort_column].className == 'int') {\n"
279 " return parseInt(at)-parseInt(bt);\n"
280 " } else {\n"
281 " aa = at.toLowerCase();\n"
282 " bb = bt.toLowerCase();\n"
283 " if (aa==bb) return 0;\n"
284 " else if (aa<bb) return -1;\n"
285 " else return 1;\n"
286 " }\n"
287 "}\n"
289 "function resort(lnk) {\n"
290 " var span = lnk.childNodes[1];\n"
291 " var table = lnk.parentNode.parentNode.parentNode.parentNode;\n"
292 " var rows = new Array();\n"
293 " for (j=1;j<table.rows.length;j++)\n"
294 " rows[j-1] = table.rows[j];\n"
295 " sort_column = lnk.parentNode.cellIndex;\n"
296 " rows.sort(sortfn);\n"
298 " if (prev_span != null) prev_span.innerHTML = '';\n"
299 " if (span.getAttribute('sortdir')=='down') {\n"
300 " span.innerHTML = '&uarr;';\n"
301 " span.setAttribute('sortdir','up');\n"
302 " rows.reverse();\n"
303 " } else {\n"
304 " span.innerHTML = '&darr;';\n"
305 " span.setAttribute('sortdir','down');\n"
306 " }\n"
307 " for (i=0;i<rows.length;i++)\n"
308 " table.tBodies[0].appendChild(rows[i]);\n"
309 " prev_span = span;\n"
310 "}\n"
311 "// -->\n"
312 "</script>\n"));
315 buffer_append_string_len(b, CONST_STR_LEN(
316 " </head>\n"
317 " <body>\n"));
321 /* connection listing */
322 buffer_append_string_len(b, CONST_STR_LEN("<h1>Server-Status (" PACKAGE_NAME " " PACKAGE_VERSION ")</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>" PACKAGE_DESC "</h1>\n"
843 " <table summary=\"status\" border=\"1\">\n"));
845 mod_status_header_append(b, "Server-Features");
846 #ifdef HAVE_PCRE_H
847 mod_status_row_append(b, "RegEx Conditionals", "enabled");
848 #else
849 mod_status_row_append(b, "RegEx Conditionals", "disabled - pcre missing");
850 #endif
851 mod_status_header_append(b, "Network Engine");
853 for (i = 0; event_handlers[i].name; i++) {
854 if (event_handlers[i].et == srv->event_handler) {
855 mod_status_row_append(b, "fd-Event-Handler", event_handlers[i].name);
856 break;
860 mod_status_header_append(b, "Config-File-Settings");
862 for (i = 0; i < srv->plugins.used; i++) {
863 plugin **ps = srv->plugins.ptr;
865 plugin *pl = ps[i];
867 if (i == 0) {
868 buffer_copy_buffer(m, pl->name);
869 } else {
870 buffer_append_string_len(m, CONST_STR_LEN("<br />"));
871 buffer_append_string_buffer(m, pl->name);
875 mod_status_row_append(b, "Loaded Modules", m->ptr);
877 buffer_append_string_len(b, CONST_STR_LEN(" </table>\n"));
879 buffer_append_string_len(b, CONST_STR_LEN(
880 " </body>\n"
881 "</html>\n"
884 chunkqueue_append_buffer(con->write_queue, b);
885 buffer_free(b);
887 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
889 con->http_status = 200;
890 con->file_finished = 1;
892 return HANDLER_FINISHED;
895 #define PATCH(x) \
896 p->conf.x = s->x;
897 static int mod_status_patch_connection(server *srv, connection *con, plugin_data *p) {
898 size_t i, j;
899 plugin_config *s = p->config_storage[0];
901 PATCH(status_url);
902 PATCH(config_url);
903 PATCH(sort);
904 PATCH(statistics_url);
906 /* skip the first, the global context */
907 for (i = 1; i < srv->config_context->used; i++) {
908 data_config *dc = (data_config *)srv->config_context->data[i];
909 s = p->config_storage[i];
911 /* condition didn't match */
912 if (!config_check_cond(srv, con, dc)) continue;
914 /* merge config */
915 for (j = 0; j < dc->value->used; j++) {
916 data_unset *du = dc->value->data[j];
918 if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.status-url"))) {
919 PATCH(status_url);
920 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.config-url"))) {
921 PATCH(config_url);
922 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.enable-sort"))) {
923 PATCH(sort);
924 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.statistics-url"))) {
925 PATCH(statistics_url);
930 return 0;
933 static handler_t mod_status_handler(server *srv, connection *con, void *p_d) {
934 plugin_data *p = p_d;
936 if (con->mode != DIRECT) return HANDLER_GO_ON;
938 mod_status_patch_connection(srv, con, p);
940 if (!buffer_string_is_empty(p->conf.status_url) &&
941 buffer_is_equal(p->conf.status_url, con->uri.path)) {
942 return mod_status_handle_server_status(srv, con, p_d);
943 } else if (!buffer_string_is_empty(p->conf.config_url) &&
944 buffer_is_equal(p->conf.config_url, con->uri.path)) {
945 return mod_status_handle_server_config(srv, con, p_d);
946 } else if (!buffer_string_is_empty(p->conf.statistics_url) &&
947 buffer_is_equal(p->conf.statistics_url, con->uri.path)) {
948 return mod_status_handle_server_statistics(srv, con, p_d);
951 return HANDLER_GO_ON;
954 TRIGGER_FUNC(mod_status_trigger) {
955 plugin_data *p = p_d;
956 size_t i;
958 /* check all connections */
959 for (i = 0; i < srv->conns->used; i++) {
960 connection *c = srv->conns->ptr[i];
962 p->bytes_written += c->bytes_written_cur_second;
965 /* a sliding average */
966 p->mod_5s_traffic_out[p->mod_5s_ndx] = p->bytes_written;
967 p->mod_5s_requests [p->mod_5s_ndx] = p->requests;
969 p->mod_5s_ndx = (p->mod_5s_ndx+1) % 5;
971 p->abs_traffic_out += p->bytes_written;
972 p->rel_traffic_out += p->bytes_written;
974 p->bytes_written = 0;
976 /* reset storage - second */
977 p->traffic_out = 0;
978 p->requests = 0;
980 return HANDLER_GO_ON;
983 REQUESTDONE_FUNC(mod_status_account) {
984 plugin_data *p = p_d;
986 UNUSED(srv);
988 p->requests++;
989 p->rel_requests++;
990 p->abs_requests++;
992 p->bytes_written += con->bytes_written_cur_second;
994 return HANDLER_GO_ON;
997 int mod_status_plugin_init(plugin *p);
998 int mod_status_plugin_init(plugin *p) {
999 p->version = LIGHTTPD_VERSION_ID;
1000 p->name = buffer_init_string("status");
1002 p->init = mod_status_init;
1003 p->cleanup = mod_status_free;
1004 p->set_defaults= mod_status_set_defaults;
1006 p->handle_uri_clean = mod_status_handler;
1007 p->handle_trigger = mod_status_trigger;
1008 p->handle_request_done = mod_status_account;
1010 p->data = NULL;
1012 return 0;