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