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