blob: 4e2929c5917d5290370859eacbdeadc95a1e9929 [file] [log] [blame]
Willy Tarreau91861262007-10-17 17:06:05 +02001/*
2 * Functions dedicated to statistics output
3 *
4 * Copyright 2000-2007 Willy Tarreau <w@1wt.eu>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13#include <ctype.h>
14#include <errno.h>
15#include <fcntl.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19#include <time.h>
20
21#include <sys/socket.h>
22#include <sys/stat.h>
23#include <sys/types.h>
24
25#include <common/compat.h>
26#include <common/config.h>
27#include <common/debug.h>
28#include <common/memory.h>
29#include <common/mini-clist.h>
30#include <common/standard.h>
31#include <common/time.h>
32#include <common/uri_auth.h>
33#include <common/version.h>
34
35#include <types/client.h>
36#include <types/global.h>
37#include <types/polling.h>
38#include <types/proxy.h>
39#include <types/server.h>
40
41#include <proto/backend.h>
42#include <proto/buffers.h>
43#include <proto/dumpstats.h>
44#include <proto/fd.h>
45#include <proto/senddata.h>
46#include <proto/session.h>
47
48/*
49 * Produces statistics data for the session <s>. Expects to be called with
50 * s->cli_state == CL_STSHUTR. It stops by itself by unsetting the SN_SELF_GEN
51 * flag from the session, which it uses to keep on being called when there is
52 * free space in the buffer, of simply by letting an empty buffer upon return.
53 * It returns 0 if it had to stop writing data and an I/O is needed, 1 if the
54 * dump is finished and the session must be closed, or -1 in case of any error.
55 */
56int stats_dump_http(struct session *s, struct uri_auth *uri, int flags)
57{
58 struct buffer *rep = s->rep;
59 struct proxy *px;
60 struct chunk msg;
61 unsigned int up;
62
63 msg.len = 0;
64 msg.str = trash;
65
66 switch (s->data_state) {
67 case DATA_ST_INIT:
68 /* the function had not been called yet */
69 s->flags |= SN_SELF_GEN; // more data will follow
70
71 chunk_printf(&msg, sizeof(trash),
72 "HTTP/1.0 200 OK\r\n"
73 "Cache-Control: no-cache\r\n"
74 "Connection: close\r\n"
75 "Content-Type: text/html\r\n");
76
77 if (uri->refresh > 0 && !(s->flags & SN_STAT_NORFRSH))
78 chunk_printf(&msg, sizeof(trash), "Refresh: %d\r\n",
79 uri->refresh);
80
81 chunk_printf(&msg, sizeof(trash), "\r\n");
82
83 s->txn.status = 200;
84 client_retnclose(s, &msg); // send the start of the response.
85 msg.len = 0;
86
87 if (!(s->flags & SN_ERR_MASK)) // this is not really an error but it is
88 s->flags |= SN_ERR_PRXCOND; // to mark that it comes from the proxy
89 if (!(s->flags & SN_FINST_MASK))
90 s->flags |= SN_FINST_R;
91
92 if (s->txn.meth == HTTP_METH_HEAD) {
93 /* that's all we return in case of HEAD request */
94 s->data_state = DATA_ST_FIN;
95 s->flags &= ~SN_SELF_GEN;
96 return 1;
97 }
98
99 s->data_state = DATA_ST_HEAD; /* let's start producing data */
100 /* fall through */
101
102 case DATA_ST_HEAD:
103 /* WARNING! This must fit in the first buffer !!! */
104 chunk_printf(&msg, sizeof(trash),
105 "<html><head><title>Statistics Report for " PRODUCT_NAME "</title>\n"
106 "<meta http-equiv=\"content-type\" content=\"text/html; charset=iso-8859-1\">\n"
107 "<style type=\"text/css\"><!--\n"
108 "body {"
109 " font-family: helvetica, arial;"
110 " font-size: 12px;"
111 " font-weight: normal;"
112 " color: black;"
113 " background: white;"
114 "}\n"
115 "th,td {"
116 " font-size: 0.8em;"
117 " align: center;"
118 "}\n"
119 "h1 {"
120 " font-size: xx-large;"
121 " margin-bottom: 0.5em;"
122 "}\n"
123 "h2 {"
124 " font-family: helvetica, arial;"
125 " font-size: x-large;"
126 " font-weight: bold;"
127 " font-style: italic;"
128 " color: #6020a0;"
129 " margin-top: 0em;"
130 " margin-bottom: 0em;"
131 "}\n"
132 "h3 {"
133 " font-family: helvetica, arial;"
134 " font-size: 16px;"
135 " font-weight: bold;"
136 " color: #b00040;"
137 " background: #e8e8d0;"
138 " margin-top: 0em;"
139 " margin-bottom: 0em;"
140 "}\n"
141 "li {"
142 " margin-top: 0.25em;"
143 " margin-right: 2em;"
144 "}\n"
145 ".hr {margin-top: 0.25em;"
146 " border-color: black;"
147 " border-bottom-style: solid;"
148 "}\n"
149 ".pxname {background: #b00040;color: #ffff40;font-weight: bold;}\n"
150 ".titre {background: #20D0D0;color: #000000;font-weight: bold;}\n"
151 ".total {background: #20D0D0;color: #ffff80;}\n"
152 ".frontend {background: #e8e8d0;}\n"
153 ".backend {background: #e8e8d0;}\n"
154 ".active0 {background: #ff9090;}\n"
155 ".active1 {background: #ffd020;}\n"
156 ".active2 {background: #ffffa0;}\n"
157 ".active3 {background: #c0ffc0;}\n"
158 ".active4 {background: #e0e0e0;}\n"
159 ".backup0 {background: #ff9090;}\n"
160 ".backup1 {background: #ff80ff;}\n"
161 ".backup2 {background: #c060ff;}\n"
162 ".backup3 {background: #b0d0ff;}\n"
163 ".backup4 {background: #e0e0e0;}\n"
164 "table.tbl { border-collapse: collapse; border-style: none;}\n"
165 "table.tbl td { border-width: 1px 1px 1px 1px; border-style: solid solid solid solid; padding: 2px 3px; border-color: gray;}\n"
166 "table.tbl th { border-width: 1px; border-style: solid solid solid solid; border-color: gray;}\n"
167 "table.tbl th.empty { border-style: none; empty-cells: hide;}\n"
168 "table.lgd { border-collapse: collapse; border-width: 1px; border-style: none none none solid; border-color: black;}\n"
169 "table.lgd td { border-width: 1px; border-style: solid solid solid solid; border-color: gray; padding: 2px;}\n"
170 "table.lgd td.noborder { border-style: none; padding: 2px; white-space: nowrap;}\n"
171 "-->\n"
172 "</style></head>\n");
173
174 if (buffer_write_chunk(rep, &msg) != 0)
175 return 0;
176
177 s->data_state = DATA_ST_INFO;
178 /* fall through */
179
180 case DATA_ST_INFO:
181 up = (now.tv_sec - start_date.tv_sec);
182
183 /* WARNING! this has to fit the first packet too.
184 * We are around 3.5 kB, add adding entries will
185 * become tricky if we want to support 4kB buffers !
186 */
187 chunk_printf(&msg, sizeof(trash),
188 "<body><h1><a href=\"" PRODUCT_URL "\" style=\"text-decoration: none;\">"
189 PRODUCT_NAME "%s</a></h1>\n"
190 "<h2>Statistics Report for pid %d</h2>\n"
191 "<hr width=\"100%%\" class=\"hr\">\n"
192 "<h3>&gt; General process information</h3>\n"
193 "<table border=0 cols=4><tr><td align=\"left\" nowrap width=\"1%%\">\n"
194 "<p><b>pid = </b> %d (nbproc = %d)<br>\n"
195 "<b>uptime = </b> %dd %dh%02dm%02ds<br>\n"
196 "<b>system limits :</b> memmax = %s%s ; ulimit-n = %d<br>\n"
197 "<b>maxsock = </b> %d<br>\n"
198 "<b>maxconn = </b> %d (current conns = %d)<br>\n"
199 "</td><td align=\"center\" nowrap>\n"
200 "<table class=\"lgd\"><tr>\n"
201 "<td class=\"active3\">&nbsp;</td><td class=\"noborder\">active UP </td>"
202 "<td class=\"backup3\">&nbsp;</td><td class=\"noborder\">backup UP </td>"
203 "</tr><tr>\n"
204 "<td class=\"active2\"></td><td class=\"noborder\">active UP, going down </td>"
205 "<td class=\"backup2\"></td><td class=\"noborder\">backup UP, going down </td>"
206 "</tr><tr>\n"
207 "<td class=\"active1\"></td><td class=\"noborder\">active DOWN, going up </td>"
208 "<td class=\"backup1\"></td><td class=\"noborder\">backup DOWN, going up </td>"
209 "</tr><tr>\n"
210 "<td class=\"active0\"></td><td class=\"noborder\">active or backup DOWN &nbsp;</td>"
211 "<td class=\"active4\"></td><td class=\"noborder\">not checked </td>"
212 "</tr></table>\n"
213 "</td>"
214 "<td align=\"left\" valign=\"top\" nowrap width=\"1%%\">"
215 "<b>Display option:</b><ul style=\"margin-top: 0.25em;\">"
216 "",
217 (uri->flags&ST_HIDEVER)?"":(STATS_VERSION_STRING),
218 pid, pid, global.nbproc,
219 up / 86400, (up % 86400) / 3600,
220 (up % 3600) / 60, (up % 60),
221 global.rlimit_memmax ? ultoa(global.rlimit_memmax) : "unlimited",
222 global.rlimit_memmax ? " MB" : "",
223 global.rlimit_nofile,
224 global.maxsock,
225 global.maxconn,
226 actconn
227 );
228
229 if (s->flags & SN_STAT_HIDEDWN)
230 chunk_printf(&msg, sizeof(trash),
231 "<li><a href=\"%s%s%s\">Show all servers</a><br>\n",
232 uri->uri_prefix,
233 "",
234 (s->flags & SN_STAT_NORFRSH) ? ";norefresh" : "");
235 else
236 chunk_printf(&msg, sizeof(trash),
237 "<li><a href=\"%s%s%s\">Hide 'DOWN' servers</a><br>\n",
238 uri->uri_prefix,
239 ";up",
240 (s->flags & SN_STAT_NORFRSH) ? ";norefresh" : "");
241
242 if (uri->refresh > 0) {
243 if (s->flags & SN_STAT_NORFRSH)
244 chunk_printf(&msg, sizeof(trash),
245 "<li><a href=\"%s%s%s\">Enable refresh</a><br>\n",
246 uri->uri_prefix,
247 (s->flags & SN_STAT_HIDEDWN) ? ";up" : "",
248 "");
249 else
250 chunk_printf(&msg, sizeof(trash),
251 "<li><a href=\"%s%s%s\">Disable refresh</a><br>\n",
252 uri->uri_prefix,
253 (s->flags & SN_STAT_HIDEDWN) ? ";up" : "",
254 ";norefresh");
255 }
256
257 chunk_printf(&msg, sizeof(trash),
258 "<li><a href=\"%s%s%s\">Refresh now</a><br>\n",
259 uri->uri_prefix,
260 (s->flags & SN_STAT_HIDEDWN) ? ";up" : "",
261 (s->flags & SN_STAT_NORFRSH) ? ";norefresh" : "");
262
263 chunk_printf(&msg, sizeof(trash),
264 "</td>"
265 "<td align=\"left\" valign=\"top\" nowrap width=\"1%%\">"
266 "<b>External ressources:</b><ul style=\"margin-top: 0.25em;\">\n"
267 "<li><a href=\"" PRODUCT_URL "\">Primary site</a><br>\n"
268 "<li><a href=\"" PRODUCT_URL_UPD "\">Updates (v" PRODUCT_BRANCH ")</a><br>\n"
269 "<li><a href=\"" PRODUCT_URL_DOC "\">Online manual</a><br>\n"
270 "</ul>"
271 "</td>"
272 "</tr></table>\n"
273 ""
274 );
275
276 if (buffer_write_chunk(rep, &msg) != 0)
277 return 0;
278
279 memset(&s->data_ctx, 0, sizeof(s->data_ctx));
280
281 s->data_ctx.stats.px = proxy;
282 s->data_ctx.stats.px_st = DATA_ST_PX_INIT;
283 s->data_state = DATA_ST_LIST;
284 /* fall through */
285
286 case DATA_ST_LIST:
287 /* dump proxies */
288 while (s->data_ctx.stats.px) {
289 px = s->data_ctx.stats.px;
290 /* skip the disabled proxies and non-networked ones */
291 if (px->state != PR_STSTOPPED && (px->cap & (PR_CAP_FE | PR_CAP_BE)))
292 if (stats_dump_proxy(s, px, uri, flags) == 0)
293 return 0;
294
295 s->data_ctx.stats.px = px->next;
296 s->data_ctx.stats.px_st = DATA_ST_PX_INIT;
297 }
298 /* here, we just have reached the last proxy */
299
300 s->data_state = DATA_ST_END;
301 /* fall through */
302
303 case DATA_ST_END:
304 chunk_printf(&msg, sizeof(trash), "</body></html>\n");
305 if (buffer_write_chunk(rep, &msg) != 0)
306 return 0;
307
308 s->data_state = DATA_ST_FIN;
309 /* fall through */
310
311 case DATA_ST_FIN:
312 s->flags &= ~SN_SELF_GEN;
313 return 1;
314
315 default:
316 /* unknown state ! */
317 s->flags &= ~SN_SELF_GEN;
318 return -1;
319 }
320}
321
322
323/*
324 * Dumps statistics for a proxy.
325 * Returns 0 if it had to stop dumping data because of lack of buffer space,
326 * ot non-zero if everything completed.
327 */
328int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri, int flags)
329{
330 struct buffer *rep = s->rep;
331 struct server *sv;
332 struct chunk msg;
333
334 msg.len = 0;
335 msg.str = trash;
336
337 switch (s->data_ctx.stats.px_st) {
338 case DATA_ST_PX_INIT:
339 /* we are on a new proxy */
340
341 if (uri && uri->scope) {
342 /* we have a limited scope, we have to check the proxy name */
343 struct stat_scope *scope;
344 int len;
345
346 len = strlen(px->id);
347 scope = uri->scope;
348
349 while (scope) {
350 /* match exact proxy name */
351 if (scope->px_len == len && !memcmp(px->id, scope->px_id, len))
352 break;
353
354 /* match '.' which means 'self' proxy */
355 if (!strcmp(scope->px_id, ".") && px == s->fe)
356 break;
357 scope = scope->next;
358 }
359
360 /* proxy name not found : don't dump anything */
361 if (scope == NULL)
362 return 1;
363 }
364
365 s->data_ctx.stats.px_st = DATA_ST_PX_TH;
366 /* fall through */
367
368 case DATA_ST_PX_TH:
369 /* print a new table */
370 chunk_printf(&msg, sizeof(trash),
371 "<table cols=\"20\" class=\"tbl\" width=\"100%%\">\n"
372 "<tr align=\"center\" class=\"titre\">"
373 "<th colspan=2 class=\"pxname\">%s</th>"
374 "<th colspan=18 class=\"empty\"></th>"
375 "</tr>\n"
376 "<tr align=\"center\" class=\"titre\">"
377 "<th rowspan=2></th>"
378 "<th colspan=2>Queue</th><th colspan=4>Sessions</th>"
379 "<th colspan=2>Bytes</th><th colspan=2>Denied</th>"
380 "<th colspan=3>Errors</th><th colspan=6>Server</th>"
381 "</tr>\n"
382 "<tr align=\"center\" class=\"titre\">"
383 "<th>Cur</th><th>Max</th><th>Cur</th><th>Max</th>"
384 "<th>Limit</th><th>Cumul</th><th>In</th><th>Out</th>"
385 "<th>Req</th><th>Resp</th><th>Req</th><th>Conn</th>"
386 "<th>Resp</th><th>Status</th><th>Weight</th><th>Act</th>"
387 "<th>Bck</th><th>Check</th><th>Down</th></tr>\n"
388 "",
389 px->id);
390
391 if (buffer_write_chunk(rep, &msg) != 0)
392 return 0;
393
394 s->data_ctx.stats.px_st = DATA_ST_PX_FE;
395 /* fall through */
396
397 case DATA_ST_PX_FE:
398 /* print the frontend */
399 if (px->cap & PR_CAP_FE) {
400 chunk_printf(&msg, sizeof(trash),
401 /* name, queue */
402 "<tr align=center class=\"frontend\"><td>Frontend</td><td colspan=2></td>"
403 /* sessions : current, max, limit, cumul. */
404 "<td align=right>%d</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td>"
405 /* bytes : in, out */
406 "<td align=right>%lld</td><td align=right>%lld</td>"
407 /* denied: req, resp */
408 "<td align=right>%d</td><td align=right>%d</td>"
409 /* errors : request, connect, response */
410 "<td align=right>%d</td><td align=right></td><td align=right></td>"
411 /* server status : reflect backend status */
412 "<td align=center>%s</td>"
413 /* rest of server: nothing */
414 "<td align=center colspan=5></td></tr>"
415 "",
416 px->feconn, px->feconn_max, px->maxconn, px->cum_feconn,
417 px->bytes_in, px->bytes_out,
418 px->denied_req, px->denied_resp,
419 px->failed_req,
420 px->state == PR_STRUN ? "OPEN" :
421 px->state == PR_STIDLE ? "FULL" : "STOP");
422
423 if (buffer_write_chunk(rep, &msg) != 0)
424 return 0;
425 }
426
427 s->data_ctx.stats.sv = px->srv; /* may be NULL */
428 s->data_ctx.stats.px_st = DATA_ST_PX_SV;
429 /* fall through */
430
431 case DATA_ST_PX_SV:
432 /* stats.sv has been initialized above */
433 while (s->data_ctx.stats.sv != NULL) {
434 static char *srv_hlt_st[5] = { "DOWN", "DN %d/%d &uarr;", "UP %d/%d &darr;", "UP", "<i>no check</i>" };
435 int sv_state; /* 0=DOWN, 1=going up, 2=going down, 3=UP, 4=unchecked */
436
437 sv = s->data_ctx.stats.sv;
438
439 /* FIXME: produce some small strings for "UP/DOWN x/y &#xxxx;" */
440 if (!(sv->state & SRV_CHECKED))
441 sv_state = 4;
442 else if (sv->state & SRV_RUNNING)
443 if (sv->health == sv->rise + sv->fall - 1)
444 sv_state = 3; /* UP */
445 else
446 sv_state = 2; /* going down */
447 else
448 if (sv->health)
449 sv_state = 1; /* going up */
450 else
451 sv_state = 0; /* DOWN */
452
453 if ((sv_state == 0) && (s->flags & SN_STAT_HIDEDWN)) {
454 /* do not report servers which are DOWN */
455 s->data_ctx.stats.sv = sv->next;
456 continue;
457 }
458
459 chunk_printf(&msg, sizeof(trash),
460 /* name */
461 "<tr align=\"center\" class=\"%s%d\"><td>%s</td>"
462 /* queue : current, max */
463 "<td align=right>%d</td><td align=right>%d</td>"
464 /* sessions : current, max, limit, cumul */
465 "<td align=right>%d</td><td align=right>%d</td><td align=right>%s</td><td align=right>%d</td>"
466 /* bytes : in, out */
467 "<td align=right>%lld</td><td align=right>%lld</td>"
468 /* denied: req, resp */
469 "<td align=right></td><td align=right>%d</td>"
470 /* errors : request, connect, response */
471 "<td align=right></td><td align=right>%d</td><td align=right>%d</td>\n"
472 "",
473 (sv->state & SRV_BACKUP) ? "backup" : "active",
474 sv_state, sv->id,
475 sv->nbpend, sv->nbpend_max,
476 sv->cur_sess, sv->cur_sess_max, sv->maxconn ? ultoa(sv->maxconn) : "-", sv->cum_sess,
477 sv->bytes_in, sv->bytes_out,
478 sv->failed_secu,
479 sv->failed_conns, sv->failed_resp);
480
481 /* status */
482 chunk_printf(&msg, sizeof(trash), "<td nowrap>");
483 chunk_printf(&msg, sizeof(trash),
484 srv_hlt_st[sv_state],
485 (sv->state & SRV_RUNNING) ? (sv->health - sv->rise + 1) : (sv->health),
486 (sv->state & SRV_RUNNING) ? (sv->fall) : (sv->rise));
487
488 chunk_printf(&msg, sizeof(trash),
489 /* weight */
490 "</td><td>%d</td>"
491 /* act, bck */
492 "<td>%s</td><td>%s</td>"
493 "",
494 sv->uweight,
495 (sv->state & SRV_BACKUP) ? "-" : "Y",
496 (sv->state & SRV_BACKUP) ? "Y" : "-");
497
498 /* check failures : unique, fatal */
499 if (sv->state & SRV_CHECKED)
500 chunk_printf(&msg, sizeof(trash),
501 "<td align=right>%d</td><td align=right>%d</td></tr>\n",
502 sv->failed_checks, sv->down_trans);
503 else
504 chunk_printf(&msg, sizeof(trash),
505 "<td colspan=2></td></tr>\n");
506
507 if (buffer_write_chunk(rep, &msg) != 0)
508 return 0;
509
510 s->data_ctx.stats.sv = sv->next;
511 } /* while sv */
512
513 s->data_ctx.stats.px_st = DATA_ST_PX_BE;
514 /* fall through */
515
516 case DATA_ST_PX_BE:
517 /* print the backend */
518 if (px->cap & PR_CAP_BE) {
519 int gcd = 1;
520
521 if (px->map_state & PR_MAP_RECALC)
522 recalc_server_map(px);
523
524 /* The GCD which was computed causes the total effective
525 * weight to appear lower than all weights. Let's
526 * recompute it.
527 */
528 if (px->srv && px->srv->eweight)
529 gcd = px->srv->uweight / px->srv->eweight;
530
531 chunk_printf(&msg, sizeof(trash),
532 /* name */
533 "<tr align=center class=\"backend\"><td>Backend</td>"
534 /* queue : current, max */
535 "<td align=right>%d</td><td align=right>%d</td>"
536 /* sessions : current, max, limit, cumul. */
537 "<td align=right>%d</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td>"
538 /* bytes : in, out */
539 "<td align=right>%lld</td><td align=right>%lld</td>"
540 /* denied: req, resp */
541 "<td align=right>%d</td><td align=right>%d</td>"
542 /* errors : request, connect, response */
543 "<td align=right></td><td align=right>%d</td><td align=right>%d</td>\n"
544 /* server status : reflect backend status (up/down) : we display UP
545 * if the backend has known working servers or if it has no server at
546 * all (eg: for stats). Tthen we display the total weight, number of
547 * active and backups. */
548 "<td align=center>%s</td><td align=center>%d</td>"
549 "<td align=center>%d</td><td align=center>%d</td>"
550 /* rest of server: nothing */
551 "<td align=center colspan=2></td></tr>"
552 "",
553 px->nbpend /* or px->totpend ? */, px->nbpend_max,
554 px->beconn, px->beconn_max, px->fullconn, px->cum_beconn,
555 px->bytes_in, px->bytes_out,
556 px->denied_req, px->denied_resp,
557 px->failed_conns, px->failed_resp,
558 (px->srv_map_sz > 0 || !px->srv) ? "UP" : "DOWN",
559 px->srv_map_sz * gcd, px->srv_act, px->srv_bck);
560
561 if (buffer_write_chunk(rep, &msg) != 0)
562 return 0;
563 }
564
565 s->data_ctx.stats.px_st = DATA_ST_PX_END;
566 /* fall through */
567
568 case DATA_ST_PX_END:
569 chunk_printf(&msg, sizeof(trash), "</table><p>\n");
570
571 if (buffer_write_chunk(rep, &msg) != 0)
572 return 0;
573
574 s->data_ctx.stats.px_st = DATA_ST_PX_FIN;
575 /* fall through */
576
577 case DATA_ST_PX_FIN:
578 return 1;
579
580 default:
581 /* unknown state, we should put an abort() here ! */
582 return 1;
583 }
584}
585
586/*
587 * Local variables:
588 * c-indent-level: 8
589 * c-basic-offset: 8
590 * End:
591 */