1 #include "first.h" 2 3 #include "base.h" 4 #include "fdevent.h" 5 #include "h2.h" 6 #include "http_chunk.h" 7 #include "http_header.h" 8 #include "log.h" 9 10 #include "plugin.h" 11 12 #include <sys/types.h> 13 #include "sys-time.h" 14 15 #include <fcntl.h> 16 #include <stdlib.h> 17 #include <string.h> 18 #include <stdio.h> 19 20 typedef struct { 21 const buffer *config_url; 22 const buffer *status_url; 23 const buffer *statistics_url; 24 25 int sort; 26 } plugin_config; 27 28 typedef struct { 29 PLUGIN_DATA; 30 plugin_config defaults; 31 plugin_config conf; 32 33 double traffic_out; 34 double requests; 35 36 double mod_5s_traffic_out[5]; 37 double mod_5s_requests[5]; 38 size_t mod_5s_ndx; 39 40 double rel_traffic_out; 41 double rel_requests; 42 43 double abs_traffic_out; 44 double abs_requests; 45 46 double bytes_written; 47 } plugin_data; 48 49 INIT_FUNC(mod_status_init) { 50 return calloc(1, sizeof(plugin_data)); 51 } 52 53 static void mod_status_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 54 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 55 case 0: /* status.status-url */ 56 pconf->status_url = cpv->v.b; 57 break; 58 case 1: /* status.config-url */ 59 pconf->config_url = cpv->v.b; 60 break; 61 case 2: /* status.statistics-url */ 62 pconf->statistics_url = cpv->v.b; 63 break; 64 case 3: /* status.enable-sort */ 65 pconf->sort = (int)cpv->v.u; 66 break; 67 default:/* should not happen */ 68 return; 69 } 70 } 71 72 static void mod_status_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 73 do { 74 mod_status_merge_config_cpv(pconf, cpv); 75 } while ((++cpv)->k_id != -1); 76 } 77 78 static void mod_status_patch_config(request_st * const r, plugin_data * const p) { 79 p->conf = p->defaults; /* copy small struct instead of memcpy() */ 80 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/ 81 for (int i = 1, used = p->nconfig; i < used; ++i) { 82 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 83 mod_status_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]); 84 } 85 } 86 87 SETDEFAULTS_FUNC(mod_status_set_defaults) { 88 static const config_plugin_keys_t cpk[] = { 89 { CONST_STR_LEN("status.status-url"), 90 T_CONFIG_STRING, 91 T_CONFIG_SCOPE_CONNECTION } 92 ,{ CONST_STR_LEN("status.config-url"), 93 T_CONFIG_STRING, 94 T_CONFIG_SCOPE_CONNECTION } 95 ,{ CONST_STR_LEN("status.statistics-url"), 96 T_CONFIG_STRING, 97 T_CONFIG_SCOPE_CONNECTION } 98 ,{ CONST_STR_LEN("status.enable-sort"), 99 T_CONFIG_BOOL, 100 T_CONFIG_SCOPE_CONNECTION } 101 ,{ NULL, 0, 102 T_CONFIG_UNSET, 103 T_CONFIG_SCOPE_UNSET } 104 }; 105 106 plugin_data * const p = p_d; 107 if (!config_plugin_values_init(srv, p, cpk, "mod_status")) 108 return HANDLER_ERROR; 109 110 p->defaults.sort = 1; 111 112 /* initialize p->defaults from global config context */ 113 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 114 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 115 if (-1 != cpv->k_id) 116 mod_status_merge_config(&p->defaults, cpv); 117 } 118 119 return HANDLER_GO_ON; 120 } 121 122 123 124 static void 125 mod_status_append_state (buffer * const b, request_state_t state) 126 { 127 const char *s; 128 size_t n; 129 switch (state) { 130 case CON_STATE_CONNECT: 131 s = "connect"; n = sizeof("connect")-1; break; 132 case CON_STATE_READ: 133 s = "read"; n = sizeof("read")-1; break; 134 case CON_STATE_READ_POST: 135 s = "readpost"; n = sizeof("readpost")-1; break; 136 case CON_STATE_WRITE: 137 s = "write"; n = sizeof("write")-1; break; 138 case CON_STATE_CLOSE: 139 s = "close"; n = sizeof("close")-1; break; 140 case CON_STATE_ERROR: 141 s = "error"; n = sizeof("error")-1; break; 142 case CON_STATE_HANDLE_REQUEST: 143 s = "handle-req"; n = sizeof("handle-req")-1; break; 144 case CON_STATE_REQUEST_START: 145 s = "req-start"; n = sizeof("req-start")-1; break; 146 case CON_STATE_REQUEST_END: 147 s = "req-end"; n = sizeof("req-end")-1; break; 148 case CON_STATE_RESPONSE_START: 149 s = "resp-start"; n = sizeof("resp-start")-1; break; 150 case CON_STATE_RESPONSE_END: 151 s = "resp-end"; n = sizeof("resp-end")-1; break; 152 default: 153 s = "(unknown)"; n = sizeof("(unknown)")-1; break; 154 } 155 buffer_append_string_len(b, s, n); 156 } 157 158 159 static const char * 160 mod_status_get_short_state (request_state_t state) 161 { 162 switch (state) { 163 case CON_STATE_CONNECT: return "."; 164 case CON_STATE_READ: return "r"; 165 case CON_STATE_READ_POST: return "R"; 166 case CON_STATE_WRITE: return "W"; 167 case CON_STATE_CLOSE: return "C"; 168 case CON_STATE_ERROR: return "E"; 169 case CON_STATE_HANDLE_REQUEST: return "h"; 170 case CON_STATE_REQUEST_START: return "q"; 171 case CON_STATE_REQUEST_END: return "Q"; 172 case CON_STATE_RESPONSE_START: return "s"; 173 case CON_STATE_RESPONSE_END: return "S"; 174 default: return "x"; 175 } 176 } 177 178 179 static void mod_status_header_append_sort(buffer *b, plugin_data *p, const char* k, size_t klen) 180 { 181 p->conf.sort 182 ? buffer_append_str3(b, 183 CONST_STR_LEN("<th class=\"status\"><a href=\"#\" class=\"sortheader\" onclick=\"resort(this);return false;\">"), 184 k, klen, 185 CONST_STR_LEN("<span class=\"sortarrow\">:</span></a></th>\n")) 186 : buffer_append_str3(b, 187 CONST_STR_LEN("<th class=\"status\">"), 188 k, klen, 189 CONST_STR_LEN("</th>\n")); 190 } 191 192 static void mod_status_get_multiplier(buffer *b, double avg, int size) { 193 char unit[] = " "; 194 195 if (avg > size) { avg /= size; unit[1] = 'k'; } 196 if (avg > size) { avg /= size; unit[1] = 'M'; } 197 if (avg > size) { avg /= size; unit[1] = 'G'; } 198 if (avg > size) { avg /= size; unit[1] = 'T'; } 199 if (avg > size) { avg /= size; unit[1] = 'P'; } 200 if (avg > size) { avg /= size; unit[1] = 'E'; } 201 if (avg > size) { avg /= size; unit[1] = 'Z'; } 202 if (avg > size) { avg /= size; unit[1] = 'Y'; } 203 204 if (size == 1000) { 205 buffer_append_int(b, (intmax_t)avg); 206 } 207 else { /* (size == 1024) */ 208 char buf[32+1]; 209 buffer_append_string_len(b, buf, (size_t) 210 snprintf(buf, sizeof(buf), "%.2f", avg)); 211 } 212 buffer_append_string_len(b, unit, 2); 213 } 214 215 static void mod_status_html_rtable_r (buffer * const b, const request_st * const r, const connection * const con, const time_t cur_ts) { 216 buffer_append_str3(b, CONST_STR_LEN("<tr><td class=\"string\">"), 217 CONST_BUF_LEN(con->dst_addr_buf), 218 CONST_STR_LEN("</td><td class=\"int\">")); 219 220 if (r->reqbody_length) { 221 buffer_append_int(b, r->reqbody_queue.bytes_in); 222 buffer_append_string_len(b, CONST_STR_LEN("/")); 223 buffer_append_int(b, r->reqbody_length); 224 } 225 else 226 buffer_append_string_len(b, CONST_STR_LEN("0/0")); 227 228 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">")); 229 230 buffer_append_int(b, r->write_queue.bytes_out); 231 buffer_append_string_len(b, CONST_STR_LEN("/")); 232 buffer_append_int(b, r->write_queue.bytes_out + chunkqueue_length(&r->write_queue)); 233 234 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">")); 235 236 if (CON_STATE_READ == r->state && !buffer_string_is_empty(&r->target_orig)) { 237 buffer_append_string_len(b, CONST_STR_LEN("keep-alive")); 238 } 239 else 240 mod_status_append_state(b, r->state); 241 242 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">")); 243 244 buffer_append_int(b, cur_ts - r->start_hp.tv_sec); 245 246 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">")); 247 248 if (buffer_string_is_empty(r->server_name)) 249 buffer_append_string_encoded(b, CONST_BUF_LEN(&r->uri.authority), ENCODING_HTML); 250 else 251 buffer_append_string_encoded(b, CONST_BUF_LEN(r->server_name), ENCODING_HTML); 252 253 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">")); 254 255 if (!buffer_string_is_empty(&r->uri.path)) 256 buffer_append_string_encoded(b, CONST_BUF_LEN(&r->uri.path), ENCODING_HTML); 257 258 if (!buffer_string_is_empty(&r->uri.query)) { 259 buffer_append_string_len(b, CONST_STR_LEN("?")); 260 buffer_append_string_encoded(b, CONST_BUF_LEN(&r->uri.query), ENCODING_HTML); 261 } 262 263 if (!buffer_string_is_empty(&r->target_orig)) { 264 buffer_append_string_len(b, CONST_STR_LEN(" (")); 265 buffer_append_string_encoded(b, CONST_BUF_LEN(&r->target_orig), ENCODING_HTML); 266 buffer_append_string_len(b, CONST_STR_LEN(")")); 267 } 268 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">")); 269 270 buffer_append_string_encoded(b, CONST_BUF_LEN(&r->physical.path), ENCODING_HTML); 271 272 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n")); 273 } 274 275 static void mod_status_html_rtable (request_st * const rq, const server * const srv, const time_t cur_ts) { 276 /* connection table and URLs might be large, so double-buffer to aggregate 277 * before sending to chunkqueue, which might be temporary file 278 * (avoid write() per connection) */ 279 buffer * const b = rq->tmp_buf; 280 buffer_clear(b); 281 connection * const * const cptr = srv->conns.ptr; 282 for (uint32_t i = 0, used = srv->conns.used; i < used; ++i) { 283 const connection * const con = cptr[i]; 284 const request_st * const r = &con->request; 285 if (r->http_status <= HTTP_VERSION_1_1) { 286 if (buffer_string_space(b) < 4096) { 287 http_chunk_append_mem(rq, CONST_BUF_LEN(b)); 288 buffer_clear(b); 289 } 290 mod_status_html_rtable_r(b, r, con, cur_ts); 291 } 292 else { 293 h2con * const h2c = con->h2; 294 for (uint32_t j = 0, rused = h2c->rused; j < rused; ++j) { 295 if (buffer_string_space(b) < 4096) { 296 http_chunk_append_mem(rq, CONST_BUF_LEN(b)); 297 buffer_clear(b); 298 } 299 mod_status_html_rtable_r(b, h2c->r[j], con, cur_ts); 300 } 301 } 302 } 303 http_chunk_append_mem(rq, CONST_BUF_LEN(b)); 304 } 305 306 static handler_t mod_status_handle_server_status_html(server *srv, request_st * const r, plugin_data *p) { 307 buffer * const b = chunkqueue_append_buffer_open(&r->write_queue); 308 buffer_string_prepare_append(b, 8192-1);/*(status page base HTML is ~5.2k)*/ 309 double avg; 310 uint32_t j; 311 time_t ts; 312 const time_t cur_ts = log_epoch_secs; 313 314 int days, hours, mins, seconds; 315 316 /*(CON_STATE_CLOSE must be last state in enum connection_state_t)*/ 317 int cstates[CON_STATE_CLOSE+3]; 318 memset(cstates, 0, sizeof(cstates)); 319 320 buffer_copy_string_len(b, CONST_STR_LEN( 321 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" 322 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 323 " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" 324 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" 325 " <head>\n" 326 " <title>Status</title>\n" 327 328 " <style type=\"text/css\">\n" 329 " table.status { border: black solid thin; }\n" 330 " td { white-space: nowrap; }\n" 331 " td.int { background-color: #f0f0f0; text-align: right }\n" 332 " td.string { background-color: #f0f0f0; text-align: left }\n" 333 " th.status { background-color: black; color: white; font-weight: bold; }\n" 334 " a.sortheader { background-color: black; color: white; font-weight: bold; text-decoration: none; display: block; }\n" 335 " span.sortarrow { color: white; text-decoration: none; }\n" 336 " </style>\n")); 337 338 if (!buffer_string_is_empty(&r->uri.query) && 0 == memcmp(r->uri.query.ptr, CONST_STR_LEN("refresh="))) { 339 /* Note: Refresh is an historical, but non-standard HTTP header 340 * References (meta http-equiv="refresh" use is deprecated): 341 * https://www.w3.org/TR/WCAG10-HTML-TECHS/#meta-element 342 * https://www.w3.org/TR/WCAG10-CORE-TECHS/#auto-page-refresh 343 * https://www.w3.org/QA/Tips/reback 344 */ 345 const long refresh = strtol(r->uri.query.ptr+sizeof("refresh=")-1, NULL, 10); 346 if (refresh > 0) { 347 buffer_append_string_len(b, CONST_STR_LEN("<meta http-equiv=\"refresh\" content=\"")); 348 buffer_append_int(b, refresh < 604800 ? refresh : 604800); 349 buffer_append_string_len(b, CONST_STR_LEN("\">\n")); 350 } 351 } 352 353 if (p->conf.sort) { 354 buffer_append_string_len(b, CONST_STR_LEN( 355 "<script type=\"text/javascript\">\n" 356 "// <!--\n" 357 "var sort_column;\n" 358 "var prev_span = null;\n" 359 360 "function get_inner_text(el) {\n" 361 " if((typeof el == 'string')||(typeof el == 'undefined'))\n" 362 " return el;\n" 363 " if(el.innerText)\n" 364 " return el.innerText;\n" 365 " else {\n" 366 " var str = \"\";\n" 367 " var cs = el.childNodes;\n" 368 " var l = cs.length;\n" 369 " for (i=0;i<l;i++) {\n" 370 " if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n" 371 " else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n" 372 " }\n" 373 " }\n" 374 " return str;\n" 375 "}\n" 376 377 "function sortfn(a,b) {\n" 378 " var at = get_inner_text(a.cells[sort_column]);\n" 379 " var bt = get_inner_text(b.cells[sort_column]);\n" 380 " if (a.cells[sort_column].className == 'int') {\n" 381 " return parseInt(at)-parseInt(bt);\n" 382 " } else {\n" 383 " aa = at.toLowerCase();\n" 384 " bb = bt.toLowerCase();\n" 385 " if (aa==bb) return 0;\n" 386 " else if (aa<bb) return -1;\n" 387 " else return 1;\n" 388 " }\n" 389 "}\n" 390 391 "function resort(lnk) {\n" 392 " var span = lnk.childNodes[1];\n" 393 " var table = lnk.parentNode.parentNode.parentNode.parentNode;\n" 394 " var rows = new Array();\n" 395 " for (j=1;j<table.rows.length;j++)\n" 396 " rows[j-1] = table.rows[j];\n" 397 " sort_column = lnk.parentNode.cellIndex;\n" 398 " rows.sort(sortfn);\n" 399 400 " if (prev_span != null) prev_span.innerHTML = '';\n" 401 " if (span.getAttribute('sortdir')=='down') {\n" 402 " span.innerHTML = '↑';\n" 403 " span.setAttribute('sortdir','up');\n" 404 " rows.reverse();\n" 405 " } else {\n" 406 " span.innerHTML = '↓';\n" 407 " span.setAttribute('sortdir','down');\n" 408 " }\n" 409 " for (i=0;i<rows.length;i++)\n" 410 " table.tBodies[0].appendChild(rows[i]);\n" 411 " prev_span = span;\n" 412 "}\n" 413 "// -->\n" 414 "</script>\n")); 415 } 416 417 buffer_append_string_len(b, CONST_STR_LEN( 418 " </head>\n" 419 "<body>\n")); 420 421 422 423 /* connection listing */ 424 buffer_append_str3(b, CONST_STR_LEN("<h1>Server-Status ("), 425 CONST_BUF_LEN(r->conf.server_tag), 426 CONST_STR_LEN(")</h1>" 427 "<table summary=\"status\" class=\"status\">" 428 "<tr><td>Hostname</td><td class=\"string\">")); 429 buffer_append_string_encoded(b, CONST_BUF_LEN(&r->uri.authority), ENCODING_HTML); 430 buffer_append_string_len(b, CONST_STR_LEN(" (")); 431 buffer_append_string_encoded(b, CONST_BUF_LEN(r->server_name), ENCODING_HTML); 432 buffer_append_string_len(b, CONST_STR_LEN(")</td></tr>\n" 433 "<tr><td>Uptime</td><td class=\"string\">")); 434 435 ts = cur_ts - srv->startup_ts; 436 437 days = ts / (60 * 60 * 24); 438 ts %= (60 * 60 * 24); 439 440 hours = ts / (60 * 60); 441 ts %= (60 * 60); 442 443 mins = ts / (60); 444 ts %= (60); 445 446 seconds = ts; 447 448 if (days) { 449 buffer_append_int(b, days); 450 buffer_append_string_len(b, CONST_STR_LEN(" days ")); 451 } 452 453 if (hours) { 454 buffer_append_int(b, hours); 455 buffer_append_string_len(b, CONST_STR_LEN(" hours ")); 456 } 457 458 if (mins) { 459 buffer_append_int(b, mins); 460 buffer_append_string_len(b, CONST_STR_LEN(" min ")); 461 } 462 463 buffer_append_int(b, seconds); 464 buffer_append_string_len(b, CONST_STR_LEN(" s" 465 "</td></tr>\n" 466 "<tr><td>Started at</td><td class=\"string\">")); 467 468 ts = srv->startup_ts; 469 470 struct tm tm; 471 buffer_append_strftime(b, "%F %T", localtime_r(&ts, &tm)); 472 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n" 473 "<tr><th colspan=\"2\">absolute (since start)</th></tr>\n" 474 "<tr><td>Requests</td><td class=\"string\">")); 475 avg = p->abs_requests; 476 mod_status_get_multiplier(b, avg, 1000); 477 buffer_append_string_len(b, CONST_STR_LEN("req</td></tr>\n" 478 "<tr><td>Traffic</td><td class=\"string\">")); 479 avg = p->abs_traffic_out; 480 mod_status_get_multiplier(b, avg, 1024); 481 buffer_append_string_len(b, CONST_STR_LEN("byte</td></tr>\n" 482 "<tr><th colspan=\"2\">average (since start)</th></tr>\n" 483 "<tr><td>Requests</td><td class=\"string\">")); 484 avg = p->abs_requests / (cur_ts - srv->startup_ts); 485 mod_status_get_multiplier(b, avg, 1000); 486 buffer_append_string_len(b, CONST_STR_LEN("req/s</td></tr>\n" 487 "<tr><td>Traffic</td><td class=\"string\">")); 488 avg = p->abs_traffic_out / (cur_ts - srv->startup_ts); 489 mod_status_get_multiplier(b, avg, 1024); 490 buffer_append_string_len(b, CONST_STR_LEN("byte/s</td></tr>\n" 491 "<tr><th colspan=\"2\">average (5s sliding average)</th></tr>\n")); 492 for (j = 0, avg = 0; j < 5; j++) { 493 avg += p->mod_5s_requests[j]; 494 } 495 496 avg /= 5; 497 498 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Requests</td><td class=\"string\">")); 499 500 mod_status_get_multiplier(b, avg, 1000); 501 buffer_append_string_len(b, CONST_STR_LEN("req/s</td></tr>\n")); 502 503 for (j = 0, avg = 0; j < 5; j++) { 504 avg += p->mod_5s_traffic_out[j]; 505 } 506 507 avg /= 5; 508 509 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Traffic</td><td class=\"string\">")); 510 511 mod_status_get_multiplier(b, avg, 1024); 512 buffer_append_string_len(b, CONST_STR_LEN("byte/s</td></tr>\n" 513 "</table>\n" 514 "<hr />\n<pre>\n" 515 "<b>")); 516 buffer_append_int(b, srv->conns.used); 517 buffer_append_string_len(b, CONST_STR_LEN(" connections</b>\n")); 518 519 int per_line = 50; 520 for (j = 0; j < srv->conns.used; ++j) { 521 connection *c = srv->conns.ptr[j]; 522 const request_st * const cr = &c->request; 523 const char *state; 524 525 if ((c->h2 && 0 == c->h2->rused) 526 || (CON_STATE_READ == cr->state && !buffer_string_is_empty(&cr->target_orig))) { 527 state = "k"; 528 ++cstates[CON_STATE_CLOSE+2]; 529 } else { 530 state = mod_status_get_short_state(cr->state); 531 ++cstates[(cr->state <= CON_STATE_CLOSE ? cr->state : CON_STATE_CLOSE+1)]; 532 } 533 534 buffer_append_string_len(b, state, 1); 535 536 if (0 == --per_line) { 537 per_line = 50; 538 buffer_append_string_len(b, CONST_STR_LEN("\n")); 539 } 540 } 541 buffer_append_string_len(b, CONST_STR_LEN("\n\n<table>\n" 542 "<tr><td style=\"text-align:right\">")); 543 buffer_append_int(b, cstates[CON_STATE_CLOSE+2]); 544 buffer_append_string_len(b, CONST_STR_LEN("<td> k = keep-alive</td></tr>\n")); 545 for (j = 0; j < CON_STATE_CLOSE+2; ++j) { 546 /*(skip "unknown" state if there are none; there should not be any unknown)*/ 547 if (0 == cstates[j] && j == CON_STATE_CLOSE+1) continue; 548 buffer_append_string_len(b, CONST_STR_LEN("<tr><td style=\"text-align:right\">")); 549 buffer_append_int(b, cstates[j]); 550 buffer_append_str3(b, CONST_STR_LEN("</td><td> "), 551 mod_status_get_short_state(j), 1, 552 CONST_STR_LEN(" = ")); 553 mod_status_append_state(b, j); 554 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n")); 555 } 556 buffer_append_string_len(b, CONST_STR_LEN( 557 "</table>\n" 558 "</pre><hr />\n<h2>Connections</h2>\n" 559 "<table summary=\"status\" class=\"status\">\n" 560 "<tr>")); 561 mod_status_header_append_sort(b, p, CONST_STR_LEN("Client IP")); 562 mod_status_header_append_sort(b, p, CONST_STR_LEN("Read")); 563 mod_status_header_append_sort(b, p, CONST_STR_LEN("Written")); 564 mod_status_header_append_sort(b, p, CONST_STR_LEN("State")); 565 mod_status_header_append_sort(b, p, CONST_STR_LEN("Time")); 566 mod_status_header_append_sort(b, p, CONST_STR_LEN("Host")); 567 mod_status_header_append_sort(b, p, CONST_STR_LEN("URI")); 568 mod_status_header_append_sort(b, p, CONST_STR_LEN("File")); 569 buffer_append_string_len(b, CONST_STR_LEN("</tr>\n")); 570 571 chunkqueue_append_buffer_commit(&r->write_queue); 572 /* connection table might be large, so buffer separately */ 573 574 mod_status_html_rtable(r, srv, cur_ts); 575 576 http_chunk_append_mem(r, CONST_STR_LEN( 577 "</table>\n" 578 "</body>\n" 579 "</html>\n" 580 )); 581 582 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); 583 584 return 0; 585 } 586 587 588 static handler_t mod_status_handle_server_status_text(server *srv, request_st * const r, plugin_data *p) { 589 buffer *b = chunkqueue_append_buffer_open(&r->write_queue); 590 591 /* output total number of requests */ 592 buffer_append_string_len(b, CONST_STR_LEN("Total Accesses: ")); 593 buffer_append_int(b, (intmax_t)p->abs_requests); 594 595 buffer_append_string_len(b, CONST_STR_LEN("\nTotal kBytes: ")); 596 buffer_append_int(b, (intmax_t)(p->abs_traffic_out / 1024)); 597 598 buffer_append_string_len(b, CONST_STR_LEN("\nUptime: ")); 599 buffer_append_int(b, log_epoch_secs - srv->startup_ts); 600 601 buffer_append_string_len(b, CONST_STR_LEN("\nBusyServers: ")); 602 buffer_append_int(b, srv->conns.used); 603 604 buffer_append_string_len(b, CONST_STR_LEN("\nIdleServers: ")); 605 buffer_append_int(b, srv->conns.size - srv->conns.used); 606 607 buffer_append_string_len(b, CONST_STR_LEN("\nScoreboard: ")); 608 for (uint32_t i = 0; i < srv->conns.used; ++i) { 609 connection *c = srv->conns.ptr[i]; 610 const request_st * const cr = &c->request; 611 const char *state = 612 ((c->h2 && 0 == c->h2->rused) 613 || (CON_STATE_READ == cr->state && !buffer_string_is_empty(&cr->target_orig))) 614 ? "k" 615 : mod_status_get_short_state(cr->state); 616 buffer_append_string_len(b, state, 1); 617 } 618 for (uint32_t i = 0; i < srv->conns.size - srv->conns.used; ++i) { 619 buffer_append_string_len(b, CONST_STR_LEN("_")); 620 } 621 buffer_append_string_len(b, CONST_STR_LEN("\n")); 622 623 chunkqueue_append_buffer_commit(&r->write_queue); 624 625 /* set text/plain output */ 626 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain")); 627 628 return 0; 629 } 630 631 632 static handler_t mod_status_handle_server_status_json(server *srv, request_st * const r, plugin_data *p) { 633 buffer *b = chunkqueue_append_buffer_open(&r->write_queue); 634 double avg; 635 uint32_t j; 636 unsigned int jsonp = 0; 637 638 if (buffer_string_length(&r->uri.query) >= sizeof("jsonp=")-1 639 && 0 == memcmp(r->uri.query.ptr, CONST_STR_LEN("jsonp="))) { 640 /* not a full parse of query string for multiple parameters, 641 * not URL-decoding param and not XML-encoding (XSS protection), 642 * so simply ensure that json function name isalnum() or '_' */ 643 const char *f = r->uri.query.ptr + sizeof("jsonp=")-1; 644 int len = 0; 645 while (light_isalnum(f[len]) || f[len] == '_') ++len; 646 if (0 != len && light_isalpha(f[0]) && f[len] == '\0') { 647 buffer_append_str2(b, f, len, CONST_STR_LEN("(")); 648 jsonp = 1; 649 } 650 } 651 652 buffer_append_string_len(b, CONST_STR_LEN("{\n\t\"RequestsTotal\": ")); 653 buffer_append_int(b, (intmax_t)p->abs_requests); 654 655 buffer_append_string_len(b, CONST_STR_LEN(",\n\t\"TrafficTotal\": ")); 656 buffer_append_int(b, (intmax_t)(p->abs_traffic_out / 1024)); 657 658 buffer_append_string_len(b, CONST_STR_LEN(",\n\t\"Uptime\": ")); 659 buffer_append_int(b, log_epoch_secs - srv->startup_ts); 660 661 buffer_append_string_len(b, CONST_STR_LEN(",\n\t\"BusyServers\": ")); 662 buffer_append_int(b, srv->conns.used); 663 664 buffer_append_string_len(b, CONST_STR_LEN(",\n\t\"IdleServers\": ")); 665 buffer_append_int(b, srv->conns.size - srv->conns.used); 666 buffer_append_string_len(b, CONST_STR_LEN(",\n")); 667 668 for (j = 0, avg = 0; j < 5; j++) { 669 avg += p->mod_5s_requests[j]; 670 } 671 672 avg /= 5; 673 674 buffer_append_string_len(b, CONST_STR_LEN("\t\"RequestAverage5s\":")); 675 buffer_append_int(b, avg); 676 buffer_append_string_len(b, CONST_STR_LEN(",\n")); 677 678 for (j = 0, avg = 0; j < 5; j++) { 679 avg += p->mod_5s_traffic_out[j]; 680 } 681 682 avg /= 5; 683 684 buffer_append_string_len(b, CONST_STR_LEN("\t\"TrafficAverage5s\":")); 685 buffer_append_int(b, avg / 1024); /* kbps */ 686 buffer_append_string_len(b, CONST_STR_LEN("\n}")); 687 688 if (jsonp) buffer_append_string_len(b, CONST_STR_LEN(");")); 689 690 chunkqueue_append_buffer_commit(&r->write_queue); 691 692 /* set text/plain output */ 693 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/javascript")); 694 695 return 0; 696 } 697 698 699 static handler_t mod_status_handle_server_statistics(request_st * const r) { 700 buffer *b; 701 size_t i; 702 array *st = &plugin_stats; 703 704 if (0 == st->used) { 705 /* we have nothing to send */ 706 r->http_status = 204; 707 r->resp_body_finished = 1; 708 709 return HANDLER_FINISHED; 710 } 711 712 b = chunkqueue_append_buffer_open(&r->write_queue); 713 for (i = 0; i < st->used; i++) { 714 buffer_append_str2(b, CONST_BUF_LEN(&st->sorted[i]->key), 715 CONST_STR_LEN(": ")); 716 buffer_append_int(b, ((data_integer *)st->sorted[i])->value); 717 buffer_append_string_len(b, CONST_STR_LEN("\n")); 718 } 719 chunkqueue_append_buffer_commit(&r->write_queue); 720 721 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain")); 722 723 r->http_status = 200; 724 r->resp_body_finished = 1; 725 726 return HANDLER_FINISHED; 727 } 728 729 730 static handler_t mod_status_handle_server_status(request_st * const r, plugin_data * const p) { 731 server * const srv = r->con->srv; 732 if (buffer_is_equal_string(&r->uri.query, CONST_STR_LEN("auto"))) { 733 mod_status_handle_server_status_text(srv, r, p); 734 } else if (buffer_string_length(&r->uri.query) >= sizeof("json")-1 735 && 0 == memcmp(r->uri.query.ptr, CONST_STR_LEN("json"))) { 736 mod_status_handle_server_status_json(srv, r, p); 737 } else { 738 mod_status_handle_server_status_html(srv, r, p); 739 } 740 741 r->http_status = 200; 742 r->resp_body_finished = 1; 743 744 return HANDLER_FINISHED; 745 } 746 747 748 static void mod_status_row_append(buffer *b, const char *k, size_t klen, const char *v, size_t vlen) 749 { 750 struct const_iovec iov[] = { 751 { CONST_STR_LEN(" <tr>\n" 752 " <td><b>") } 753 ,{ k, klen } 754 ,{ CONST_STR_LEN("</b></td>\n" 755 " <td>") } 756 ,{ v, vlen } 757 ,{ CONST_STR_LEN("</td>\n" 758 " </tr>\n") } 759 }; 760 buffer_append_iovec(b, iov, sizeof(iov)/sizeof(*iov)); 761 } 762 763 static void mod_status_header_append(buffer *b, const char *k, size_t klen) 764 { 765 buffer_append_str3(b, 766 CONST_STR_LEN(" <tr>\n" 767 " <th colspan=\"2\">"), 768 k, klen, 769 CONST_STR_LEN("</th>\n" 770 " </tr>\n")); 771 } 772 773 static handler_t mod_status_handle_server_config(request_st * const r) { 774 server * const srv = r->con->srv; 775 buffer * const tb = r->tmp_buf; 776 buffer_clear(tb); 777 for (uint32_t i = 0; i < srv->plugins.used; ++i) { 778 const char *name = ((plugin **)srv->plugins.ptr)[i]->name; 779 if (i != 0) { 780 buffer_append_string_len(tb, CONST_STR_LEN("<br />")); 781 } 782 buffer_append_string_len(tb, name, strlen(name)); 783 } 784 785 buffer *b = chunkqueue_append_buffer_open(&r->write_queue); 786 787 /*(could expand the following into a single buffer_append_iovec(), 788 * but this routine is not expected to be under high load)*/ 789 790 buffer_append_str3(b, CONST_STR_LEN( 791 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" 792 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 793 " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" 794 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" 795 " <head>\n" 796 " <title>Status</title>\n" 797 " </head>\n" 798 " <body>\n" 799 " <h1>"), 800 CONST_BUF_LEN(r->conf.server_tag), 801 CONST_STR_LEN( 802 "</h1>\n" 803 " <table summary=\"status\" border=\"1\">\n")); 804 805 mod_status_header_append(b, CONST_STR_LEN("Server-Features")); 806 #ifdef HAVE_PCRE_H 807 mod_status_row_append(b, CONST_STR_LEN("RegEx Conditionals"), CONST_STR_LEN("enabled")); 808 #else 809 mod_status_row_append(b, CONST_STR_LEN("RegEx Conditionals"), CONST_STR_LEN("disabled - pcre missing")); 810 #endif 811 mod_status_header_append(b, CONST_STR_LEN("Network Engine")); 812 813 mod_status_row_append(b, CONST_STR_LEN("fd-Event-Handler"), 814 srv->srvconf.event_handler, 815 strlen(srv->srvconf.event_handler)); 816 817 mod_status_header_append(b, CONST_STR_LEN("Config-File-Settings")); 818 819 mod_status_row_append(b, CONST_STR_LEN("Loaded Modules"), CONST_BUF_LEN(tb)); 820 821 buffer_append_string_len(b, CONST_STR_LEN( 822 " </table>\n" 823 " </body>\n" 824 "</html>\n" 825 )); 826 827 chunkqueue_append_buffer_commit(&r->write_queue); 828 829 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); 830 831 r->http_status = 200; 832 r->resp_body_finished = 1; 833 834 return HANDLER_FINISHED; 835 } 836 837 static handler_t mod_status_handler(request_st * const r, void *p_d) { 838 plugin_data *p = p_d; 839 840 if (NULL != r->handler_module) return HANDLER_GO_ON; 841 842 mod_status_patch_config(r, p); 843 844 if (!buffer_string_is_empty(p->conf.status_url) && 845 buffer_is_equal(p->conf.status_url, &r->uri.path)) { 846 return mod_status_handle_server_status(r, p); 847 } else if (!buffer_string_is_empty(p->conf.config_url) && 848 buffer_is_equal(p->conf.config_url, &r->uri.path)) { 849 return mod_status_handle_server_config(r); 850 } else if (!buffer_string_is_empty(p->conf.statistics_url) && 851 buffer_is_equal(p->conf.statistics_url, &r->uri.path)) { 852 return mod_status_handle_server_statistics(r); 853 } 854 855 return HANDLER_GO_ON; 856 } 857 858 TRIGGER_FUNC(mod_status_trigger) { 859 plugin_data *p = p_d; 860 861 /* check all connections */ 862 for (uint32_t i = 0; i < srv->conns.used; ++i) { 863 connection *c = srv->conns.ptr[i]; 864 865 p->bytes_written += c->bytes_written_cur_second; 866 } 867 868 /* a sliding average */ 869 p->mod_5s_traffic_out[p->mod_5s_ndx] = p->bytes_written; 870 p->mod_5s_requests [p->mod_5s_ndx] = p->requests; 871 872 p->mod_5s_ndx = (p->mod_5s_ndx+1) % 5; 873 874 p->abs_traffic_out += p->bytes_written; 875 p->rel_traffic_out += p->bytes_written; 876 877 p->bytes_written = 0; 878 879 /* reset storage - second */ 880 p->traffic_out = 0; 881 p->requests = 0; 882 883 return HANDLER_GO_ON; 884 } 885 886 REQUESTDONE_FUNC(mod_status_account) { 887 plugin_data *p = p_d; 888 889 p->requests++; 890 p->rel_requests++; 891 p->abs_requests++; 892 893 p->bytes_written += r->con->bytes_written_cur_second; 894 895 return HANDLER_GO_ON; 896 } 897 898 899 int mod_status_plugin_init(plugin *p); 900 int mod_status_plugin_init(plugin *p) { 901 p->version = LIGHTTPD_VERSION_ID; 902 p->name = "status"; 903 904 p->init = mod_status_init; 905 p->set_defaults= mod_status_set_defaults; 906 907 p->handle_uri_clean = mod_status_handler; 908 p->handle_trigger = mod_status_trigger; 909 p->handle_request_done = mod_status_account; 910 911 return 0; 912 } 913