blob: 82f179a4c5f3f34f2bc1936a95db881a274ebd9d [file] [log] [blame]
Willy Tarreau72c28532009-01-22 18:56:50 +01001/*
Willy Tarreaud8fc1102010-09-12 17:56:16 +02002 * haproxy log statistics reporter
Willy Tarreau72c28532009-01-22 18:56:50 +01003 *
Willy Tarreaud2201062010-05-27 18:17:30 +02004 * Copyright 2000-2010 Willy Tarreau <w@1wt.eu>
Willy Tarreau72c28532009-01-22 18:56:50 +01005 *
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
Willy Tarreau72c28532009-01-22 18:56:50 +010013#include <errno.h>
14#include <fcntl.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <syslog.h>
18#include <string.h>
19#include <unistd.h>
20#include <ctype.h>
21
Willy Tarreau45cb4fb2009-10-26 21:10:04 +010022#include <eb32tree.h>
Willy Tarreauabe45b62010-10-28 20:33:46 +020023#include <eb64tree.h>
24#include <ebistree.h>
Willy Tarreaud2201062010-05-27 18:17:30 +020025#include <ebsttree.h>
Willy Tarreau72c28532009-01-22 18:56:50 +010026
Willy Tarreaud2201062010-05-27 18:17:30 +020027#define SOURCE_FIELD 5
Willy Tarreau72c28532009-01-22 18:56:50 +010028#define ACCEPT_FIELD 6
Willy Tarreaud2201062010-05-27 18:17:30 +020029#define SERVER_FIELD 8
Willy Tarreau72c28532009-01-22 18:56:50 +010030#define TIME_FIELD 9
31#define STATUS_FIELD 10
Willy Tarreaud8fc1102010-09-12 17:56:16 +020032#define TERM_CODES_FIELD 14
Willy Tarreau72c28532009-01-22 18:56:50 +010033#define CONN_FIELD 15
Willy Tarreauabe45b62010-10-28 20:33:46 +020034#define METH_FIELD 17
35#define URL_FIELD 18
Willy Tarreau72c28532009-01-22 18:56:50 +010036#define MAXLINE 16384
37#define QBITS 4
38
Willy Tarreaudf6f0d12011-07-10 18:15:08 +020039#define SEP(c) ((unsigned char)(c) <= ' ')
40#define SKIP_CHAR(p,c) do { while (1) { int __c = (unsigned char)*p++; if (__c == c) break; if (__c <= ' ') { p--; break; } } } while (0)
Willy Tarreau72c28532009-01-22 18:56:50 +010041
42/* [0] = err/date, [1] = req, [2] = conn, [3] = resp, [4] = data */
43static struct eb_root timers[5] = {
44 EB_ROOT_UNIQUE, EB_ROOT_UNIQUE, EB_ROOT_UNIQUE,
45 EB_ROOT_UNIQUE, EB_ROOT_UNIQUE,
46};
47
48struct timer {
49 struct eb32_node node;
50 unsigned int count;
51};
52
Willy Tarreaud2201062010-05-27 18:17:30 +020053struct srv_st {
54 unsigned int st_cnt[6]; /* 0xx to 5xx */
55 unsigned int nb_ct, nb_rt, nb_ok;
56 unsigned long long cum_ct, cum_rt;
57 struct ebmb_node node;
58 /* don't put anything else here, the server name will be there */
59};
Willy Tarreau72c28532009-01-22 18:56:50 +010060
Willy Tarreauabe45b62010-10-28 20:33:46 +020061struct url_stat {
62 union {
63 struct ebpt_node url;
64 struct eb64_node val;
65 } node;
66 char *url;
67 unsigned long long total_time; /* sum(all reqs' times) */
68 unsigned long long total_time_ok; /* sum(all OK reqs' times) */
69 unsigned int nb_err, nb_req;
70};
71
Willy Tarreau72c28532009-01-22 18:56:50 +010072#define FILT_COUNT_ONLY 0x01
73#define FILT_INVERT 0x02
74#define FILT_QUIET 0x04
75#define FILT_ERRORS_ONLY 0x08
76#define FILT_ACC_DELAY 0x10
77#define FILT_ACC_COUNT 0x20
78#define FILT_GRAPH_TIMERS 0x40
Willy Tarreau214c2032009-02-20 11:02:32 +010079#define FILT_PERCENTILE 0x80
Willy Tarreau5bdfd962009-10-14 15:16:29 +020080#define FILT_TIME_RESP 0x100
81
82#define FILT_INVERT_ERRORS 0x200
83#define FILT_INVERT_TIME_RESP 0x400
Willy Tarreau72c28532009-01-22 18:56:50 +010084
Willy Tarreau0f423a72010-05-03 10:50:54 +020085#define FILT_COUNT_STATUS 0x800
Willy Tarreaud2201062010-05-27 18:17:30 +020086#define FILT_COUNT_SRV_STATUS 0x1000
Willy Tarreaud8fc1102010-09-12 17:56:16 +020087#define FILT_COUNT_TERM_CODES 0x2000
Willy Tarreau0f423a72010-05-03 10:50:54 +020088
Willy Tarreauabe45b62010-10-28 20:33:46 +020089#define FILT_COUNT_URL_ONLY 0x004000
90#define FILT_COUNT_URL_COUNT 0x008000
91#define FILT_COUNT_URL_ERR 0x010000
92#define FILT_COUNT_URL_TTOT 0x020000
93#define FILT_COUNT_URL_TAVG 0x040000
94#define FILT_COUNT_URL_TTOTO 0x080000
95#define FILT_COUNT_URL_TAVGO 0x100000
96#define FILT_COUNT_URL_ANY (FILT_COUNT_URL_ONLY|FILT_COUNT_URL_COUNT|FILT_COUNT_URL_ERR| \
97 FILT_COUNT_URL_TTOT|FILT_COUNT_URL_TAVG|FILT_COUNT_URL_TTOTO|FILT_COUNT_URL_TAVGO)
98
Willy Tarreau70c428f2011-07-10 17:27:40 +020099#define FILT_HTTP_ONLY 0x200000
100
Willy Tarreau72c28532009-01-22 18:56:50 +0100101unsigned int filter = 0;
102unsigned int filter_invert = 0;
Willy Tarreau214c2032009-02-20 11:02:32 +0100103const char *line;
Willy Tarreau72c28532009-01-22 18:56:50 +0100104
Willy Tarreau214c2032009-02-20 11:02:32 +0100105const char *fgets2(FILE *stream);
Willy Tarreau72c28532009-01-22 18:56:50 +0100106
107void die(const char *msg)
108{
109 fprintf(stderr,
110 "%s"
Willy Tarreauabe45b62010-10-28 20:33:46 +0200111 "Usage: halog [-q] [-c] [-v] {-gt|-pct|-st|-tc|-srv|-u|-uc|-ue|-ua|-ut|-uao|-uto}\n"
Willy Tarreau70c428f2011-07-10 17:27:40 +0200112 " [-s <skip>] [-e|-E] [-H] [-rt|-RT <time>] [-ad <delay>] [-ac <count>] < log\n"
Willy Tarreau72c28532009-01-22 18:56:50 +0100113 "\n",
114 msg ? msg : ""
115 );
116 exit(1);
117}
118
119
120/* return pointer to first char not part of current field starting at <p>. */
121const char *field_stop(const char *p)
122{
123 unsigned char c;
124
125 while (1) {
126 c = *(p++);
127 if (c > ' ')
128 continue;
129 if (c == ' ' || c == '\t' || c == 0)
130 break;
131 }
132 return p - 1;
133}
134
135/* return field <field> (starting from 1) in string <p>. Only consider
136 * contiguous spaces (or tabs) as one delimiter. May return pointer to
137 * last char if field is not found. Equivalent to awk '{print $field}'.
138 */
139const char *field_start(const char *p, int field)
140{
141 unsigned char c;
142 while (1) {
143 /* skip spaces */
144 while (1) {
145 c = *p;
146 if (c > ' ')
147 break;
148 if (c == ' ' || c == '\t')
149 goto next;
150 if (!c) /* end of line */
151 return p;
152 /* other char => new field */
153 break;
154 next:
155 p++;
156 }
157
158 /* start of field */
159 field--;
160 if (!field)
161 return p;
162
163 /* skip this field */
164 while (1) {
165 c = *(p++);
166 if (c > ' ')
167 continue;
168 if (c == ' ' || c == '\t')
169 break;
170 if (c == '\0')
171 return p;
172 }
173 }
174}
175
176/* keep only the <bits> higher bits of <i> */
177static inline unsigned int quantify_u32(unsigned int i, int bits)
178{
179 int high;
180
181 if (!bits)
182 return 0;
183
184 if (i)
185 high = fls_auto(i); // 1 to 32
186 else
187 high = 0;
188
189 if (high <= bits)
190 return i;
191
192 return i & ~((1 << (high - bits)) - 1);
193}
194
195/* keep only the <bits> higher bits of the absolute value of <i>, as well as
196 * its sign. */
197static inline int quantify(int i, int bits)
198{
199 if (i >= 0)
200 return quantify_u32(i, bits);
201 else
202 return -quantify_u32(-i, bits);
203}
204
205/* Insert timer value <v> into tree <r>. A pre-allocated node must be passed
206 * in <alloc>. It may be NULL, in which case the function will allocate it
207 * itself. It will be reset to NULL once consumed. The caller is responsible
208 * for freeing the node once not used anymore. The node where the value was
209 * inserted is returned.
210 */
211struct timer *insert_timer(struct eb_root *r, struct timer **alloc, int v)
212{
213 struct timer *t = *alloc;
214 struct eb32_node *n;
215
216 if (!t) {
217 t = calloc(sizeof(*t), 1);
218 if (unlikely(!t)) {
219 fprintf(stderr, "%s: not enough memory\n", __FUNCTION__);
220 exit(1);
221 }
222 }
223 t->node.key = quantify(v, QBITS); // keep only the higher QBITS bits
224
225 n = eb32i_insert(r, &t->node);
226 if (n == &t->node)
227 t = NULL; /* node inserted, will malloc next time */
228
229 *alloc = t;
230 return container_of(n, struct timer, node);
231}
232
233/* Insert value value <v> into tree <r>. A pre-allocated node must be passed
234 * in <alloc>. It may be NULL, in which case the function will allocate it
235 * itself. It will be reset to NULL once consumed. The caller is responsible
236 * for freeing the node once not used anymore. The node where the value was
237 * inserted is returned.
238 */
239struct timer *insert_value(struct eb_root *r, struct timer **alloc, int v)
240{
241 struct timer *t = *alloc;
242 struct eb32_node *n;
243
244 if (!t) {
245 t = calloc(sizeof(*t), 1);
246 if (unlikely(!t)) {
247 fprintf(stderr, "%s: not enough memory\n", __FUNCTION__);
248 exit(1);
249 }
250 }
251 t->node.key = v;
252
253 n = eb32i_insert(r, &t->node);
254 if (n == &t->node)
255 t = NULL; /* node inserted, will malloc next time */
256
257 *alloc = t;
258 return container_of(n, struct timer, node);
259}
260
261int str2ic(const char *s)
262{
263 int i = 0;
264 int j, k;
265
266 if (*s != '-') {
267 /* positive number */
268 while (1) {
269 j = (*s++) - '0';
270 k = i * 10;
271 if ((unsigned)j > 9)
272 break;
273 i = k + j;
274 }
275 } else {
276 /* negative number */
277 s++;
278 while (1) {
279 j = (*s++) - '0';
280 k = i * 10;
281 if ((unsigned)j > 9)
282 break;
283 i = k - j;
284 }
285 }
286
287 return i;
288}
289
290
291/* Equivalent to strtoul with a length. */
292static inline unsigned int __strl2ui(const char *s, int len)
293{
294 unsigned int i = 0;
295 while (len-- > 0) {
296 i = i * 10 - '0';
297 i += (unsigned char)*s++;
298 }
299 return i;
300}
301
302unsigned int strl2ui(const char *s, int len)
303{
304 return __strl2ui(s, len);
305}
306
307/* Convert "[04/Dec/2008:09:49:40.555]" to an integer equivalent to the time of
308 * the day in milliseconds. It returns -1 for all unparsable values. The parser
309 * looks ugly but gcc emits far better code that way.
310 */
311int convert_date(const char *field)
312{
313 unsigned int h, m, s, ms;
314 unsigned char c;
315 const char *b, *e;
316
317 h = m = s = ms = 0;
318 e = field;
319
320 /* skip the date */
321 while (1) {
322 c = *(e++);
323 if (c == ':')
324 break;
325 if (!c)
326 goto out_err;
327 }
328
329 /* hour + ':' */
330 b = e;
331 while (1) {
332 c = *(e++) - '0';
333 if (c > 9)
334 break;
335 h = h * 10 + c;
336 }
337 if (c == (unsigned char)(0 - '0'))
338 goto out_err;
339
340 /* minute + ':' */
341 b = e;
342 while (1) {
343 c = *(e++) - '0';
344 if (c > 9)
345 break;
346 m = m * 10 + c;
347 }
348 if (c == (unsigned char)(0 - '0'))
349 goto out_err;
350
351 /* second + '.' or ']' */
352 b = e;
353 while (1) {
354 c = *(e++) - '0';
355 if (c > 9)
356 break;
357 s = s * 10 + c;
358 }
359 if (c == (unsigned char)(0 - '0'))
360 goto out_err;
361
362 /* if there's a '.', we have milliseconds */
363 if (c == (unsigned char)('.' - '0')) {
364 /* millisecond second + ']' */
365 b = e;
366 while (1) {
367 c = *(e++) - '0';
368 if (c > 9)
369 break;
370 ms = ms * 10 + c;
371 }
372 if (c == (unsigned char)(0 - '0'))
373 goto out_err;
374 }
375 return (((h * 60) + m) * 60 + s) * 1000 + ms;
376 out_err:
377 return -1;
378}
379
380void truncated_line(int linenum, const char *line)
381{
382 if (!(filter & FILT_QUIET))
383 fprintf(stderr, "Truncated line %d: %s\n", linenum, line);
384}
385
386int main(int argc, char **argv)
387{
388 const char *b, *e, *p;
389 const char *output_file = NULL;
390 int f, tot, last, linenum, err, parse_err;
391 struct timer *t = NULL, *t2;
392 struct eb32_node *n;
Willy Tarreauabe45b62010-10-28 20:33:46 +0200393 struct url_stat *ustat = NULL;
394 struct ebpt_node *ebpt_old;
Willy Tarreau72c28532009-01-22 18:56:50 +0100395 int val, test;
396 int array[5];
397 int filter_acc_delay = 0, filter_acc_count = 0;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200398 int filter_time_resp = 0;
Willy Tarreau72c28532009-01-22 18:56:50 +0100399 int skip_fields = 1;
400
401 argc--; argv++;
402 while (argc > 0) {
403 if (*argv[0] != '-')
404 break;
405
406 if (strcmp(argv[0], "-ad") == 0) {
407 if (argc < 2) die("missing option for -ad");
408 argc--; argv++;
409 filter |= FILT_ACC_DELAY;
410 filter_acc_delay = atol(*argv);
411 }
412 else if (strcmp(argv[0], "-ac") == 0) {
413 if (argc < 2) die("missing option for -ac");
414 argc--; argv++;
415 filter |= FILT_ACC_COUNT;
416 filter_acc_count = atol(*argv);
417 }
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200418 else if (strcmp(argv[0], "-rt") == 0) {
419 if (argc < 2) die("missing option for -rt");
420 argc--; argv++;
421 filter |= FILT_TIME_RESP;
422 filter_time_resp = atol(*argv);
423 }
424 else if (strcmp(argv[0], "-RT") == 0) {
425 if (argc < 2) die("missing option for -RT");
426 argc--; argv++;
427 filter |= FILT_TIME_RESP | FILT_INVERT_TIME_RESP;
428 filter_time_resp = atol(*argv);
429 }
Willy Tarreau72c28532009-01-22 18:56:50 +0100430 else if (strcmp(argv[0], "-s") == 0) {
431 if (argc < 2) die("missing option for -s");
432 argc--; argv++;
433 skip_fields = atol(*argv);
434 }
435 else if (strcmp(argv[0], "-e") == 0)
436 filter |= FILT_ERRORS_ONLY;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200437 else if (strcmp(argv[0], "-E") == 0)
438 filter |= FILT_ERRORS_ONLY | FILT_INVERT_ERRORS;
Willy Tarreau70c428f2011-07-10 17:27:40 +0200439 else if (strcmp(argv[0], "-H") == 0)
440 filter |= FILT_HTTP_ONLY;
Willy Tarreau72c28532009-01-22 18:56:50 +0100441 else if (strcmp(argv[0], "-c") == 0)
442 filter |= FILT_COUNT_ONLY;
443 else if (strcmp(argv[0], "-q") == 0)
444 filter |= FILT_QUIET;
445 else if (strcmp(argv[0], "-v") == 0)
446 filter_invert = !filter_invert;
447 else if (strcmp(argv[0], "-gt") == 0)
448 filter |= FILT_GRAPH_TIMERS;
Willy Tarreau214c2032009-02-20 11:02:32 +0100449 else if (strcmp(argv[0], "-pct") == 0)
450 filter |= FILT_PERCENTILE;
Willy Tarreau0f423a72010-05-03 10:50:54 +0200451 else if (strcmp(argv[0], "-st") == 0)
452 filter |= FILT_COUNT_STATUS;
Willy Tarreaud2201062010-05-27 18:17:30 +0200453 else if (strcmp(argv[0], "-srv") == 0)
454 filter |= FILT_COUNT_SRV_STATUS;
Willy Tarreaud8fc1102010-09-12 17:56:16 +0200455 else if (strcmp(argv[0], "-tc") == 0)
456 filter |= FILT_COUNT_TERM_CODES;
Willy Tarreauabe45b62010-10-28 20:33:46 +0200457 else if (strcmp(argv[0], "-u") == 0)
458 filter |= FILT_COUNT_URL_ONLY;
459 else if (strcmp(argv[0], "-uc") == 0)
460 filter |= FILT_COUNT_URL_COUNT;
461 else if (strcmp(argv[0], "-ue") == 0)
462 filter |= FILT_COUNT_URL_ERR;
463 else if (strcmp(argv[0], "-ua") == 0)
464 filter |= FILT_COUNT_URL_TAVG;
465 else if (strcmp(argv[0], "-ut") == 0)
466 filter |= FILT_COUNT_URL_TTOT;
467 else if (strcmp(argv[0], "-uao") == 0)
468 filter |= FILT_COUNT_URL_TAVGO;
469 else if (strcmp(argv[0], "-uto") == 0)
470 filter |= FILT_COUNT_URL_TTOTO;
Willy Tarreau72c28532009-01-22 18:56:50 +0100471 else if (strcmp(argv[0], "-o") == 0) {
472 if (output_file)
473 die("Fatal: output file name already specified.\n");
474 if (argc < 2)
475 die("Fatal: missing output file name.\n");
476 output_file = argv[1];
477 }
478 argc--;
479 argv++;
480 }
481
482 if (!filter)
483 die("No action specified.\n");
484
485 if (filter & FILT_ACC_COUNT && !filter_acc_count)
486 filter_acc_count=1;
487
488 if (filter & FILT_ACC_DELAY && !filter_acc_delay)
489 filter_acc_delay = 1;
490
491 linenum = 0;
492 tot = 0;
493 parse_err = 0;
494
Willy Tarreau214c2032009-02-20 11:02:32 +0100495 while ((line = fgets2(stdin)) != NULL) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100496 linenum++;
497
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200498 test = 1;
Willy Tarreau70c428f2011-07-10 17:27:40 +0200499 if (unlikely(filter & FILT_HTTP_ONLY)) {
500 /* only report lines with at least 4 timers */
501 b = field_start(line, TIME_FIELD + skip_fields);
502 if (!*b) {
503 truncated_line(linenum, line);
504 continue;
505 }
506
507 e = field_stop(b + 1);
508 /* we have field TIME_FIELD in [b]..[e-1] */
509
510 p = b;
511 err = 0;
512 f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200513 while (!SEP(*p)) {
Willy Tarreau70c428f2011-07-10 17:27:40 +0200514 if (++f == 4)
515 break;
516 SKIP_CHAR(p, '/');
517 }
518 test &= (f >= 4);
519 }
520
Willy Tarreau2651ac32010-05-05 12:20:19 +0200521 if (unlikely(filter & FILT_TIME_RESP)) {
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200522 int tps;
523
524 /* only report lines with response times larger than filter_time_resp */
525 b = field_start(line, TIME_FIELD + skip_fields);
526 if (!*b) {
527 truncated_line(linenum, line);
528 continue;
529 }
530
531 e = field_stop(b + 1);
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200532 /* we have field TIME_FIELD in [b]..[e-1], let's check only the response time */
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200533
534 p = b;
535 err = 0;
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200536 f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200537 while (!SEP(*p)) {
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200538 tps = str2ic(p);
539 if (tps < 0) {
540 tps = -1;
541 err = 1;
542 }
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200543 if (++f == 4)
544 break;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200545 SKIP_CHAR(p, '/');
546 }
547
548 if (f < 4) {
549 parse_err++;
550 continue;
551 }
552
553 test &= (tps >= filter_time_resp) ^ !!(filter & FILT_INVERT_TIME_RESP);
554 }
555
Willy Tarreau2651ac32010-05-05 12:20:19 +0200556 if (unlikely(filter & FILT_ERRORS_ONLY)) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100557 /* only report erroneous status codes */
558 b = field_start(line, STATUS_FIELD + skip_fields);
559 if (!*b) {
560 truncated_line(linenum, line);
561 continue;
562 }
563 if (*b == '-') {
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200564 test &= !!(filter & FILT_INVERT_ERRORS);
Willy Tarreau72c28532009-01-22 18:56:50 +0100565 } else {
566 val = strl2ui(b, 3);
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200567 test &= (val >= 500 && val <= 599) ^ !!(filter & FILT_INVERT_ERRORS);
Willy Tarreau72c28532009-01-22 18:56:50 +0100568 }
Willy Tarreau72c28532009-01-22 18:56:50 +0100569 }
570
Willy Tarreau0f423a72010-05-03 10:50:54 +0200571 test ^= filter_invert;
572 if (!test)
573 continue;
574
Willy Tarreau2651ac32010-05-05 12:20:19 +0200575 if (unlikely(filter & (FILT_ACC_COUNT|FILT_ACC_DELAY))) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100576 b = field_start(line, ACCEPT_FIELD + skip_fields);
577 if (!*b) {
578 truncated_line(linenum, line);
579 continue;
580 }
581
Willy Tarreau214c2032009-02-20 11:02:32 +0100582 tot++;
Willy Tarreau72c28532009-01-22 18:56:50 +0100583 val = convert_date(b);
584 //printf("date=%s => %d\n", b, val);
585 if (val < 0) {
586 parse_err++;
587 continue;
588 }
589
590 t2 = insert_value(&timers[0], &t, val);
591 t2->count++;
592 continue;
593 }
594
Willy Tarreau2651ac32010-05-05 12:20:19 +0200595 if (unlikely(filter & (FILT_GRAPH_TIMERS|FILT_PERCENTILE))) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100596 int f;
597
598 b = field_start(line, TIME_FIELD + skip_fields);
599 if (!*b) {
600 truncated_line(linenum, line);
601 continue;
602 }
603
604 e = field_stop(b + 1);
605 /* we have field TIME_FIELD in [b]..[e-1] */
606
607 p = b;
608 err = 0;
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200609 f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200610 while (!SEP(*p)) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100611 array[f] = str2ic(p);
612 if (array[f] < 0) {
613 array[f] = -1;
614 err = 1;
615 }
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200616 if (++f == 5)
617 break;
Willy Tarreau72c28532009-01-22 18:56:50 +0100618 SKIP_CHAR(p, '/');
619 }
620
621 if (f < 5) {
622 parse_err++;
623 continue;
624 }
625
626 /* if we find at least one negative time, we count one error
627 * with a time equal to the total session time. This will
628 * emphasize quantum timing effects associated to known
629 * timeouts. Note that on some buggy machines, it is possible
630 * that the total time is negative, hence the reason to reset
631 * it.
632 */
Willy Tarreau214c2032009-02-20 11:02:32 +0100633
634 if (filter & FILT_GRAPH_TIMERS) {
635 if (err) {
636 if (array[4] < 0)
637 array[4] = -1;
638 t2 = insert_timer(&timers[0], &t, array[4]); // total time
639 t2->count++;
640 } else {
641 int v;
Willy Tarreau72c28532009-01-22 18:56:50 +0100642
Willy Tarreau214c2032009-02-20 11:02:32 +0100643 t2 = insert_timer(&timers[1], &t, array[0]); t2->count++; // req
644 t2 = insert_timer(&timers[2], &t, array[2]); t2->count++; // conn
645 t2 = insert_timer(&timers[3], &t, array[3]); t2->count++; // resp
646
647 v = array[4] - array[0] - array[1] - array[2] - array[3]; // data time
648 if (v < 0 && !(filter & FILT_QUIET))
649 fprintf(stderr, "ERR: %s (%d %d %d %d %d => %d)\n",
650 line, array[0], array[1], array[2], array[3], array[4], v);
651 t2 = insert_timer(&timers[4], &t, v); t2->count++;
652 tot++;
653 }
654 } else { /* percentile */
655 if (err) {
656 if (array[4] < 0)
657 array[4] = -1;
658 t2 = insert_value(&timers[0], &t, array[4]); // total time
659 t2->count++;
660 } else {
661 int v;
Willy Tarreau72c28532009-01-22 18:56:50 +0100662
Willy Tarreau214c2032009-02-20 11:02:32 +0100663 t2 = insert_value(&timers[1], &t, array[0]); t2->count++; // req
664 t2 = insert_value(&timers[2], &t, array[2]); t2->count++; // conn
665 t2 = insert_value(&timers[3], &t, array[3]); t2->count++; // resp
666
667 v = array[4] - array[0] - array[1] - array[2] - array[3]; // data time
668 if (v < 0 && !(filter & FILT_QUIET))
669 fprintf(stderr, "ERR: %s (%d %d %d %d %d => %d)\n",
670 line, array[0], array[1], array[2], array[3], array[4], v);
671 t2 = insert_value(&timers[4], &t, v); t2->count++;
672 tot++;
673 }
Willy Tarreau72c28532009-01-22 18:56:50 +0100674 }
675 continue;
676 }
677
Willy Tarreau2651ac32010-05-05 12:20:19 +0200678 if (unlikely(filter & FILT_COUNT_STATUS)) {
Willy Tarreau54170812010-09-13 22:50:49 +0200679 /* first, let's ensure that the line is a traffic line (beginning
680 * with an IP address)
681 */
682 b = field_start(line, SOURCE_FIELD + skip_fields);
683 if (*b < '0' || *b > '9') {
684 parse_err++;
685 continue;
686 }
687
688 b = field_start(b, STATUS_FIELD - SOURCE_FIELD + 1);
Willy Tarreau0f423a72010-05-03 10:50:54 +0200689 if (!*b) {
690 truncated_line(linenum, line);
691 continue;
692 }
693 val = str2ic(b);
694
695 t2 = insert_value(&timers[0], &t, val);
696 t2->count++;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200697 continue;
Willy Tarreau0f423a72010-05-03 10:50:54 +0200698 }
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200699
Willy Tarreaud8fc1102010-09-12 17:56:16 +0200700 if (unlikely(filter & FILT_COUNT_TERM_CODES)) {
Willy Tarreau54170812010-09-13 22:50:49 +0200701 /* first, let's ensure that the line is a traffic line (beginning
702 * with an IP address)
703 */
704 b = field_start(line, SOURCE_FIELD + skip_fields);
705 if (*b < '0' || *b > '9') {
706 parse_err++;
707 continue;
708 }
709
710 b = field_start(b, TERM_CODES_FIELD - SOURCE_FIELD + 1);
Willy Tarreaud8fc1102010-09-12 17:56:16 +0200711 if (!*b) {
712 truncated_line(linenum, line);
713 continue;
714 }
715 val = 256 * b[0] + b[1];
716
717 t2 = insert_value(&timers[0], &t, val);
718 t2->count++;
719 continue;
720 }
721
Willy Tarreaud2201062010-05-27 18:17:30 +0200722 if (unlikely(filter & FILT_COUNT_SRV_STATUS)) {
723 char *srv_name;
724 struct ebmb_node *srv_node;
725 struct srv_st *srv;
726
727 /* first, let's ensure that the line is a traffic line (beginning
728 * with an IP address)
729 */
730 b = field_start(line, SOURCE_FIELD + skip_fields);
731 if (*b < '0' || *b > '9') {
732 parse_err++;
733 continue;
734 }
735
736 /* the server field is before the status field, so let's
737 * parse them in the proper order.
738 */
739 b = field_start(b, SERVER_FIELD - SOURCE_FIELD + 1);
740 if (!*b) {
741 truncated_line(linenum, line);
742 continue;
743 }
744
745 e = field_stop(b + 1); /* we have the server name in [b]..[e-1] */
746
747 /* the chance that a server name already exists is extremely high,
748 * so let's perform a normal lookup first.
749 */
750 srv_node = ebst_lookup_len(&timers[0], b, e - b);
751 srv = container_of(srv_node, struct srv_st, node);
752
753 if (!srv_node) {
754 /* server not yet in the tree, let's create it */
755 srv = (void *)calloc(1, sizeof(struct srv_st) + e - b + 1);
756 srv_node = &srv->node;
757 memcpy(&srv_node->key, b, e - b);
758 srv_node->key[e - b] = '\0';
759 ebst_insert(&timers[0], srv_node);
760 }
761
762 /* let's collect the connect and response times */
763 b = field_start(e, TIME_FIELD - SERVER_FIELD);
764 if (!*b) {
765 truncated_line(linenum, line);
766 continue;
767 }
768
769 e = field_stop(b + 1);
770 /* we have field TIME_FIELD in [b]..[e-1] */
771
772 p = b;
773 err = 0;
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200774 f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200775 while (!SEP(*p)) {
Willy Tarreaud2201062010-05-27 18:17:30 +0200776 array[f] = str2ic(p);
777 if (array[f] < 0) {
778 array[f] = -1;
779 err = 1;
780 }
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200781 if (++f == 5)
782 break;
Willy Tarreaud2201062010-05-27 18:17:30 +0200783 SKIP_CHAR(p, '/');
784 }
785
786 if (f < 5) {
787 parse_err++;
788 continue;
789 }
790
791 /* OK we have our timers in array[2,3] */
792 if (!err)
793 srv->nb_ok++;
794
795 if (array[2] >= 0) {
796 srv->cum_ct += array[2];
797 srv->nb_ct++;
798 }
799
800 if (array[3] >= 0) {
801 srv->cum_rt += array[3];
802 srv->nb_rt++;
803 }
804
805 /* we're interested in the 5 HTTP status classes (1xx ... 5xx), and
806 * the invalid ones which will be reported as 0.
807 */
808 b = field_start(e, STATUS_FIELD - TIME_FIELD);
809 if (!*b) {
810 truncated_line(linenum, line);
811 continue;
812 }
813
814 val = 0;
815 if (*b >= '1' && *b <= '5')
816 val = *b - '0';
817
818 srv->st_cnt[val]++;
819 continue;
820 }
821
Willy Tarreauabe45b62010-10-28 20:33:46 +0200822 if (unlikely(filter & FILT_COUNT_URL_ANY)) {
823 /* first, let's ensure that the line is a traffic line (beginning
824 * with an IP address)
825 */
826 b = field_start(line, SOURCE_FIELD + skip_fields); // avg 95 ns per line
827 if (*b < '0' || *b > '9') {
828 parse_err++;
829 continue;
830 }
831
832 /* let's collect the response time */
833 b = field_start(field_stop(b + 1), TIME_FIELD - SOURCE_FIELD); // avg 115 ns per line
834 if (!*b) {
835 truncated_line(linenum, line);
836 continue;
837 }
838
839 /* we have the field TIME_FIELD starting at <b>. We'll
840 * parse the 5 timers to detect errors, it takes avg 55 ns per line.
841 */
842 e = b; err = 0; f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200843 while (!SEP(*e)) {
Willy Tarreauabe45b62010-10-28 20:33:46 +0200844 array[f] = str2ic(e);
845 if (array[f] < 0) {
846 array[f] = -1;
847 err = 1;
848 }
849 if (++f == 5)
850 break;
851 SKIP_CHAR(e, '/');
852 }
853 if (f < 5) {
854 parse_err++;
855 continue;
856 }
857
858 /* OK we have our timers in array[3], and err is >0 if at
859 * least one -1 was seen. <e> points to the first char of
860 * the last timer. Let's prepare a new node with that.
861 */
862 if (unlikely(!ustat))
863 ustat = calloc(1, sizeof(*ustat));
864
865 ustat->nb_err = err;
866 ustat->nb_req = 1;
867
868 /* use array[4] = total time in case of error */
869 ustat->total_time = (array[3] >= 0) ? array[3] : array[4];
870 ustat->total_time_ok = (array[3] >= 0) ? array[3] : 0;
871
872 /* the line may be truncated because of a bad request or anything like this,
873 * without a method. Also, if it does not begin with an quote, let's skip to
874 * the next field because it's a capture. Let's fall back to the "method" itself
875 * if there's nothing else.
876 */
877 e = field_start(e, METH_FIELD - TIME_FIELD + 1); // avg 100 ns per line
878 while (*e != '"' && *e)
879 e = field_start(e, 2);
880
881 if (!*e) {
882 truncated_line(linenum, line);
883 continue;
884 }
885
886 b = field_start(e, URL_FIELD - METH_FIELD + 1); // avg 40 ns per line
887 if (!*b)
888 b = e;
889
890 /* stop at end of field or first ';' or '?', takes avg 64 ns per line */
891 e = b;
892 do {
893 if (*e == ' ' || *e == '?' || *e == ';' || *e == '\t') {
894 *(char *)e = 0;
895 break;
896 }
897 e++;
898 } while (*e);
899
900 /* now instead of copying the URL for a simple lookup, we'll link
901 * to it from the node we're trying to insert. If it returns a
902 * different value, it was already there. Otherwise we just have
903 * to dynamically realloc an entry using strdup().
904 */
905 ustat->node.url.key = (char *)b;
906 ebpt_old = ebis_insert(&timers[0], &ustat->node.url);
907
908 if (ebpt_old != &ustat->node.url) {
909 struct url_stat *ustat_old;
910 /* node was already there, let's update previous one */
911 ustat_old = container_of(ebpt_old, struct url_stat, node.url);
912 ustat_old->nb_req ++;
913 ustat_old->nb_err += ustat->nb_err;
914 ustat_old->total_time += ustat->total_time;
915 ustat_old->total_time_ok += ustat->total_time_ok;
916 } else {
917 ustat->url = ustat->node.url.key = strdup(ustat->node.url.key);
918 ustat = NULL; /* node was used */
919 }
920
921 continue;
922 }
923
Willy Tarreau72c28532009-01-22 18:56:50 +0100924 /* all other cases mean we just want to count lines */
925 tot++;
Willy Tarreau2651ac32010-05-05 12:20:19 +0200926 if (unlikely(!(filter & FILT_COUNT_ONLY)))
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200927 puts(line);
Willy Tarreau72c28532009-01-22 18:56:50 +0100928 }
929
930 if (t)
931 free(t);
932
933 if (filter & FILT_COUNT_ONLY) {
934 printf("%d\n", tot);
935 exit(0);
936 }
937
Willy Tarreau72c28532009-01-22 18:56:50 +0100938 if (filter & (FILT_ACC_COUNT|FILT_ACC_DELAY)) {
939 /* sort and count all timers. Output will look like this :
940 * <accept_date> <delta_ms from previous one> <nb entries>
941 */
942 n = eb32_first(&timers[0]);
943
944 if (n)
945 last = n->key;
946 while (n) {
947 unsigned int d, h, m, s, ms;
948
949 t = container_of(n, struct timer, node);
950 h = n->key;
951 d = h - last;
952 last = h;
953
954 if (d >= filter_acc_delay && t->count >= filter_acc_count) {
955 ms = h % 1000; h = h / 1000;
956 s = h % 60; h = h / 60;
957 m = h % 60; h = h / 60;
958 tot++;
959 printf("%02d:%02d:%02d.%03d %d %d %d\n", h, m, s, ms, last, d, t->count);
960 }
961 n = eb32_next(n);
962 }
963 }
964 else if (filter & FILT_GRAPH_TIMERS) {
965 /* sort all timers */
966 for (f = 0; f < 5; f++) {
967 struct eb32_node *n;
968 int val;
969
970 val = 0;
971 n = eb32_first(&timers[f]);
972 while (n) {
973 int i;
974 double d;
975
976 t = container_of(n, struct timer, node);
977 last = n->key;
978 val = t->count;
979
980 i = (last < 0) ? -last : last;
981 i = fls_auto(i) - QBITS;
982
983 if (i > 0)
984 d = val / (double)(1 << i);
985 else
986 d = val;
987
988 if (d > 0.0) {
989 printf("%d %d %f\n", f, last, d+1.0);
990 tot++;
991 }
992
993 n = eb32_next(n);
994 }
Willy Tarreau214c2032009-02-20 11:02:32 +0100995 }
996 }
997 else if (filter & FILT_PERCENTILE) {
998 /* report timers by percentile :
999 * <percent> <total> <max_req_time> <max_conn_time> <max_resp_time> <max_data_time>
1000 * We don't count errs.
1001 */
1002 struct eb32_node *n[5];
1003 unsigned long cum[5];
1004 double step;
1005
Willy Tarreau910ba4b2009-11-17 10:16:19 +01001006 if (!tot)
1007 goto empty;
1008
Willy Tarreau214c2032009-02-20 11:02:32 +01001009 for (f = 1; f < 5; f++) {
1010 n[f] = eb32_first(&timers[f]);
1011 cum[f] = container_of(n[f], struct timer, node)->count;
1012 }
1013
1014 for (step = 1; step <= 1000;) {
1015 unsigned int thres = tot * (step / 1000.0);
1016
1017 printf("%3.1f %d ", step/10.0, thres);
1018 for (f = 1; f < 5; f++) {
1019 struct eb32_node *next;
1020 while (cum[f] < thres) {
1021 /* need to find other keys */
1022 next = eb32_next(n[f]);
1023 if (!next)
1024 break;
1025 n[f] = next;
1026 cum[f] += container_of(next, struct timer, node)->count;
1027 }
1028
1029 /* value still within $step % of total */
1030 printf("%d ", n[f]->key);
1031 }
1032 putchar('\n');
1033 if (step >= 100 && step < 900)
1034 step += 50; // jump 5% by 5% between those steps.
1035 else if (step >= 20 && step < 980)
1036 step += 10;
1037 else
1038 step += 1;
Willy Tarreau72c28532009-01-22 18:56:50 +01001039 }
1040 }
Willy Tarreau0f423a72010-05-03 10:50:54 +02001041 else if (filter & FILT_COUNT_STATUS) {
1042 /* output all statuses in the form of <status> <occurrences> */
1043 n = eb32_first(&timers[0]);
1044 while (n) {
1045 t = container_of(n, struct timer, node);
1046 printf("%d %d\n", n->key, t->count);
1047 n = eb32_next(n);
1048 }
1049 }
Willy Tarreaud2201062010-05-27 18:17:30 +02001050 else if (unlikely(filter & FILT_COUNT_SRV_STATUS)) {
1051 char *srv_name;
1052 struct ebmb_node *srv_node;
1053 struct srv_st *srv;
1054
1055 printf("#srv_name 1xx 2xx 3xx 4xx 5xx other tot_req req_ok pct_ok avg_ct avg_rt\n");
1056
1057 srv_node = ebmb_first(&timers[0]);
1058 while (srv_node) {
1059 int tot_rq;
1060
1061 srv = container_of(srv_node, struct srv_st, node);
1062
1063 tot_rq = 0;
1064 for (f = 0; f <= 5; f++)
1065 tot_rq += srv->st_cnt[f];
1066
1067 printf("%s %d %d %d %d %d %d %d %d %.1f %d %d\n",
1068 srv_node->key, srv->st_cnt[1], srv->st_cnt[2],
1069 srv->st_cnt[3], srv->st_cnt[4], srv->st_cnt[5], srv->st_cnt[0],
1070 tot_rq,
1071 srv->nb_ok, (double)srv->nb_ok * 100.0 / (tot_rq?tot_rq:1),
1072 (int)(srv->cum_ct / (srv->nb_ct?srv->nb_ct:1)), (int)(srv->cum_rt / (srv->nb_rt?srv->nb_rt:1)));
1073 srv_node = ebmb_next(srv_node);
1074 tot++;
1075 }
1076 }
Willy Tarreaud8fc1102010-09-12 17:56:16 +02001077 else if (filter & FILT_COUNT_TERM_CODES) {
1078 /* output all statuses in the form of <code> <occurrences> */
1079 n = eb32_first(&timers[0]);
1080 while (n) {
1081 t = container_of(n, struct timer, node);
1082 printf("%c%c %d\n", (n->key >> 8), (n->key) & 255, t->count);
1083 n = eb32_next(n);
1084 }
1085 }
Willy Tarreauabe45b62010-10-28 20:33:46 +02001086 else if (unlikely(filter & FILT_COUNT_URL_ANY)) {
1087 char *srv_name;
1088 struct eb_node *node, *next;
1089
1090 if (!(filter & FILT_COUNT_URL_ONLY)) {
1091 /* we have to sort on another criterion. We'll use timers[1] for the
1092 * destination tree.
1093 */
1094
1095 timers[1] = EB_ROOT; /* reconfigure to accept duplicates */
1096 for (node = eb_first(&timers[0]); node; node = next) {
1097 next = eb_next(node);
1098 eb_delete(node);
1099
1100 ustat = container_of(node, struct url_stat, node.url.node);
1101
1102 if (filter & FILT_COUNT_URL_COUNT)
1103 ustat->node.val.key = ustat->nb_req;
1104 else if (filter & FILT_COUNT_URL_ERR)
1105 ustat->node.val.key = ustat->nb_err;
1106 else if (filter & FILT_COUNT_URL_TTOT)
1107 ustat->node.val.key = ustat->total_time;
1108 else if (filter & FILT_COUNT_URL_TAVG)
1109 ustat->node.val.key = ustat->nb_req ? ustat->total_time / ustat->nb_req : 0;
1110 else if (filter & FILT_COUNT_URL_TTOTO)
1111 ustat->node.val.key = ustat->total_time_ok;
1112 else if (filter & FILT_COUNT_URL_TAVGO)
1113 ustat->node.val.key = (ustat->nb_req - ustat->nb_err) ? ustat->total_time_ok / (ustat->nb_req - ustat->nb_err) : 0;
1114 else
1115 ustat->node.val.key = 0;
1116
1117 eb64_insert(&timers[1], &ustat->node.val);
1118 }
1119 /* switch trees */
1120 timers[0] = timers[1];
1121 }
1122
1123 printf("#req err ttot tavg oktot okavg url\n");
1124
1125 /* scan the tree in its reverse sorting order */
1126 node = eb_last(&timers[0]);
1127 while (node) {
1128 ustat = container_of(node, struct url_stat, node.url.node);
1129 printf("%d %d %Ld %Ld %Ld %Ld %s\n",
1130 ustat->nb_req,
1131 ustat->nb_err,
1132 ustat->total_time,
1133 ustat->nb_req ? ustat->total_time / ustat->nb_req : 0,
1134 ustat->total_time_ok,
1135 (ustat->nb_req - ustat->nb_err) ? ustat->total_time_ok / (ustat->nb_req - ustat->nb_err) : 0,
1136 ustat->url);
1137
1138 node = eb_prev(node);
1139 tot++;
1140 }
1141 }
Willy Tarreaud2201062010-05-27 18:17:30 +02001142
Willy Tarreau910ba4b2009-11-17 10:16:19 +01001143 empty:
Willy Tarreau72c28532009-01-22 18:56:50 +01001144 if (!(filter & FILT_QUIET))
1145 fprintf(stderr, "%d lines in, %d lines out, %d parsing errors\n",
1146 linenum, tot, parse_err);
1147 exit(0);
1148}
1149
1150/*
1151 * Local variables:
1152 * c-indent-level: 8
1153 * c-basic-offset: 8
1154 * End:
1155 */