blob: 32b4a7fb33225a64c265d57970678743c58e0378 [file] [log] [blame]
Willy Tarreau72c28532009-01-22 18:56:50 +01001/*
2 * haproxy log time reporter
3 *
4 * Copyright 2000-2009 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/*
Willy Tarreau214c2032009-02-20 11:02:32 +010014 * gcc -O2 -o halog2 halog2.c -Iinclude src/ebtree.c src/eb32tree.c fgets2.c
Willy Tarreau72c28532009-01-22 18:56:50 +010015 *
16 * Usage:
17 * $0 [ min_delay [ min_count [ field_shift ]]] < haproxy.log
18 * Note: if min_delay < 0, it only outputs lines with status codes 5xx.
19 */
20
21#include <errno.h>
22#include <fcntl.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <syslog.h>
26#include <string.h>
27#include <unistd.h>
28#include <ctype.h>
29
30#include <common/eb32tree.h>
31
32#define ACCEPT_FIELD 6
33#define TIME_FIELD 9
34#define STATUS_FIELD 10
35#define CONN_FIELD 15
36#define MAXLINE 16384
37#define QBITS 4
38
39#define SKIP_CHAR(p,c) do { while (1) if (!*p) break; else if (*(p++) == c) break; } while (0)
40
41/* [0] = err/date, [1] = req, [2] = conn, [3] = resp, [4] = data */
42static struct eb_root timers[5] = {
43 EB_ROOT_UNIQUE, EB_ROOT_UNIQUE, EB_ROOT_UNIQUE,
44 EB_ROOT_UNIQUE, EB_ROOT_UNIQUE,
45};
46
47struct timer {
48 struct eb32_node node;
49 unsigned int count;
50};
51
52
53#define FILT_COUNT_ONLY 0x01
54#define FILT_INVERT 0x02
55#define FILT_QUIET 0x04
56#define FILT_ERRORS_ONLY 0x08
57#define FILT_ACC_DELAY 0x10
58#define FILT_ACC_COUNT 0x20
59#define FILT_GRAPH_TIMERS 0x40
Willy Tarreau214c2032009-02-20 11:02:32 +010060#define FILT_PERCENTILE 0x80
Willy Tarreau72c28532009-01-22 18:56:50 +010061
62unsigned int filter = 0;
63unsigned int filter_invert = 0;
Willy Tarreau214c2032009-02-20 11:02:32 +010064const char *line;
Willy Tarreau72c28532009-01-22 18:56:50 +010065
Willy Tarreau214c2032009-02-20 11:02:32 +010066const char *fgets2(FILE *stream);
Willy Tarreau72c28532009-01-22 18:56:50 +010067
68void die(const char *msg)
69{
70 fprintf(stderr,
71 "%s"
Willy Tarreau214c2032009-02-20 11:02:32 +010072 "Usage: halog [-c] [-v] [-gt] [-pct] [-s <skip>] [-e] [-ad <delay>] [-ac <count>] < file.log\n"
Willy Tarreau72c28532009-01-22 18:56:50 +010073 "\n",
74 msg ? msg : ""
75 );
76 exit(1);
77}
78
79
80/* return pointer to first char not part of current field starting at <p>. */
81const char *field_stop(const char *p)
82{
83 unsigned char c;
84
85 while (1) {
86 c = *(p++);
87 if (c > ' ')
88 continue;
89 if (c == ' ' || c == '\t' || c == 0)
90 break;
91 }
92 return p - 1;
93}
94
95/* return field <field> (starting from 1) in string <p>. Only consider
96 * contiguous spaces (or tabs) as one delimiter. May return pointer to
97 * last char if field is not found. Equivalent to awk '{print $field}'.
98 */
99const char *field_start(const char *p, int field)
100{
101 unsigned char c;
102 while (1) {
103 /* skip spaces */
104 while (1) {
105 c = *p;
106 if (c > ' ')
107 break;
108 if (c == ' ' || c == '\t')
109 goto next;
110 if (!c) /* end of line */
111 return p;
112 /* other char => new field */
113 break;
114 next:
115 p++;
116 }
117
118 /* start of field */
119 field--;
120 if (!field)
121 return p;
122
123 /* skip this field */
124 while (1) {
125 c = *(p++);
126 if (c > ' ')
127 continue;
128 if (c == ' ' || c == '\t')
129 break;
130 if (c == '\0')
131 return p;
132 }
133 }
134}
135
136/* keep only the <bits> higher bits of <i> */
137static inline unsigned int quantify_u32(unsigned int i, int bits)
138{
139 int high;
140
141 if (!bits)
142 return 0;
143
144 if (i)
145 high = fls_auto(i); // 1 to 32
146 else
147 high = 0;
148
149 if (high <= bits)
150 return i;
151
152 return i & ~((1 << (high - bits)) - 1);
153}
154
155/* keep only the <bits> higher bits of the absolute value of <i>, as well as
156 * its sign. */
157static inline int quantify(int i, int bits)
158{
159 if (i >= 0)
160 return quantify_u32(i, bits);
161 else
162 return -quantify_u32(-i, bits);
163}
164
165/* Insert timer value <v> into tree <r>. A pre-allocated node must be passed
166 * in <alloc>. It may be NULL, in which case the function will allocate it
167 * itself. It will be reset to NULL once consumed. The caller is responsible
168 * for freeing the node once not used anymore. The node where the value was
169 * inserted is returned.
170 */
171struct timer *insert_timer(struct eb_root *r, struct timer **alloc, int v)
172{
173 struct timer *t = *alloc;
174 struct eb32_node *n;
175
176 if (!t) {
177 t = calloc(sizeof(*t), 1);
178 if (unlikely(!t)) {
179 fprintf(stderr, "%s: not enough memory\n", __FUNCTION__);
180 exit(1);
181 }
182 }
183 t->node.key = quantify(v, QBITS); // keep only the higher QBITS bits
184
185 n = eb32i_insert(r, &t->node);
186 if (n == &t->node)
187 t = NULL; /* node inserted, will malloc next time */
188
189 *alloc = t;
190 return container_of(n, struct timer, node);
191}
192
193/* Insert value value <v> into tree <r>. A pre-allocated node must be passed
194 * in <alloc>. It may be NULL, in which case the function will allocate it
195 * itself. It will be reset to NULL once consumed. The caller is responsible
196 * for freeing the node once not used anymore. The node where the value was
197 * inserted is returned.
198 */
199struct timer *insert_value(struct eb_root *r, struct timer **alloc, int v)
200{
201 struct timer *t = *alloc;
202 struct eb32_node *n;
203
204 if (!t) {
205 t = calloc(sizeof(*t), 1);
206 if (unlikely(!t)) {
207 fprintf(stderr, "%s: not enough memory\n", __FUNCTION__);
208 exit(1);
209 }
210 }
211 t->node.key = v;
212
213 n = eb32i_insert(r, &t->node);
214 if (n == &t->node)
215 t = NULL; /* node inserted, will malloc next time */
216
217 *alloc = t;
218 return container_of(n, struct timer, node);
219}
220
221int str2ic(const char *s)
222{
223 int i = 0;
224 int j, k;
225
226 if (*s != '-') {
227 /* positive number */
228 while (1) {
229 j = (*s++) - '0';
230 k = i * 10;
231 if ((unsigned)j > 9)
232 break;
233 i = k + j;
234 }
235 } else {
236 /* negative number */
237 s++;
238 while (1) {
239 j = (*s++) - '0';
240 k = i * 10;
241 if ((unsigned)j > 9)
242 break;
243 i = k - j;
244 }
245 }
246
247 return i;
248}
249
250
251/* Equivalent to strtoul with a length. */
252static inline unsigned int __strl2ui(const char *s, int len)
253{
254 unsigned int i = 0;
255 while (len-- > 0) {
256 i = i * 10 - '0';
257 i += (unsigned char)*s++;
258 }
259 return i;
260}
261
262unsigned int strl2ui(const char *s, int len)
263{
264 return __strl2ui(s, len);
265}
266
267/* Convert "[04/Dec/2008:09:49:40.555]" to an integer equivalent to the time of
268 * the day in milliseconds. It returns -1 for all unparsable values. The parser
269 * looks ugly but gcc emits far better code that way.
270 */
271int convert_date(const char *field)
272{
273 unsigned int h, m, s, ms;
274 unsigned char c;
275 const char *b, *e;
276
277 h = m = s = ms = 0;
278 e = field;
279
280 /* skip the date */
281 while (1) {
282 c = *(e++);
283 if (c == ':')
284 break;
285 if (!c)
286 goto out_err;
287 }
288
289 /* hour + ':' */
290 b = e;
291 while (1) {
292 c = *(e++) - '0';
293 if (c > 9)
294 break;
295 h = h * 10 + c;
296 }
297 if (c == (unsigned char)(0 - '0'))
298 goto out_err;
299
300 /* minute + ':' */
301 b = e;
302 while (1) {
303 c = *(e++) - '0';
304 if (c > 9)
305 break;
306 m = m * 10 + c;
307 }
308 if (c == (unsigned char)(0 - '0'))
309 goto out_err;
310
311 /* second + '.' or ']' */
312 b = e;
313 while (1) {
314 c = *(e++) - '0';
315 if (c > 9)
316 break;
317 s = s * 10 + c;
318 }
319 if (c == (unsigned char)(0 - '0'))
320 goto out_err;
321
322 /* if there's a '.', we have milliseconds */
323 if (c == (unsigned char)('.' - '0')) {
324 /* millisecond second + ']' */
325 b = e;
326 while (1) {
327 c = *(e++) - '0';
328 if (c > 9)
329 break;
330 ms = ms * 10 + c;
331 }
332 if (c == (unsigned char)(0 - '0'))
333 goto out_err;
334 }
335 return (((h * 60) + m) * 60 + s) * 1000 + ms;
336 out_err:
337 return -1;
338}
339
340void truncated_line(int linenum, const char *line)
341{
342 if (!(filter & FILT_QUIET))
343 fprintf(stderr, "Truncated line %d: %s\n", linenum, line);
344}
345
346int main(int argc, char **argv)
347{
348 const char *b, *e, *p;
349 const char *output_file = NULL;
350 int f, tot, last, linenum, err, parse_err;
351 struct timer *t = NULL, *t2;
352 struct eb32_node *n;
353 int val, test;
354 int array[5];
355 int filter_acc_delay = 0, filter_acc_count = 0;
356 int skip_fields = 1;
357
358 argc--; argv++;
359 while (argc > 0) {
360 if (*argv[0] != '-')
361 break;
362
363 if (strcmp(argv[0], "-ad") == 0) {
364 if (argc < 2) die("missing option for -ad");
365 argc--; argv++;
366 filter |= FILT_ACC_DELAY;
367 filter_acc_delay = atol(*argv);
368 }
369 else if (strcmp(argv[0], "-ac") == 0) {
370 if (argc < 2) die("missing option for -ac");
371 argc--; argv++;
372 filter |= FILT_ACC_COUNT;
373 filter_acc_count = atol(*argv);
374 }
375 else if (strcmp(argv[0], "-s") == 0) {
376 if (argc < 2) die("missing option for -s");
377 argc--; argv++;
378 skip_fields = atol(*argv);
379 }
380 else if (strcmp(argv[0], "-e") == 0)
381 filter |= FILT_ERRORS_ONLY;
382 else if (strcmp(argv[0], "-c") == 0)
383 filter |= FILT_COUNT_ONLY;
384 else if (strcmp(argv[0], "-q") == 0)
385 filter |= FILT_QUIET;
386 else if (strcmp(argv[0], "-v") == 0)
387 filter_invert = !filter_invert;
388 else if (strcmp(argv[0], "-gt") == 0)
389 filter |= FILT_GRAPH_TIMERS;
Willy Tarreau214c2032009-02-20 11:02:32 +0100390 else if (strcmp(argv[0], "-pct") == 0)
391 filter |= FILT_PERCENTILE;
Willy Tarreau72c28532009-01-22 18:56:50 +0100392 else if (strcmp(argv[0], "-o") == 0) {
393 if (output_file)
394 die("Fatal: output file name already specified.\n");
395 if (argc < 2)
396 die("Fatal: missing output file name.\n");
397 output_file = argv[1];
398 }
399 argc--;
400 argv++;
401 }
402
403 if (!filter)
404 die("No action specified.\n");
405
406 if (filter & FILT_ACC_COUNT && !filter_acc_count)
407 filter_acc_count=1;
408
409 if (filter & FILT_ACC_DELAY && !filter_acc_delay)
410 filter_acc_delay = 1;
411
412 linenum = 0;
413 tot = 0;
414 parse_err = 0;
415
Willy Tarreau214c2032009-02-20 11:02:32 +0100416 while ((line = fgets2(stdin)) != NULL) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100417 linenum++;
418
419 if (filter & FILT_ERRORS_ONLY) {
420 /* only report erroneous status codes */
421 b = field_start(line, STATUS_FIELD + skip_fields);
422 if (!*b) {
423 truncated_line(linenum, line);
424 continue;
425 }
426 if (*b == '-') {
427 test = 1;
428 } else {
429 val = strl2ui(b, 3);
430 test = (val >= 500 && val <= 599);
431 }
432 test ^= filter_invert;
433 if (test) {
434 tot++;
435 if (!(filter & FILT_COUNT_ONLY))
Willy Tarreau214c2032009-02-20 11:02:32 +0100436 puts(line);
Willy Tarreau72c28532009-01-22 18:56:50 +0100437 }
438 continue;
439 }
440
441 if (filter & (FILT_ACC_COUNT|FILT_ACC_DELAY)) {
442 b = field_start(line, ACCEPT_FIELD + skip_fields);
443 if (!*b) {
444 truncated_line(linenum, line);
445 continue;
446 }
447
Willy Tarreau214c2032009-02-20 11:02:32 +0100448 tot++;
Willy Tarreau72c28532009-01-22 18:56:50 +0100449 val = convert_date(b);
450 //printf("date=%s => %d\n", b, val);
451 if (val < 0) {
452 parse_err++;
453 continue;
454 }
455
456 t2 = insert_value(&timers[0], &t, val);
457 t2->count++;
458 continue;
459 }
460
Willy Tarreau214c2032009-02-20 11:02:32 +0100461 if (filter & (FILT_GRAPH_TIMERS|FILT_PERCENTILE)) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100462 int f;
463
464 b = field_start(line, TIME_FIELD + skip_fields);
465 if (!*b) {
466 truncated_line(linenum, line);
467 continue;
468 }
469
470 e = field_stop(b + 1);
471 /* we have field TIME_FIELD in [b]..[e-1] */
472
473 p = b;
474 err = 0;
475 for (f = 0; f < 5 && *p; f++) {
476 array[f] = str2ic(p);
477 if (array[f] < 0) {
478 array[f] = -1;
479 err = 1;
480 }
481
482 SKIP_CHAR(p, '/');
483 }
484
485 if (f < 5) {
486 parse_err++;
487 continue;
488 }
489
490 /* if we find at least one negative time, we count one error
491 * with a time equal to the total session time. This will
492 * emphasize quantum timing effects associated to known
493 * timeouts. Note that on some buggy machines, it is possible
494 * that the total time is negative, hence the reason to reset
495 * it.
496 */
Willy Tarreau214c2032009-02-20 11:02:32 +0100497
498 if (filter & FILT_GRAPH_TIMERS) {
499 if (err) {
500 if (array[4] < 0)
501 array[4] = -1;
502 t2 = insert_timer(&timers[0], &t, array[4]); // total time
503 t2->count++;
504 } else {
505 int v;
Willy Tarreau72c28532009-01-22 18:56:50 +0100506
Willy Tarreau214c2032009-02-20 11:02:32 +0100507 t2 = insert_timer(&timers[1], &t, array[0]); t2->count++; // req
508 t2 = insert_timer(&timers[2], &t, array[2]); t2->count++; // conn
509 t2 = insert_timer(&timers[3], &t, array[3]); t2->count++; // resp
510
511 v = array[4] - array[0] - array[1] - array[2] - array[3]; // data time
512 if (v < 0 && !(filter & FILT_QUIET))
513 fprintf(stderr, "ERR: %s (%d %d %d %d %d => %d)\n",
514 line, array[0], array[1], array[2], array[3], array[4], v);
515 t2 = insert_timer(&timers[4], &t, v); t2->count++;
516 tot++;
517 }
518 } else { /* percentile */
519 if (err) {
520 if (array[4] < 0)
521 array[4] = -1;
522 t2 = insert_value(&timers[0], &t, array[4]); // total time
523 t2->count++;
524 } else {
525 int v;
Willy Tarreau72c28532009-01-22 18:56:50 +0100526
Willy Tarreau214c2032009-02-20 11:02:32 +0100527 t2 = insert_value(&timers[1], &t, array[0]); t2->count++; // req
528 t2 = insert_value(&timers[2], &t, array[2]); t2->count++; // conn
529 t2 = insert_value(&timers[3], &t, array[3]); t2->count++; // resp
530
531 v = array[4] - array[0] - array[1] - array[2] - array[3]; // data time
532 if (v < 0 && !(filter & FILT_QUIET))
533 fprintf(stderr, "ERR: %s (%d %d %d %d %d => %d)\n",
534 line, array[0], array[1], array[2], array[3], array[4], v);
535 t2 = insert_value(&timers[4], &t, v); t2->count++;
536 tot++;
537 }
Willy Tarreau72c28532009-01-22 18:56:50 +0100538 }
539 continue;
540 }
541
542 /* all other cases mean we just want to count lines */
543 tot++;
544 }
545
546 if (t)
547 free(t);
548
549 if (filter & FILT_COUNT_ONLY) {
550 printf("%d\n", tot);
551 exit(0);
552 }
553
554 if (filter & FILT_ERRORS_ONLY)
555 exit(0);
556
557 if (filter & (FILT_ACC_COUNT|FILT_ACC_DELAY)) {
558 /* sort and count all timers. Output will look like this :
559 * <accept_date> <delta_ms from previous one> <nb entries>
560 */
561 n = eb32_first(&timers[0]);
562
563 if (n)
564 last = n->key;
565 while (n) {
566 unsigned int d, h, m, s, ms;
567
568 t = container_of(n, struct timer, node);
569 h = n->key;
570 d = h - last;
571 last = h;
572
573 if (d >= filter_acc_delay && t->count >= filter_acc_count) {
574 ms = h % 1000; h = h / 1000;
575 s = h % 60; h = h / 60;
576 m = h % 60; h = h / 60;
577 tot++;
578 printf("%02d:%02d:%02d.%03d %d %d %d\n", h, m, s, ms, last, d, t->count);
579 }
580 n = eb32_next(n);
581 }
582 }
583 else if (filter & FILT_GRAPH_TIMERS) {
584 /* sort all timers */
585 for (f = 0; f < 5; f++) {
586 struct eb32_node *n;
587 int val;
588
589 val = 0;
590 n = eb32_first(&timers[f]);
591 while (n) {
592 int i;
593 double d;
594
595 t = container_of(n, struct timer, node);
596 last = n->key;
597 val = t->count;
598
599 i = (last < 0) ? -last : last;
600 i = fls_auto(i) - QBITS;
601
602 if (i > 0)
603 d = val / (double)(1 << i);
604 else
605 d = val;
606
607 if (d > 0.0) {
608 printf("%d %d %f\n", f, last, d+1.0);
609 tot++;
610 }
611
612 n = eb32_next(n);
613 }
Willy Tarreau214c2032009-02-20 11:02:32 +0100614 }
615 }
616 else if (filter & FILT_PERCENTILE) {
617 /* report timers by percentile :
618 * <percent> <total> <max_req_time> <max_conn_time> <max_resp_time> <max_data_time>
619 * We don't count errs.
620 */
621 struct eb32_node *n[5];
622 unsigned long cum[5];
623 double step;
624
625 for (f = 1; f < 5; f++) {
626 n[f] = eb32_first(&timers[f]);
627 cum[f] = container_of(n[f], struct timer, node)->count;
628 }
629
630 for (step = 1; step <= 1000;) {
631 unsigned int thres = tot * (step / 1000.0);
632
633 printf("%3.1f %d ", step/10.0, thres);
634 for (f = 1; f < 5; f++) {
635 struct eb32_node *next;
636 while (cum[f] < thres) {
637 /* need to find other keys */
638 next = eb32_next(n[f]);
639 if (!next)
640 break;
641 n[f] = next;
642 cum[f] += container_of(next, struct timer, node)->count;
643 }
644
645 /* value still within $step % of total */
646 printf("%d ", n[f]->key);
647 }
648 putchar('\n');
649 if (step >= 100 && step < 900)
650 step += 50; // jump 5% by 5% between those steps.
651 else if (step >= 20 && step < 980)
652 step += 10;
653 else
654 step += 1;
Willy Tarreau72c28532009-01-22 18:56:50 +0100655 }
656 }
657
658 if (!(filter & FILT_QUIET))
659 fprintf(stderr, "%d lines in, %d lines out, %d parsing errors\n",
660 linenum, tot, parse_err);
661 exit(0);
662}
663
664/*
665 * Local variables:
666 * c-indent-level: 8
667 * c-basic-offset: 8
668 * End:
669 */