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