blob: 67952fb5ba4aa79cf9da9ca177b834dd2a0ed4d3 [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{
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200388 const char *b, *e, *p, *time_field;
Willy Tarreau72c28532009-01-22 18:56:50 +0100389 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++;
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200497 time_field = NULL;
Willy Tarreau72c28532009-01-22 18:56:50 +0100498
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200499 test = 1;
Willy Tarreau70c428f2011-07-10 17:27:40 +0200500 if (unlikely(filter & FILT_HTTP_ONLY)) {
501 /* only report lines with at least 4 timers */
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200502
503 if (!time_field)
504 time_field = field_start(line, TIME_FIELD + skip_fields);
505 if (!*time_field) {
Willy Tarreau70c428f2011-07-10 17:27:40 +0200506 truncated_line(linenum, line);
507 continue;
508 }
509
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200510 e = field_stop(time_field + 1);
511 /* we have field TIME_FIELD in [time_field]..[e-1] */
512 p = time_field;
Willy Tarreau70c428f2011-07-10 17:27:40 +0200513 f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200514 while (!SEP(*p)) {
Willy Tarreau70c428f2011-07-10 17:27:40 +0200515 if (++f == 4)
516 break;
517 SKIP_CHAR(p, '/');
518 }
519 test &= (f >= 4);
520 }
521
Willy Tarreau2651ac32010-05-05 12:20:19 +0200522 if (unlikely(filter & FILT_TIME_RESP)) {
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200523 int tps;
524
525 /* only report lines with response times larger than filter_time_resp */
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200526 if (!time_field)
527 time_field = field_start(line, TIME_FIELD + skip_fields);
528 if (!*time_field) {
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200529 truncated_line(linenum, line);
530 continue;
531 }
532
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200533 e = field_stop(time_field + 1);
534 /* we have field TIME_FIELD in [time_field]..[e-1], let's check only the response time */
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200535
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200536 p = time_field;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200537 err = 0;
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200538 f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200539 while (!SEP(*p)) {
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200540 tps = str2ic(p);
541 if (tps < 0) {
542 tps = -1;
543 err = 1;
544 }
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200545 if (++f == 4)
546 break;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200547 SKIP_CHAR(p, '/');
548 }
549
550 if (f < 4) {
551 parse_err++;
552 continue;
553 }
554
555 test &= (tps >= filter_time_resp) ^ !!(filter & FILT_INVERT_TIME_RESP);
556 }
557
Willy Tarreau2651ac32010-05-05 12:20:19 +0200558 if (unlikely(filter & FILT_ERRORS_ONLY)) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100559 /* only report erroneous status codes */
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200560 if (!time_field)
561 time_field = field_start(line, TIME_FIELD + skip_fields);
562 if (!*time_field) {
563 truncated_line(linenum, line);
564 continue;
565 }
566 b = field_start(time_field, STATUS_FIELD - TIME_FIELD + 1);
Willy Tarreau72c28532009-01-22 18:56:50 +0100567 if (!*b) {
568 truncated_line(linenum, line);
569 continue;
570 }
571 if (*b == '-') {
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200572 test &= !!(filter & FILT_INVERT_ERRORS);
Willy Tarreau72c28532009-01-22 18:56:50 +0100573 } else {
574 val = strl2ui(b, 3);
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200575 test &= (val >= 500 && val <= 599) ^ !!(filter & FILT_INVERT_ERRORS);
Willy Tarreau72c28532009-01-22 18:56:50 +0100576 }
Willy Tarreau72c28532009-01-22 18:56:50 +0100577 }
578
Willy Tarreau0f423a72010-05-03 10:50:54 +0200579 test ^= filter_invert;
580 if (!test)
581 continue;
582
Willy Tarreau2651ac32010-05-05 12:20:19 +0200583 if (unlikely(filter & (FILT_ACC_COUNT|FILT_ACC_DELAY))) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100584 b = field_start(line, ACCEPT_FIELD + skip_fields);
585 if (!*b) {
586 truncated_line(linenum, line);
587 continue;
588 }
589
Willy Tarreau214c2032009-02-20 11:02:32 +0100590 tot++;
Willy Tarreau72c28532009-01-22 18:56:50 +0100591 val = convert_date(b);
592 //printf("date=%s => %d\n", b, val);
593 if (val < 0) {
594 parse_err++;
595 continue;
596 }
597
598 t2 = insert_value(&timers[0], &t, val);
599 t2->count++;
600 continue;
601 }
602
Willy Tarreau2651ac32010-05-05 12:20:19 +0200603 if (unlikely(filter & (FILT_GRAPH_TIMERS|FILT_PERCENTILE))) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100604 int f;
605
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200606 if (!time_field)
607 time_field = field_start(line, TIME_FIELD + skip_fields);
608 if (!*time_field) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100609 truncated_line(linenum, line);
610 continue;
611 }
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200612 if (!*time_field) {
613 truncated_line(linenum, line);
614 continue;
615 }
Willy Tarreau72c28532009-01-22 18:56:50 +0100616
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200617 e = field_stop(time_field + 1);
618 /* we have field TIME_FIELD in [time_field]..[e-1] */
Willy Tarreau72c28532009-01-22 18:56:50 +0100619
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200620 p = time_field;
Willy Tarreau72c28532009-01-22 18:56:50 +0100621 err = 0;
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200622 f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200623 while (!SEP(*p)) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100624 array[f] = str2ic(p);
625 if (array[f] < 0) {
626 array[f] = -1;
627 err = 1;
628 }
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200629 if (++f == 5)
630 break;
Willy Tarreau72c28532009-01-22 18:56:50 +0100631 SKIP_CHAR(p, '/');
632 }
633
634 if (f < 5) {
635 parse_err++;
636 continue;
637 }
638
639 /* if we find at least one negative time, we count one error
640 * with a time equal to the total session time. This will
641 * emphasize quantum timing effects associated to known
642 * timeouts. Note that on some buggy machines, it is possible
643 * that the total time is negative, hence the reason to reset
644 * it.
645 */
Willy Tarreau214c2032009-02-20 11:02:32 +0100646
647 if (filter & FILT_GRAPH_TIMERS) {
648 if (err) {
649 if (array[4] < 0)
650 array[4] = -1;
651 t2 = insert_timer(&timers[0], &t, array[4]); // total time
652 t2->count++;
653 } else {
654 int v;
Willy Tarreau72c28532009-01-22 18:56:50 +0100655
Willy Tarreau214c2032009-02-20 11:02:32 +0100656 t2 = insert_timer(&timers[1], &t, array[0]); t2->count++; // req
657 t2 = insert_timer(&timers[2], &t, array[2]); t2->count++; // conn
658 t2 = insert_timer(&timers[3], &t, array[3]); t2->count++; // resp
659
660 v = array[4] - array[0] - array[1] - array[2] - array[3]; // data time
661 if (v < 0 && !(filter & FILT_QUIET))
662 fprintf(stderr, "ERR: %s (%d %d %d %d %d => %d)\n",
663 line, array[0], array[1], array[2], array[3], array[4], v);
664 t2 = insert_timer(&timers[4], &t, v); t2->count++;
665 tot++;
666 }
667 } else { /* percentile */
668 if (err) {
669 if (array[4] < 0)
670 array[4] = -1;
671 t2 = insert_value(&timers[0], &t, array[4]); // total time
672 t2->count++;
673 } else {
674 int v;
Willy Tarreau72c28532009-01-22 18:56:50 +0100675
Willy Tarreau214c2032009-02-20 11:02:32 +0100676 t2 = insert_value(&timers[1], &t, array[0]); t2->count++; // req
677 t2 = insert_value(&timers[2], &t, array[2]); t2->count++; // conn
678 t2 = insert_value(&timers[3], &t, array[3]); t2->count++; // resp
679
680 v = array[4] - array[0] - array[1] - array[2] - array[3]; // data time
681 if (v < 0 && !(filter & FILT_QUIET))
682 fprintf(stderr, "ERR: %s (%d %d %d %d %d => %d)\n",
683 line, array[0], array[1], array[2], array[3], array[4], v);
684 t2 = insert_value(&timers[4], &t, v); t2->count++;
685 tot++;
686 }
Willy Tarreau72c28532009-01-22 18:56:50 +0100687 }
688 continue;
689 }
690
Willy Tarreau2651ac32010-05-05 12:20:19 +0200691 if (unlikely(filter & FILT_COUNT_STATUS)) {
Willy Tarreau54170812010-09-13 22:50:49 +0200692 /* first, let's ensure that the line is a traffic line (beginning
693 * with an IP address)
694 */
695 b = field_start(line, SOURCE_FIELD + skip_fields);
696 if (*b < '0' || *b > '9') {
697 parse_err++;
698 continue;
699 }
700
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200701 if (time_field)
702 b = field_start(time_field, STATUS_FIELD - TIME_FIELD + 1);
703 else
704 b = field_start(b, STATUS_FIELD - SOURCE_FIELD + 1);
Willy Tarreau0f423a72010-05-03 10:50:54 +0200705 if (!*b) {
706 truncated_line(linenum, line);
707 continue;
708 }
709 val = str2ic(b);
710
711 t2 = insert_value(&timers[0], &t, val);
712 t2->count++;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200713 continue;
Willy Tarreau0f423a72010-05-03 10:50:54 +0200714 }
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200715
Willy Tarreaud8fc1102010-09-12 17:56:16 +0200716 if (unlikely(filter & FILT_COUNT_TERM_CODES)) {
Willy Tarreau54170812010-09-13 22:50:49 +0200717 /* first, let's ensure that the line is a traffic line (beginning
718 * with an IP address)
719 */
720 b = field_start(line, SOURCE_FIELD + skip_fields);
721 if (*b < '0' || *b > '9') {
722 parse_err++;
723 continue;
724 }
725
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200726 if (time_field)
727 b = field_start(time_field, TERM_CODES_FIELD - TIME_FIELD + 1);
728 else
729 b = field_start(b, TERM_CODES_FIELD - SOURCE_FIELD + 1);
Willy Tarreaud8fc1102010-09-12 17:56:16 +0200730 if (!*b) {
731 truncated_line(linenum, line);
732 continue;
733 }
734 val = 256 * b[0] + b[1];
735
736 t2 = insert_value(&timers[0], &t, val);
737 t2->count++;
738 continue;
739 }
740
Willy Tarreaud2201062010-05-27 18:17:30 +0200741 if (unlikely(filter & FILT_COUNT_SRV_STATUS)) {
742 char *srv_name;
743 struct ebmb_node *srv_node;
744 struct srv_st *srv;
745
746 /* first, let's ensure that the line is a traffic line (beginning
747 * with an IP address)
748 */
749 b = field_start(line, SOURCE_FIELD + skip_fields);
750 if (*b < '0' || *b > '9') {
751 parse_err++;
752 continue;
753 }
754
755 /* the server field is before the status field, so let's
756 * parse them in the proper order.
757 */
758 b = field_start(b, SERVER_FIELD - SOURCE_FIELD + 1);
759 if (!*b) {
760 truncated_line(linenum, line);
761 continue;
762 }
763
764 e = field_stop(b + 1); /* we have the server name in [b]..[e-1] */
765
766 /* the chance that a server name already exists is extremely high,
767 * so let's perform a normal lookup first.
768 */
769 srv_node = ebst_lookup_len(&timers[0], b, e - b);
770 srv = container_of(srv_node, struct srv_st, node);
771
772 if (!srv_node) {
773 /* server not yet in the tree, let's create it */
774 srv = (void *)calloc(1, sizeof(struct srv_st) + e - b + 1);
775 srv_node = &srv->node;
776 memcpy(&srv_node->key, b, e - b);
777 srv_node->key[e - b] = '\0';
778 ebst_insert(&timers[0], srv_node);
779 }
780
781 /* let's collect the connect and response times */
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200782 if (!time_field)
783 time_field = field_start(e, TIME_FIELD - SERVER_FIELD);
784 if (!*time_field) {
Willy Tarreaud2201062010-05-27 18:17:30 +0200785 truncated_line(linenum, line);
786 continue;
787 }
788
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200789 e = field_stop(time_field + 1);
790 /* we have field TIME_FIELD in [time_field]..[e-1] */
Willy Tarreaud2201062010-05-27 18:17:30 +0200791
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200792 p = time_field;
Willy Tarreaud2201062010-05-27 18:17:30 +0200793 err = 0;
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200794 f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200795 while (!SEP(*p)) {
Willy Tarreaud2201062010-05-27 18:17:30 +0200796 array[f] = str2ic(p);
797 if (array[f] < 0) {
798 array[f] = -1;
799 err = 1;
800 }
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200801 if (++f == 5)
802 break;
Willy Tarreaud2201062010-05-27 18:17:30 +0200803 SKIP_CHAR(p, '/');
804 }
805
806 if (f < 5) {
807 parse_err++;
808 continue;
809 }
810
811 /* OK we have our timers in array[2,3] */
812 if (!err)
813 srv->nb_ok++;
814
815 if (array[2] >= 0) {
816 srv->cum_ct += array[2];
817 srv->nb_ct++;
818 }
819
820 if (array[3] >= 0) {
821 srv->cum_rt += array[3];
822 srv->nb_rt++;
823 }
824
825 /* we're interested in the 5 HTTP status classes (1xx ... 5xx), and
826 * the invalid ones which will be reported as 0.
827 */
828 b = field_start(e, STATUS_FIELD - TIME_FIELD);
829 if (!*b) {
830 truncated_line(linenum, line);
831 continue;
832 }
833
834 val = 0;
835 if (*b >= '1' && *b <= '5')
836 val = *b - '0';
837
838 srv->st_cnt[val]++;
839 continue;
840 }
841
Willy Tarreauabe45b62010-10-28 20:33:46 +0200842 if (unlikely(filter & FILT_COUNT_URL_ANY)) {
843 /* first, let's ensure that the line is a traffic line (beginning
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200844 * with an IP address, or already having a time field)
Willy Tarreauabe45b62010-10-28 20:33:46 +0200845 */
Willy Tarreauabe45b62010-10-28 20:33:46 +0200846
847 /* let's collect the response time */
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200848 if (!time_field) {
849 b = field_start(line, SOURCE_FIELD + skip_fields); // avg 95 ns per line
850 if (*b < '0' || *b > '9') {
851 parse_err++;
852 continue;
853 }
854 time_field = field_start(field_stop(b + 1), TIME_FIELD - SOURCE_FIELD); // avg 115 ns per line
855 }
856 if (!*time_field) {
Willy Tarreauabe45b62010-10-28 20:33:46 +0200857 truncated_line(linenum, line);
858 continue;
859 }
860
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200861 /* we have the field TIME_FIELD starting at <time_field>. We'll
Willy Tarreauabe45b62010-10-28 20:33:46 +0200862 * parse the 5 timers to detect errors, it takes avg 55 ns per line.
863 */
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200864 e = time_field; err = 0; f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200865 while (!SEP(*e)) {
Willy Tarreauabe45b62010-10-28 20:33:46 +0200866 array[f] = str2ic(e);
867 if (array[f] < 0) {
868 array[f] = -1;
869 err = 1;
870 }
871 if (++f == 5)
872 break;
873 SKIP_CHAR(e, '/');
874 }
875 if (f < 5) {
876 parse_err++;
877 continue;
878 }
879
880 /* OK we have our timers in array[3], and err is >0 if at
881 * least one -1 was seen. <e> points to the first char of
882 * the last timer. Let's prepare a new node with that.
883 */
884 if (unlikely(!ustat))
885 ustat = calloc(1, sizeof(*ustat));
886
887 ustat->nb_err = err;
888 ustat->nb_req = 1;
889
890 /* use array[4] = total time in case of error */
891 ustat->total_time = (array[3] >= 0) ? array[3] : array[4];
892 ustat->total_time_ok = (array[3] >= 0) ? array[3] : 0;
893
894 /* the line may be truncated because of a bad request or anything like this,
895 * without a method. Also, if it does not begin with an quote, let's skip to
896 * the next field because it's a capture. Let's fall back to the "method" itself
897 * if there's nothing else.
898 */
899 e = field_start(e, METH_FIELD - TIME_FIELD + 1); // avg 100 ns per line
900 while (*e != '"' && *e)
901 e = field_start(e, 2);
902
903 if (!*e) {
904 truncated_line(linenum, line);
905 continue;
906 }
907
908 b = field_start(e, URL_FIELD - METH_FIELD + 1); // avg 40 ns per line
909 if (!*b)
910 b = e;
911
912 /* stop at end of field or first ';' or '?', takes avg 64 ns per line */
913 e = b;
914 do {
915 if (*e == ' ' || *e == '?' || *e == ';' || *e == '\t') {
916 *(char *)e = 0;
917 break;
918 }
919 e++;
920 } while (*e);
921
922 /* now instead of copying the URL for a simple lookup, we'll link
923 * to it from the node we're trying to insert. If it returns a
924 * different value, it was already there. Otherwise we just have
925 * to dynamically realloc an entry using strdup().
926 */
927 ustat->node.url.key = (char *)b;
928 ebpt_old = ebis_insert(&timers[0], &ustat->node.url);
929
930 if (ebpt_old != &ustat->node.url) {
931 struct url_stat *ustat_old;
932 /* node was already there, let's update previous one */
933 ustat_old = container_of(ebpt_old, struct url_stat, node.url);
934 ustat_old->nb_req ++;
935 ustat_old->nb_err += ustat->nb_err;
936 ustat_old->total_time += ustat->total_time;
937 ustat_old->total_time_ok += ustat->total_time_ok;
938 } else {
939 ustat->url = ustat->node.url.key = strdup(ustat->node.url.key);
940 ustat = NULL; /* node was used */
941 }
942
943 continue;
944 }
945
Willy Tarreau72c28532009-01-22 18:56:50 +0100946 /* all other cases mean we just want to count lines */
947 tot++;
Willy Tarreau2651ac32010-05-05 12:20:19 +0200948 if (unlikely(!(filter & FILT_COUNT_ONLY)))
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200949 puts(line);
Willy Tarreau72c28532009-01-22 18:56:50 +0100950 }
951
952 if (t)
953 free(t);
954
955 if (filter & FILT_COUNT_ONLY) {
956 printf("%d\n", tot);
957 exit(0);
958 }
959
Willy Tarreau72c28532009-01-22 18:56:50 +0100960 if (filter & (FILT_ACC_COUNT|FILT_ACC_DELAY)) {
961 /* sort and count all timers. Output will look like this :
962 * <accept_date> <delta_ms from previous one> <nb entries>
963 */
964 n = eb32_first(&timers[0]);
965
966 if (n)
967 last = n->key;
968 while (n) {
969 unsigned int d, h, m, s, ms;
970
971 t = container_of(n, struct timer, node);
972 h = n->key;
973 d = h - last;
974 last = h;
975
976 if (d >= filter_acc_delay && t->count >= filter_acc_count) {
977 ms = h % 1000; h = h / 1000;
978 s = h % 60; h = h / 60;
979 m = h % 60; h = h / 60;
980 tot++;
981 printf("%02d:%02d:%02d.%03d %d %d %d\n", h, m, s, ms, last, d, t->count);
982 }
983 n = eb32_next(n);
984 }
985 }
986 else if (filter & FILT_GRAPH_TIMERS) {
987 /* sort all timers */
988 for (f = 0; f < 5; f++) {
989 struct eb32_node *n;
990 int val;
991
992 val = 0;
993 n = eb32_first(&timers[f]);
994 while (n) {
995 int i;
996 double d;
997
998 t = container_of(n, struct timer, node);
999 last = n->key;
1000 val = t->count;
1001
1002 i = (last < 0) ? -last : last;
1003 i = fls_auto(i) - QBITS;
1004
1005 if (i > 0)
1006 d = val / (double)(1 << i);
1007 else
1008 d = val;
1009
1010 if (d > 0.0) {
1011 printf("%d %d %f\n", f, last, d+1.0);
1012 tot++;
1013 }
1014
1015 n = eb32_next(n);
1016 }
Willy Tarreau214c2032009-02-20 11:02:32 +01001017 }
1018 }
1019 else if (filter & FILT_PERCENTILE) {
1020 /* report timers by percentile :
1021 * <percent> <total> <max_req_time> <max_conn_time> <max_resp_time> <max_data_time>
1022 * We don't count errs.
1023 */
1024 struct eb32_node *n[5];
1025 unsigned long cum[5];
1026 double step;
1027
Willy Tarreau910ba4b2009-11-17 10:16:19 +01001028 if (!tot)
1029 goto empty;
1030
Willy Tarreau214c2032009-02-20 11:02:32 +01001031 for (f = 1; f < 5; f++) {
1032 n[f] = eb32_first(&timers[f]);
1033 cum[f] = container_of(n[f], struct timer, node)->count;
1034 }
1035
1036 for (step = 1; step <= 1000;) {
1037 unsigned int thres = tot * (step / 1000.0);
1038
1039 printf("%3.1f %d ", step/10.0, thres);
1040 for (f = 1; f < 5; f++) {
1041 struct eb32_node *next;
1042 while (cum[f] < thres) {
1043 /* need to find other keys */
1044 next = eb32_next(n[f]);
1045 if (!next)
1046 break;
1047 n[f] = next;
1048 cum[f] += container_of(next, struct timer, node)->count;
1049 }
1050
1051 /* value still within $step % of total */
1052 printf("%d ", n[f]->key);
1053 }
1054 putchar('\n');
1055 if (step >= 100 && step < 900)
1056 step += 50; // jump 5% by 5% between those steps.
1057 else if (step >= 20 && step < 980)
1058 step += 10;
1059 else
1060 step += 1;
Willy Tarreau72c28532009-01-22 18:56:50 +01001061 }
1062 }
Willy Tarreau0f423a72010-05-03 10:50:54 +02001063 else if (filter & FILT_COUNT_STATUS) {
1064 /* output all statuses in the form of <status> <occurrences> */
1065 n = eb32_first(&timers[0]);
1066 while (n) {
1067 t = container_of(n, struct timer, node);
1068 printf("%d %d\n", n->key, t->count);
1069 n = eb32_next(n);
1070 }
1071 }
Willy Tarreaud2201062010-05-27 18:17:30 +02001072 else if (unlikely(filter & FILT_COUNT_SRV_STATUS)) {
1073 char *srv_name;
1074 struct ebmb_node *srv_node;
1075 struct srv_st *srv;
1076
1077 printf("#srv_name 1xx 2xx 3xx 4xx 5xx other tot_req req_ok pct_ok avg_ct avg_rt\n");
1078
1079 srv_node = ebmb_first(&timers[0]);
1080 while (srv_node) {
1081 int tot_rq;
1082
1083 srv = container_of(srv_node, struct srv_st, node);
1084
1085 tot_rq = 0;
1086 for (f = 0; f <= 5; f++)
1087 tot_rq += srv->st_cnt[f];
1088
1089 printf("%s %d %d %d %d %d %d %d %d %.1f %d %d\n",
1090 srv_node->key, srv->st_cnt[1], srv->st_cnt[2],
1091 srv->st_cnt[3], srv->st_cnt[4], srv->st_cnt[5], srv->st_cnt[0],
1092 tot_rq,
1093 srv->nb_ok, (double)srv->nb_ok * 100.0 / (tot_rq?tot_rq:1),
1094 (int)(srv->cum_ct / (srv->nb_ct?srv->nb_ct:1)), (int)(srv->cum_rt / (srv->nb_rt?srv->nb_rt:1)));
1095 srv_node = ebmb_next(srv_node);
1096 tot++;
1097 }
1098 }
Willy Tarreaud8fc1102010-09-12 17:56:16 +02001099 else if (filter & FILT_COUNT_TERM_CODES) {
1100 /* output all statuses in the form of <code> <occurrences> */
1101 n = eb32_first(&timers[0]);
1102 while (n) {
1103 t = container_of(n, struct timer, node);
1104 printf("%c%c %d\n", (n->key >> 8), (n->key) & 255, t->count);
1105 n = eb32_next(n);
1106 }
1107 }
Willy Tarreauabe45b62010-10-28 20:33:46 +02001108 else if (unlikely(filter & FILT_COUNT_URL_ANY)) {
1109 char *srv_name;
1110 struct eb_node *node, *next;
1111
1112 if (!(filter & FILT_COUNT_URL_ONLY)) {
1113 /* we have to sort on another criterion. We'll use timers[1] for the
1114 * destination tree.
1115 */
1116
1117 timers[1] = EB_ROOT; /* reconfigure to accept duplicates */
1118 for (node = eb_first(&timers[0]); node; node = next) {
1119 next = eb_next(node);
1120 eb_delete(node);
1121
1122 ustat = container_of(node, struct url_stat, node.url.node);
1123
1124 if (filter & FILT_COUNT_URL_COUNT)
1125 ustat->node.val.key = ustat->nb_req;
1126 else if (filter & FILT_COUNT_URL_ERR)
1127 ustat->node.val.key = ustat->nb_err;
1128 else if (filter & FILT_COUNT_URL_TTOT)
1129 ustat->node.val.key = ustat->total_time;
1130 else if (filter & FILT_COUNT_URL_TAVG)
1131 ustat->node.val.key = ustat->nb_req ? ustat->total_time / ustat->nb_req : 0;
1132 else if (filter & FILT_COUNT_URL_TTOTO)
1133 ustat->node.val.key = ustat->total_time_ok;
1134 else if (filter & FILT_COUNT_URL_TAVGO)
1135 ustat->node.val.key = (ustat->nb_req - ustat->nb_err) ? ustat->total_time_ok / (ustat->nb_req - ustat->nb_err) : 0;
1136 else
1137 ustat->node.val.key = 0;
1138
1139 eb64_insert(&timers[1], &ustat->node.val);
1140 }
1141 /* switch trees */
1142 timers[0] = timers[1];
1143 }
1144
1145 printf("#req err ttot tavg oktot okavg url\n");
1146
1147 /* scan the tree in its reverse sorting order */
1148 node = eb_last(&timers[0]);
1149 while (node) {
1150 ustat = container_of(node, struct url_stat, node.url.node);
1151 printf("%d %d %Ld %Ld %Ld %Ld %s\n",
1152 ustat->nb_req,
1153 ustat->nb_err,
1154 ustat->total_time,
1155 ustat->nb_req ? ustat->total_time / ustat->nb_req : 0,
1156 ustat->total_time_ok,
1157 (ustat->nb_req - ustat->nb_err) ? ustat->total_time_ok / (ustat->nb_req - ustat->nb_err) : 0,
1158 ustat->url);
1159
1160 node = eb_prev(node);
1161 tot++;
1162 }
1163 }
Willy Tarreaud2201062010-05-27 18:17:30 +02001164
Willy Tarreau910ba4b2009-11-17 10:16:19 +01001165 empty:
Willy Tarreau72c28532009-01-22 18:56:50 +01001166 if (!(filter & FILT_QUIET))
1167 fprintf(stderr, "%d lines in, %d lines out, %d parsing errors\n",
1168 linenum, tot, parse_err);
1169 exit(0);
1170}
1171
1172/*
1173 * Local variables:
1174 * c-indent-level: 8
1175 * c-basic-offset: 8
1176 * End:
1177 */