blob: fc336b4d483c9fd4679e96cd28423120c4637be3 [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 Tarreau8a09b662012-10-10 10:26:22 +02004 * Copyright 2000-2012 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>
Olivier Burgarde97b9042014-05-22 16:44:59 +020021#include <time.h>
Willy Tarreau72c28532009-01-22 18:56:50 +010022
Willy Tarreau45cb4fb2009-10-26 21:10:04 +010023#include <eb32tree.h>
Willy Tarreauabe45b62010-10-28 20:33:46 +020024#include <eb64tree.h>
25#include <ebistree.h>
Willy Tarreaud2201062010-05-27 18:17:30 +020026#include <ebsttree.h>
Willy Tarreau72c28532009-01-22 18:56:50 +010027
Willy Tarreaud2201062010-05-27 18:17:30 +020028#define SOURCE_FIELD 5
Willy Tarreau72c28532009-01-22 18:56:50 +010029#define ACCEPT_FIELD 6
Willy Tarreaud2201062010-05-27 18:17:30 +020030#define SERVER_FIELD 8
Willy Tarreau72c28532009-01-22 18:56:50 +010031#define TIME_FIELD 9
32#define STATUS_FIELD 10
Baptiste61aaad02012-09-08 23:10:03 +020033#define BYTES_SENT_FIELD 11
Willy Tarreaud8fc1102010-09-12 17:56:16 +020034#define TERM_CODES_FIELD 14
Willy Tarreau72c28532009-01-22 18:56:50 +010035#define CONN_FIELD 15
Willy Tarreau08911ff2011-10-13 13:28:36 +020036#define QUEUE_LEN_FIELD 16
Willy Tarreauabe45b62010-10-28 20:33:46 +020037#define METH_FIELD 17
38#define URL_FIELD 18
Willy Tarreau72c28532009-01-22 18:56:50 +010039#define MAXLINE 16384
40#define QBITS 4
41
Willy Tarreaudf6f0d12011-07-10 18:15:08 +020042#define SEP(c) ((unsigned char)(c) <= ' ')
43#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 +010044
45/* [0] = err/date, [1] = req, [2] = conn, [3] = resp, [4] = data */
46static struct eb_root timers[5] = {
47 EB_ROOT_UNIQUE, EB_ROOT_UNIQUE, EB_ROOT_UNIQUE,
48 EB_ROOT_UNIQUE, EB_ROOT_UNIQUE,
49};
50
51struct timer {
52 struct eb32_node node;
53 unsigned int count;
54};
55
Willy Tarreaud2201062010-05-27 18:17:30 +020056struct srv_st {
57 unsigned int st_cnt[6]; /* 0xx to 5xx */
58 unsigned int nb_ct, nb_rt, nb_ok;
59 unsigned long long cum_ct, cum_rt;
60 struct ebmb_node node;
61 /* don't put anything else here, the server name will be there */
62};
Willy Tarreau72c28532009-01-22 18:56:50 +010063
Willy Tarreauabe45b62010-10-28 20:33:46 +020064struct url_stat {
65 union {
66 struct ebpt_node url;
67 struct eb64_node val;
68 } node;
69 char *url;
70 unsigned long long total_time; /* sum(all reqs' times) */
71 unsigned long long total_time_ok; /* sum(all OK reqs' times) */
Baptiste61aaad02012-09-08 23:10:03 +020072 unsigned long long total_bytes_sent; /* sum(all bytes sent) */
Willy Tarreauabe45b62010-10-28 20:33:46 +020073 unsigned int nb_err, nb_req;
74};
75
Willy Tarreau72c28532009-01-22 18:56:50 +010076#define FILT_COUNT_ONLY 0x01
77#define FILT_INVERT 0x02
78#define FILT_QUIET 0x04
79#define FILT_ERRORS_ONLY 0x08
80#define FILT_ACC_DELAY 0x10
81#define FILT_ACC_COUNT 0x20
82#define FILT_GRAPH_TIMERS 0x40
Willy Tarreau214c2032009-02-20 11:02:32 +010083#define FILT_PERCENTILE 0x80
Willy Tarreau5bdfd962009-10-14 15:16:29 +020084#define FILT_TIME_RESP 0x100
85
86#define FILT_INVERT_ERRORS 0x200
87#define FILT_INVERT_TIME_RESP 0x400
Willy Tarreau72c28532009-01-22 18:56:50 +010088
Willy Tarreau0f423a72010-05-03 10:50:54 +020089#define FILT_COUNT_STATUS 0x800
Willy Tarreaud2201062010-05-27 18:17:30 +020090#define FILT_COUNT_SRV_STATUS 0x1000
Willy Tarreaud8fc1102010-09-12 17:56:16 +020091#define FILT_COUNT_TERM_CODES 0x2000
Willy Tarreau0f423a72010-05-03 10:50:54 +020092
Willy Tarreauabe45b62010-10-28 20:33:46 +020093#define FILT_COUNT_URL_ONLY 0x004000
94#define FILT_COUNT_URL_COUNT 0x008000
95#define FILT_COUNT_URL_ERR 0x010000
96#define FILT_COUNT_URL_TTOT 0x020000
97#define FILT_COUNT_URL_TAVG 0x040000
98#define FILT_COUNT_URL_TTOTO 0x080000
99#define FILT_COUNT_URL_TAVGO 0x100000
Willy Tarreauabe45b62010-10-28 20:33:46 +0200100
Willy Tarreau70c428f2011-07-10 17:27:40 +0200101#define FILT_HTTP_ONLY 0x200000
Willy Tarreaud3007ff2011-09-05 02:07:23 +0200102#define FILT_TERM_CODE_NAME 0x400000
Hervé COMMOWICK927cddd2011-08-10 17:42:41 +0200103#define FILT_INVERT_TERM_CODE_NAME 0x800000
Willy Tarreau70c428f2011-07-10 17:27:40 +0200104
Willy Tarreaud3007ff2011-09-05 02:07:23 +0200105#define FILT_HTTP_STATUS 0x1000000
106#define FILT_INVERT_HTTP_STATUS 0x2000000
Willy Tarreau08911ff2011-10-13 13:28:36 +0200107#define FILT_QUEUE_ONLY 0x4000000
108#define FILT_QUEUE_SRV_ONLY 0x8000000
Willy Tarreaud3007ff2011-09-05 02:07:23 +0200109
Baptiste61aaad02012-09-08 23:10:03 +0200110#define FILT_COUNT_URL_BAVG 0x10000000
111#define FILT_COUNT_URL_BTOT 0x20000000
112
113#define FILT_COUNT_URL_ANY (FILT_COUNT_URL_ONLY|FILT_COUNT_URL_COUNT|FILT_COUNT_URL_ERR| \
114 FILT_COUNT_URL_TTOT|FILT_COUNT_URL_TAVG|FILT_COUNT_URL_TTOTO|FILT_COUNT_URL_TAVGO| \
115 FILT_COUNT_URL_BAVG|FILT_COUNT_URL_BTOT)
116
Willy Tarreau8a09b662012-10-10 10:26:22 +0200117#define FILT_COUNT_COOK_CODES 0x40000000
Willy Tarreau7cf479c2013-02-16 23:49:04 +0100118#define FILT_COUNT_IP_COUNT 0x80000000
Willy Tarreau8a09b662012-10-10 10:26:22 +0200119
Olivier Burgarde97b9042014-05-22 16:44:59 +0200120#define FILT2_TIMESTAMP 0x01
121
Willy Tarreau72c28532009-01-22 18:56:50 +0100122unsigned int filter = 0;
Olivier Burgarde97b9042014-05-22 16:44:59 +0200123unsigned int filter2 = 0;
Willy Tarreau72c28532009-01-22 18:56:50 +0100124unsigned int filter_invert = 0;
Willy Tarreau214c2032009-02-20 11:02:32 +0100125const char *line;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200126int linenum = 0;
127int parse_err = 0;
128int lines_out = 0;
Willy Tarreau667c9052012-10-10 16:49:28 +0200129int lines_max = -1;
Willy Tarreau72c28532009-01-22 18:56:50 +0100130
Willy Tarreau214c2032009-02-20 11:02:32 +0100131const char *fgets2(FILE *stream);
Willy Tarreau72c28532009-01-22 18:56:50 +0100132
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200133void filter_count_url(const char *accept_field, const char *time_field, struct timer **tptr);
Willy Tarreau7cf479c2013-02-16 23:49:04 +0100134void filter_count_ip(const char *source_field, const char *accept_field, const char *time_field, struct timer **tptr);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200135void filter_count_srv_status(const char *accept_field, const char *time_field, struct timer **tptr);
Willy Tarreau8a09b662012-10-10 10:26:22 +0200136void filter_count_cook_codes(const char *accept_field, const char *time_field, struct timer **tptr);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200137void filter_count_term_codes(const char *accept_field, const char *time_field, struct timer **tptr);
138void filter_count_status(const char *accept_field, const char *time_field, struct timer **tptr);
139void filter_graphs(const char *accept_field, const char *time_field, struct timer **tptr);
140void filter_output_line(const char *accept_field, const char *time_field, struct timer **tptr);
141void filter_accept_holes(const char *accept_field, const char *time_field, struct timer **tptr);
142
Willy Tarreau615674c2012-01-23 08:15:51 +0100143void usage(FILE *output, const char *msg)
Willy Tarreau72c28532009-01-22 18:56:50 +0100144{
Willy Tarreau615674c2012-01-23 08:15:51 +0100145 fprintf(output,
Willy Tarreau72c28532009-01-22 18:56:50 +0100146 "%s"
Willy Tarreau615674c2012-01-23 08:15:51 +0100147 "Usage: halog [-h|--help] for long help\n"
Willy Tarreau667c9052012-10-10 16:49:28 +0200148 " halog [-q] [-c] [-m <lines>]\n"
Willy Tarreau7cf479c2013-02-16 23:49:04 +0100149 " {-cc|-gt|-pct|-st|-tc|-srv|-u|-uc|-ue|-ua|-ut|-uao|-uto|-uba|-ubt|-ic}\n"
Hervé COMMOWICK927cddd2011-08-10 17:42:41 +0200150 " [-s <skip>] [-e|-E] [-H] [-rt|-RT <time>] [-ad <delay>] [-ac <count>]\n"
Olivier Burgarde97b9042014-05-22 16:44:59 +0200151 " [-v] [-Q|-QS] [-tcn|-TCN <termcode>] [ -hs|-HS [min][:[max]] ] [ -time [min][:[max]] ] < log\n"
Willy Tarreau72c28532009-01-22 18:56:50 +0100152 "\n",
153 msg ? msg : ""
154 );
Willy Tarreau615674c2012-01-23 08:15:51 +0100155}
156
157void die(const char *msg)
158{
159 usage(stderr, msg);
Willy Tarreau72c28532009-01-22 18:56:50 +0100160 exit(1);
161}
162
Willy Tarreau615674c2012-01-23 08:15:51 +0100163void help()
164{
165 usage(stdout, NULL);
166 printf(
167 "Input filters (several filters may be combined) :\n"
168 " -H only match lines containing HTTP logs (ignore TCP)\n"
169 " -E only match lines without any error (no 5xx status)\n"
170 " -e only match lines with errors (status 5xx or negative)\n"
171 " -rt|-RT <time> only match response times larger|smaller than <time>\n"
172 " -Q|-QS only match queued requests (any queue|server queue)\n"
173 " -tcn|-TCN <code> only match requests with/without termination code <code>\n"
174 " -hs|-HS <[min][:][max]> only match requests with HTTP status codes within/not\n"
175 " within min..max. Any of them may be omitted. Exact\n"
176 " code is checked for if no ':' is specified.\n"
Olivier Burgarde97b9042014-05-22 16:44:59 +0200177 " -time <[min][:max]> only match requests recorded between timestamps.\n"
178 " Any of them may be omitted.\n"
Willy Tarreau615674c2012-01-23 08:15:51 +0100179 "Modifiers\n"
180 " -v invert the input filtering condition\n"
181 " -q don't report errors/warnings\n"
Willy Tarreau667c9052012-10-10 16:49:28 +0200182 " -m <lines> limit output to the first <lines> lines\n"
Aleksandar Lazicf2b5d752017-12-05 01:35:21 +0100183 " -s <skip_n_fields> skip n fields from the beginning of a line (default %d)\n"
184 " you can also use -n to start from earlier then field %d\n"
185 "\n"
Willy Tarreau615674c2012-01-23 08:15:51 +0100186 "Output filters - only one may be used at a time\n"
187 " -c only report the number of lines that would have been printed\n"
188 " -pct output connect and response times percentiles\n"
189 " -st output number of requests per HTTP status code\n"
Willy Tarreau8a09b662012-10-10 10:26:22 +0200190 " -cc output number of requests per cookie code (2 chars)\n"
Willy Tarreau615674c2012-01-23 08:15:51 +0100191 " -tc output number of requests per termination code (2 chars)\n"
192 " -srv output statistics per server (time, requests, errors)\n"
193 " -u* output statistics per URL (time, requests, errors)\n"
194 " Additional characters indicate the output sorting key :\n"
195 " -u : by URL, -uc : request count, -ue : error count\n"
Willy Tarreau4201df72012-10-10 14:57:35 +0200196 " -ua : average response time, -ut : average total time\n"
Willy Tarreau615674c2012-01-23 08:15:51 +0100197 " -uao, -uto: average times computed on valid ('OK') requests\n"
Aleksandar Lazicf2b5d752017-12-05 01:35:21 +0100198 " -uba, -ubt: average bytes returned, total bytes returned\n",
199 SOURCE_FIELD,SOURCE_FIELD
Willy Tarreau615674c2012-01-23 08:15:51 +0100200 );
201 exit(0);
202}
203
Willy Tarreau72c28532009-01-22 18:56:50 +0100204
205/* return pointer to first char not part of current field starting at <p>. */
Willy Tarreauf9042062011-09-10 12:26:35 +0200206
207#if defined(__i386__)
208/* this one is always faster on 32-bits */
209static inline const char *field_stop(const char *p)
210{
211 asm(
212 /* Look for spaces */
213 "4: \n\t"
214 "inc %0 \n\t"
215 "cmpb $0x20, -1(%0) \n\t"
216 "ja 4b \n\t"
217 "jz 3f \n\t"
218
219 /* we only get there for control chars 0..31. Leave if we find '\0' */
220 "cmpb $0x0, -1(%0) \n\t"
221 "jnz 4b \n\t"
222
223 /* return %0-1 = position of the last char we checked */
224 "3: \n\t"
225 "dec %0 \n\t"
226 : "=r" (p)
227 : "0" (p)
228 );
229 return p;
230}
231#else
Willy Tarreau72c28532009-01-22 18:56:50 +0100232const char *field_stop(const char *p)
233{
234 unsigned char c;
235
236 while (1) {
237 c = *(p++);
238 if (c > ' ')
239 continue;
Willy Tarreau14389e72011-07-10 22:11:17 +0200240 if (c == ' ' || c == 0)
Willy Tarreau72c28532009-01-22 18:56:50 +0100241 break;
242 }
243 return p - 1;
244}
Willy Tarreauf9042062011-09-10 12:26:35 +0200245#endif
Willy Tarreau72c28532009-01-22 18:56:50 +0100246
247/* return field <field> (starting from 1) in string <p>. Only consider
248 * contiguous spaces (or tabs) as one delimiter. May return pointer to
249 * last char if field is not found. Equivalent to awk '{print $field}'.
250 */
251const char *field_start(const char *p, int field)
252{
Willy Tarreauf9042062011-09-10 12:26:35 +0200253#ifndef PREFER_ASM
Willy Tarreau72c28532009-01-22 18:56:50 +0100254 unsigned char c;
255 while (1) {
256 /* skip spaces */
257 while (1) {
Willy Tarreauf9042062011-09-10 12:26:35 +0200258 c = *(p++);
Willy Tarreau72c28532009-01-22 18:56:50 +0100259 if (c > ' ')
260 break;
Willy Tarreau14389e72011-07-10 22:11:17 +0200261 if (c == ' ')
Willy Tarreauf9042062011-09-10 12:26:35 +0200262 continue;
Willy Tarreau72c28532009-01-22 18:56:50 +0100263 if (!c) /* end of line */
Willy Tarreauf9042062011-09-10 12:26:35 +0200264 return p-1;
Willy Tarreau72c28532009-01-22 18:56:50 +0100265 /* other char => new field */
266 break;
Willy Tarreau72c28532009-01-22 18:56:50 +0100267 }
268
269 /* start of field */
270 field--;
271 if (!field)
Willy Tarreauf9042062011-09-10 12:26:35 +0200272 return p-1;
Willy Tarreau72c28532009-01-22 18:56:50 +0100273
274 /* skip this field */
275 while (1) {
276 c = *(p++);
Willy Tarreau14389e72011-07-10 22:11:17 +0200277 if (c == ' ')
Willy Tarreau72c28532009-01-22 18:56:50 +0100278 break;
Willy Tarreauf9042062011-09-10 12:26:35 +0200279 if (c > ' ')
280 continue;
Willy Tarreau72c28532009-01-22 18:56:50 +0100281 if (c == '\0')
Willy Tarreauf9042062011-09-10 12:26:35 +0200282 return p - 1;
Willy Tarreau72c28532009-01-22 18:56:50 +0100283 }
284 }
Willy Tarreauf9042062011-09-10 12:26:35 +0200285#else
286 /* This version works optimally on i386 and x86_64 but the code above
287 * shows similar performance. However, depending on the version of GCC
288 * used, inlining rules change and it may have difficulties to make
289 * efficient use of this code at other locations and could result in
290 * worse performance (eg: gcc 4.4). You may want to experience.
291 */
292 asm(
293 /* skip spaces */
294 "1: \n\t"
295 "inc %0 \n\t"
296 "cmpb $0x20, -1(%0) \n\t"
297 "ja 2f \n\t"
298 "jz 1b \n\t"
299
300 /* we only get there for control chars 0..31. Leave if we find '\0' */
301 "cmpb $0x0, -1(%0) \n\t"
302 "jz 3f \n\t"
303
304 /* start of field at [%0-1]. Check if we need to skip more fields */
305 "2: \n\t"
306 "dec %1 \n\t"
307 "jz 3f \n\t"
308
309 /* Look for spaces */
310 "4: \n\t"
311 "inc %0 \n\t"
312 "cmpb $0x20, -1(%0) \n\t"
313 "jz 1b \n\t"
314 "ja 4b \n\t"
315
316 /* we only get there for control chars 0..31. Leave if we find '\0' */
317 "cmpb $0x0, -1(%0) \n\t"
318 "jnz 4b \n\t"
319
320 /* return %0-1 = position of the last char we checked */
321 "3: \n\t"
322 "dec %0 \n\t"
323 : "=r" (p)
324 : "r" (field), "0" (p)
325 );
326 return p;
327#endif
Willy Tarreau72c28532009-01-22 18:56:50 +0100328}
329
330/* keep only the <bits> higher bits of <i> */
331static inline unsigned int quantify_u32(unsigned int i, int bits)
332{
333 int high;
334
335 if (!bits)
336 return 0;
337
338 if (i)
339 high = fls_auto(i); // 1 to 32
340 else
341 high = 0;
342
343 if (high <= bits)
344 return i;
345
346 return i & ~((1 << (high - bits)) - 1);
347}
348
349/* keep only the <bits> higher bits of the absolute value of <i>, as well as
350 * its sign. */
351static inline int quantify(int i, int bits)
352{
353 if (i >= 0)
354 return quantify_u32(i, bits);
355 else
356 return -quantify_u32(-i, bits);
357}
358
359/* Insert timer value <v> into tree <r>. A pre-allocated node must be passed
360 * in <alloc>. It may be NULL, in which case the function will allocate it
361 * itself. It will be reset to NULL once consumed. The caller is responsible
362 * for freeing the node once not used anymore. The node where the value was
363 * inserted is returned.
364 */
365struct timer *insert_timer(struct eb_root *r, struct timer **alloc, int v)
366{
367 struct timer *t = *alloc;
368 struct eb32_node *n;
369
370 if (!t) {
371 t = calloc(sizeof(*t), 1);
372 if (unlikely(!t)) {
373 fprintf(stderr, "%s: not enough memory\n", __FUNCTION__);
374 exit(1);
375 }
376 }
377 t->node.key = quantify(v, QBITS); // keep only the higher QBITS bits
378
379 n = eb32i_insert(r, &t->node);
380 if (n == &t->node)
381 t = NULL; /* node inserted, will malloc next time */
382
383 *alloc = t;
384 return container_of(n, struct timer, node);
385}
386
387/* Insert value value <v> into tree <r>. A pre-allocated node must be passed
388 * in <alloc>. It may be NULL, in which case the function will allocate it
389 * itself. It will be reset to NULL once consumed. The caller is responsible
390 * for freeing the node once not used anymore. The node where the value was
391 * inserted is returned.
392 */
393struct timer *insert_value(struct eb_root *r, struct timer **alloc, int v)
394{
395 struct timer *t = *alloc;
396 struct eb32_node *n;
397
398 if (!t) {
399 t = calloc(sizeof(*t), 1);
400 if (unlikely(!t)) {
401 fprintf(stderr, "%s: not enough memory\n", __FUNCTION__);
402 exit(1);
403 }
404 }
405 t->node.key = v;
406
407 n = eb32i_insert(r, &t->node);
408 if (n == &t->node)
409 t = NULL; /* node inserted, will malloc next time */
410
411 *alloc = t;
412 return container_of(n, struct timer, node);
413}
414
415int str2ic(const char *s)
416{
417 int i = 0;
418 int j, k;
419
420 if (*s != '-') {
421 /* positive number */
422 while (1) {
423 j = (*s++) - '0';
424 k = i * 10;
425 if ((unsigned)j > 9)
426 break;
427 i = k + j;
428 }
429 } else {
430 /* negative number */
431 s++;
432 while (1) {
433 j = (*s++) - '0';
434 k = i * 10;
435 if ((unsigned)j > 9)
436 break;
437 i = k - j;
438 }
439 }
440
441 return i;
442}
443
444
445/* Equivalent to strtoul with a length. */
446static inline unsigned int __strl2ui(const char *s, int len)
447{
448 unsigned int i = 0;
449 while (len-- > 0) {
450 i = i * 10 - '0';
451 i += (unsigned char)*s++;
452 }
453 return i;
454}
455
456unsigned int strl2ui(const char *s, int len)
457{
458 return __strl2ui(s, len);
459}
460
461/* Convert "[04/Dec/2008:09:49:40.555]" to an integer equivalent to the time of
462 * the day in milliseconds. It returns -1 for all unparsable values. The parser
463 * looks ugly but gcc emits far better code that way.
464 */
465int convert_date(const char *field)
466{
467 unsigned int h, m, s, ms;
468 unsigned char c;
469 const char *b, *e;
470
471 h = m = s = ms = 0;
472 e = field;
473
474 /* skip the date */
475 while (1) {
476 c = *(e++);
477 if (c == ':')
478 break;
479 if (!c)
480 goto out_err;
481 }
482
483 /* hour + ':' */
484 b = e;
485 while (1) {
486 c = *(e++) - '0';
487 if (c > 9)
488 break;
489 h = h * 10 + c;
490 }
491 if (c == (unsigned char)(0 - '0'))
492 goto out_err;
493
494 /* minute + ':' */
495 b = e;
496 while (1) {
497 c = *(e++) - '0';
498 if (c > 9)
499 break;
500 m = m * 10 + c;
501 }
502 if (c == (unsigned char)(0 - '0'))
503 goto out_err;
504
505 /* second + '.' or ']' */
506 b = e;
507 while (1) {
508 c = *(e++) - '0';
509 if (c > 9)
510 break;
511 s = s * 10 + c;
512 }
513 if (c == (unsigned char)(0 - '0'))
514 goto out_err;
515
516 /* if there's a '.', we have milliseconds */
517 if (c == (unsigned char)('.' - '0')) {
518 /* millisecond second + ']' */
519 b = e;
520 while (1) {
521 c = *(e++) - '0';
522 if (c > 9)
523 break;
524 ms = ms * 10 + c;
525 }
526 if (c == (unsigned char)(0 - '0'))
527 goto out_err;
528 }
529 return (((h * 60) + m) * 60 + s) * 1000 + ms;
530 out_err:
531 return -1;
532}
533
Olivier Burgarde97b9042014-05-22 16:44:59 +0200534/* Convert "[04/Dec/2008:09:49:40.555]" to an unix timestamp.
535 * It returns -1 for all unparsable values. The parser
536 * looks ugly but gcc emits far better code that way.
537 */
538int convert_date_to_timestamp(const char *field)
539{
540 unsigned int d, mo, y, h, m, s;
541 unsigned char c;
542 const char *b, *e;
543 time_t rawtime;
Willy Tarreau9f66aa92014-05-23 16:36:56 +0200544 static struct tm * timeinfo;
545 static int last_res;
Olivier Burgarde97b9042014-05-22 16:44:59 +0200546
547 d = mo = y = h = m = s = 0;
548 e = field;
549
550 c = *(e++); // remove '['
551 /* day + '/' */
552 while (1) {
553 c = *(e++) - '0';
554 if (c > 9)
555 break;
556 d = d * 10 + c;
557 if (c == (unsigned char)(0 - '0'))
558 goto out_err;
559 }
560
561 /* month + '/' */
562 c = *(e++);
563 if (c =='F') {
564 mo = 2;
565 e = e+3;
566 } else if (c =='S') {
567 mo = 9;
568 e = e+3;
569 } else if (c =='O') {
570 mo = 10;
571 e = e+3;
572 } else if (c =='N') {
573 mo = 11;
574 e = e+3;
575 } else if (c == 'D') {
576 mo = 12;
577 e = e+3;
578 } else if (c == 'A') {
579 c = *(e++);
580 if (c == 'p') {
581 mo = 4;
582 e = e+2;
583 } else if (c == 'u') {
584 mo = 8;
585 e = e+2;
586 } else
587 goto out_err;
588 } else if (c == 'J') {
589 c = *(e++);
590 if (c == 'a') {
591 mo = 1;
592 e = e+2;
593 } else if (c == 'u') {
594 c = *(e++);
595 if (c == 'n') {
596 mo = 6;
597 e = e+1;
598 } else if (c == 'l') {
599 mo = 7;
600 e++;
601 }
602 } else
603 goto out_err;
604 } else if (c == 'M') {
605 e++;
606 c = *(e++);
607 if (c == 'r') {
608 mo = 3;
609 e = e+1;
610 } else if (c == 'y') {
611 mo = 5;
612 e = e+1;
613 } else
614 goto out_err;
615 } else
616 goto out_err;
617
618 /* year + ':' */
619 while (1) {
620 c = *(e++) - '0';
621 if (c > 9)
622 break;
623 y = y * 10 + c;
624 if (c == (unsigned char)(0 - '0'))
625 goto out_err;
626 }
627
628 /* hour + ':' */
629 b = e;
630 while (1) {
631 c = *(e++) - '0';
632 if (c > 9)
633 break;
634 h = h * 10 + c;
635 }
636 if (c == (unsigned char)(0 - '0'))
637 goto out_err;
638
639 /* minute + ':' */
640 b = e;
641 while (1) {
642 c = *(e++) - '0';
643 if (c > 9)
644 break;
645 m = m * 10 + c;
646 }
647 if (c == (unsigned char)(0 - '0'))
648 goto out_err;
649
650 /* second + '.' or ']' */
651 b = e;
652 while (1) {
653 c = *(e++) - '0';
654 if (c > 9)
655 break;
656 s = s * 10 + c;
657 }
658
Willy Tarreau9f66aa92014-05-23 16:36:56 +0200659 if (likely(timeinfo)) {
660 if (timeinfo->tm_min == m &&
661 timeinfo->tm_hour == h &&
662 timeinfo->tm_mday == d &&
663 timeinfo->tm_mon == mo - 1 &&
664 timeinfo->tm_year == y - 1900)
665 return last_res + s;
666 }
667 else {
668 time(&rawtime);
669 timeinfo = localtime(&rawtime);
670 }
Olivier Burgarde97b9042014-05-22 16:44:59 +0200671
Willy Tarreau9f66aa92014-05-23 16:36:56 +0200672 timeinfo->tm_sec = 0;
Olivier Burgarde97b9042014-05-22 16:44:59 +0200673 timeinfo->tm_min = m;
674 timeinfo->tm_hour = h;
675 timeinfo->tm_mday = d;
676 timeinfo->tm_mon = mo - 1;
677 timeinfo->tm_year = y - 1900;
Willy Tarreau9f66aa92014-05-23 16:36:56 +0200678 last_res = mktime(timeinfo);
Olivier Burgarde97b9042014-05-22 16:44:59 +0200679
Willy Tarreau9f66aa92014-05-23 16:36:56 +0200680 return last_res + s;
Olivier Burgarde97b9042014-05-22 16:44:59 +0200681 out_err:
682 return -1;
683}
684
Willy Tarreau72c28532009-01-22 18:56:50 +0100685void truncated_line(int linenum, const char *line)
686{
687 if (!(filter & FILT_QUIET))
688 fprintf(stderr, "Truncated line %d: %s\n", linenum, line);
689}
690
691int main(int argc, char **argv)
692{
Willy Tarreau7cf479c2013-02-16 23:49:04 +0100693 const char *b, *e, *p, *time_field, *accept_field, *source_field;
Hervé COMMOWICK927cddd2011-08-10 17:42:41 +0200694 const char *filter_term_code_name = NULL;
Willy Tarreau72c28532009-01-22 18:56:50 +0100695 const char *output_file = NULL;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200696 int f, last, err;
697 struct timer *t = NULL;
Willy Tarreau72c28532009-01-22 18:56:50 +0100698 struct eb32_node *n;
Willy Tarreauabe45b62010-10-28 20:33:46 +0200699 struct url_stat *ustat = NULL;
Willy Tarreau72c28532009-01-22 18:56:50 +0100700 int val, test;
Willy Tarreauc8746532014-05-28 23:05:07 +0200701 unsigned int uval;
Willy Tarreau72c28532009-01-22 18:56:50 +0100702 int filter_acc_delay = 0, filter_acc_count = 0;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200703 int filter_time_resp = 0;
Willy Tarreaud3007ff2011-09-05 02:07:23 +0200704 int filt_http_status_low = 0, filt_http_status_high = 0;
Olivier Burgarde97b9042014-05-22 16:44:59 +0200705 int filt2_timestamp_low = 0, filt2_timestamp_high = 0;
Willy Tarreau72c28532009-01-22 18:56:50 +0100706 int skip_fields = 1;
707
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200708 void (*line_filter)(const char *accept_field, const char *time_field, struct timer **tptr) = NULL;
709
Willy Tarreau72c28532009-01-22 18:56:50 +0100710 argc--; argv++;
711 while (argc > 0) {
712 if (*argv[0] != '-')
713 break;
714
715 if (strcmp(argv[0], "-ad") == 0) {
716 if (argc < 2) die("missing option for -ad");
717 argc--; argv++;
718 filter |= FILT_ACC_DELAY;
719 filter_acc_delay = atol(*argv);
720 }
721 else if (strcmp(argv[0], "-ac") == 0) {
722 if (argc < 2) die("missing option for -ac");
723 argc--; argv++;
724 filter |= FILT_ACC_COUNT;
725 filter_acc_count = atol(*argv);
726 }
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200727 else if (strcmp(argv[0], "-rt") == 0) {
728 if (argc < 2) die("missing option for -rt");
729 argc--; argv++;
730 filter |= FILT_TIME_RESP;
731 filter_time_resp = atol(*argv);
732 }
733 else if (strcmp(argv[0], "-RT") == 0) {
734 if (argc < 2) die("missing option for -RT");
735 argc--; argv++;
736 filter |= FILT_TIME_RESP | FILT_INVERT_TIME_RESP;
737 filter_time_resp = atol(*argv);
738 }
Willy Tarreau72c28532009-01-22 18:56:50 +0100739 else if (strcmp(argv[0], "-s") == 0) {
740 if (argc < 2) die("missing option for -s");
741 argc--; argv++;
742 skip_fields = atol(*argv);
743 }
Willy Tarreau667c9052012-10-10 16:49:28 +0200744 else if (strcmp(argv[0], "-m") == 0) {
745 if (argc < 2) die("missing option for -m");
746 argc--; argv++;
747 lines_max = atol(*argv);
748 }
Willy Tarreau72c28532009-01-22 18:56:50 +0100749 else if (strcmp(argv[0], "-e") == 0)
750 filter |= FILT_ERRORS_ONLY;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200751 else if (strcmp(argv[0], "-E") == 0)
752 filter |= FILT_ERRORS_ONLY | FILT_INVERT_ERRORS;
Willy Tarreau70c428f2011-07-10 17:27:40 +0200753 else if (strcmp(argv[0], "-H") == 0)
754 filter |= FILT_HTTP_ONLY;
Willy Tarreau08911ff2011-10-13 13:28:36 +0200755 else if (strcmp(argv[0], "-Q") == 0)
756 filter |= FILT_QUEUE_ONLY;
757 else if (strcmp(argv[0], "-QS") == 0)
758 filter |= FILT_QUEUE_SRV_ONLY;
Willy Tarreau72c28532009-01-22 18:56:50 +0100759 else if (strcmp(argv[0], "-c") == 0)
760 filter |= FILT_COUNT_ONLY;
761 else if (strcmp(argv[0], "-q") == 0)
762 filter |= FILT_QUIET;
763 else if (strcmp(argv[0], "-v") == 0)
764 filter_invert = !filter_invert;
765 else if (strcmp(argv[0], "-gt") == 0)
766 filter |= FILT_GRAPH_TIMERS;
Willy Tarreau214c2032009-02-20 11:02:32 +0100767 else if (strcmp(argv[0], "-pct") == 0)
768 filter |= FILT_PERCENTILE;
Willy Tarreau0f423a72010-05-03 10:50:54 +0200769 else if (strcmp(argv[0], "-st") == 0)
770 filter |= FILT_COUNT_STATUS;
Willy Tarreaud2201062010-05-27 18:17:30 +0200771 else if (strcmp(argv[0], "-srv") == 0)
772 filter |= FILT_COUNT_SRV_STATUS;
Willy Tarreau8a09b662012-10-10 10:26:22 +0200773 else if (strcmp(argv[0], "-cc") == 0)
774 filter |= FILT_COUNT_COOK_CODES;
Willy Tarreaud8fc1102010-09-12 17:56:16 +0200775 else if (strcmp(argv[0], "-tc") == 0)
776 filter |= FILT_COUNT_TERM_CODES;
Hervé COMMOWICK927cddd2011-08-10 17:42:41 +0200777 else if (strcmp(argv[0], "-tcn") == 0) {
778 if (argc < 2) die("missing option for -tcn");
779 argc--; argv++;
780 filter |= FILT_TERM_CODE_NAME;
781 filter_term_code_name = *argv;
782 }
783 else if (strcmp(argv[0], "-TCN") == 0) {
784 if (argc < 2) die("missing option for -TCN");
785 argc--; argv++;
786 filter |= FILT_TERM_CODE_NAME | FILT_INVERT_TERM_CODE_NAME;
787 filter_term_code_name = *argv;
788 }
Willy Tarreaud3007ff2011-09-05 02:07:23 +0200789 else if (strcmp(argv[0], "-hs") == 0 || strcmp(argv[0], "-HS") == 0) {
790 char *sep, *str;
791
792 if (argc < 2) die("missing option for -hs/-HS ([min]:[max])");
793 filter |= FILT_HTTP_STATUS;
794 if (argv[0][1] == 'H')
795 filter |= FILT_INVERT_HTTP_STATUS;
796
797 argc--; argv++;
798 str = *argv;
799 sep = strchr(str, ':'); /* [min]:[max] */
800 if (!sep)
801 sep = str; /* make max point to min */
802 else
803 *sep++ = 0;
804 filt_http_status_low = *str ? atol(str) : 0;
805 filt_http_status_high = *sep ? atol(sep) : 65535;
806 }
Olivier Burgarde97b9042014-05-22 16:44:59 +0200807 else if (strcmp(argv[0], "-time") == 0) {
808 char *sep, *str;
809
810 if (argc < 2) die("missing option for -time ([min]:[max])");
811 filter2 |= FILT2_TIMESTAMP;
812
813 argc--; argv++;
814 str = *argv;
815 sep = strchr(str, ':'); /* [min]:[max] */
816 filt2_timestamp_low = *str ? atol(str) : 0;
817 if (!sep)
818 filt2_timestamp_high = 0xFFFFFFFF;
819 else
820 filt2_timestamp_high = atol(++sep);
821 }
Willy Tarreauabe45b62010-10-28 20:33:46 +0200822 else if (strcmp(argv[0], "-u") == 0)
823 filter |= FILT_COUNT_URL_ONLY;
824 else if (strcmp(argv[0], "-uc") == 0)
825 filter |= FILT_COUNT_URL_COUNT;
826 else if (strcmp(argv[0], "-ue") == 0)
827 filter |= FILT_COUNT_URL_ERR;
828 else if (strcmp(argv[0], "-ua") == 0)
829 filter |= FILT_COUNT_URL_TAVG;
830 else if (strcmp(argv[0], "-ut") == 0)
831 filter |= FILT_COUNT_URL_TTOT;
832 else if (strcmp(argv[0], "-uao") == 0)
833 filter |= FILT_COUNT_URL_TAVGO;
834 else if (strcmp(argv[0], "-uto") == 0)
835 filter |= FILT_COUNT_URL_TTOTO;
Baptiste61aaad02012-09-08 23:10:03 +0200836 else if (strcmp(argv[0], "-uba") == 0)
837 filter |= FILT_COUNT_URL_BAVG;
838 else if (strcmp(argv[0], "-ubt") == 0)
839 filter |= FILT_COUNT_URL_BTOT;
Willy Tarreau7cf479c2013-02-16 23:49:04 +0100840 else if (strcmp(argv[0], "-ic") == 0)
841 filter |= FILT_COUNT_IP_COUNT;
Willy Tarreau72c28532009-01-22 18:56:50 +0100842 else if (strcmp(argv[0], "-o") == 0) {
843 if (output_file)
844 die("Fatal: output file name already specified.\n");
845 if (argc < 2)
846 die("Fatal: missing output file name.\n");
847 output_file = argv[1];
848 }
Willy Tarreau615674c2012-01-23 08:15:51 +0100849 else if (strcmp(argv[0], "-h") == 0 || strcmp(argv[0], "--help") == 0)
850 help();
Willy Tarreau72c28532009-01-22 18:56:50 +0100851 argc--;
852 argv++;
853 }
854
855 if (!filter)
856 die("No action specified.\n");
857
858 if (filter & FILT_ACC_COUNT && !filter_acc_count)
859 filter_acc_count=1;
860
861 if (filter & FILT_ACC_DELAY && !filter_acc_delay)
862 filter_acc_delay = 1;
863
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200864
865 /* by default, all lines are printed */
866 line_filter = filter_output_line;
867 if (filter & (FILT_ACC_COUNT|FILT_ACC_DELAY))
868 line_filter = filter_accept_holes;
869 else if (filter & (FILT_GRAPH_TIMERS|FILT_PERCENTILE))
870 line_filter = filter_graphs;
871 else if (filter & FILT_COUNT_STATUS)
872 line_filter = filter_count_status;
Willy Tarreau8a09b662012-10-10 10:26:22 +0200873 else if (filter & FILT_COUNT_COOK_CODES)
874 line_filter = filter_count_cook_codes;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200875 else if (filter & FILT_COUNT_TERM_CODES)
876 line_filter = filter_count_term_codes;
877 else if (filter & FILT_COUNT_SRV_STATUS)
878 line_filter = filter_count_srv_status;
879 else if (filter & FILT_COUNT_URL_ANY)
880 line_filter = filter_count_url;
881 else if (filter & FILT_COUNT_ONLY)
882 line_filter = NULL;
Willy Tarreau72c28532009-01-22 18:56:50 +0100883
Willy Tarreauf8c95d22012-06-12 09:16:56 +0200884#if defined(POSIX_FADV_SEQUENTIAL)
885 /* around 20% performance improvement is observed on Linux with this
886 * on cold-cache. Surprizingly, WILLNEED is less performant. Don't
887 * use NOREUSE as it flushes the cache and prevents easy data
888 * manipulation on logs!
889 */
890 posix_fadvise(0, 0, 0, POSIX_FADV_SEQUENTIAL);
891#endif
892
Willy Tarreaua1629a52012-11-13 20:48:15 +0100893 if (!line_filter && /* FILT_COUNT_ONLY ( see above), and no input filter (see below) */
Olivier Burgarde97b9042014-05-22 16:44:59 +0200894 !(filter & (FILT_HTTP_ONLY|FILT_TIME_RESP|FILT_ERRORS_ONLY|FILT_HTTP_STATUS|FILT_QUEUE_ONLY|FILT_QUEUE_SRV_ONLY|FILT_TERM_CODE_NAME)) &&
895 !(filter2 & (FILT2_TIMESTAMP))) {
Willy Tarreaua1629a52012-11-13 20:48:15 +0100896 /* read the whole file at once first, ignore it if inverted output */
Willy Tarreaue1a908c2012-01-03 09:23:03 +0100897 if (!filter_invert)
Willy Tarreaua1629a52012-11-13 20:48:15 +0100898 while ((lines_max < 0 || lines_out < lines_max) && fgets2(stdin) != NULL)
Willy Tarreaue1a908c2012-01-03 09:23:03 +0100899 lines_out++;
900
901 goto skip_filters;
902 }
903
Willy Tarreau214c2032009-02-20 11:02:32 +0100904 while ((line = fgets2(stdin)) != NULL) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100905 linenum++;
Willy Tarreau26deaf52011-07-10 19:47:48 +0200906 time_field = NULL; accept_field = NULL;
Willy Tarreau7cf479c2013-02-16 23:49:04 +0100907 source_field = NULL;
Willy Tarreau72c28532009-01-22 18:56:50 +0100908
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200909 test = 1;
Willy Tarreau26deaf52011-07-10 19:47:48 +0200910
911 /* for any line we process, we first ensure that there is a field
912 * looking like the accept date field (beginning with a '[').
913 */
Willy Tarreau7cf479c2013-02-16 23:49:04 +0100914 if (filter & FILT_COUNT_IP_COUNT) {
915 /* we need the IP first */
916 source_field = field_start(line, SOURCE_FIELD + skip_fields);
917 accept_field = field_start(source_field, ACCEPT_FIELD - SOURCE_FIELD + 1);
918 }
919 else
920 accept_field = field_start(line, ACCEPT_FIELD + skip_fields);
921
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200922 if (unlikely(*accept_field != '[')) {
Willy Tarreau26deaf52011-07-10 19:47:48 +0200923 parse_err++;
924 continue;
925 }
926
927 /* the day of month field is begin 01 and 31 */
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200928 if (accept_field[1] < '0' || accept_field[1] > '3') {
Willy Tarreau26deaf52011-07-10 19:47:48 +0200929 parse_err++;
930 continue;
931 }
932
Olivier Burgarde97b9042014-05-22 16:44:59 +0200933 if (filter2 & FILT2_TIMESTAMP) {
934 uval = convert_date_to_timestamp(accept_field);
935 test &= (uval>=filt2_timestamp_low && uval<=filt2_timestamp_high) ;
936 }
937
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200938 if (filter & FILT_HTTP_ONLY) {
Willy Tarreau70c428f2011-07-10 17:27:40 +0200939 /* only report lines with at least 4 timers */
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200940 if (!time_field) {
Willy Tarreau26deaf52011-07-10 19:47:48 +0200941 time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200942 if (unlikely(!*time_field)) {
Willy Tarreau26deaf52011-07-10 19:47:48 +0200943 truncated_line(linenum, line);
944 continue;
945 }
Willy Tarreau70c428f2011-07-10 17:27:40 +0200946 }
947
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200948 e = field_stop(time_field + 1);
949 /* we have field TIME_FIELD in [time_field]..[e-1] */
950 p = time_field;
Willy Tarreau70c428f2011-07-10 17:27:40 +0200951 f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200952 while (!SEP(*p)) {
Willy Tarreau70c428f2011-07-10 17:27:40 +0200953 if (++f == 4)
954 break;
955 SKIP_CHAR(p, '/');
956 }
957 test &= (f >= 4);
958 }
959
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200960 if (filter & FILT_TIME_RESP) {
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200961 int tps;
962
963 /* only report lines with response times larger than filter_time_resp */
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200964 if (!time_field) {
Willy Tarreau26deaf52011-07-10 19:47:48 +0200965 time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200966 if (unlikely(!*time_field)) {
Willy Tarreau26deaf52011-07-10 19:47:48 +0200967 truncated_line(linenum, line);
968 continue;
969 }
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200970 }
971
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200972 e = field_stop(time_field + 1);
973 /* we have field TIME_FIELD in [time_field]..[e-1], let's check only the response time */
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200974
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200975 p = time_field;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200976 err = 0;
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200977 f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200978 while (!SEP(*p)) {
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200979 tps = str2ic(p);
980 if (tps < 0) {
981 tps = -1;
982 err = 1;
983 }
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200984 if (++f == 4)
985 break;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200986 SKIP_CHAR(p, '/');
987 }
988
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200989 if (unlikely(f < 4)) {
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200990 parse_err++;
991 continue;
992 }
993
994 test &= (tps >= filter_time_resp) ^ !!(filter & FILT_INVERT_TIME_RESP);
995 }
996
Willy Tarreaud3007ff2011-09-05 02:07:23 +0200997 if (filter & (FILT_ERRORS_ONLY | FILT_HTTP_STATUS)) {
998 /* Check both error codes (-1, 5xx) and status code ranges */
Willy Tarreau26deaf52011-07-10 19:47:48 +0200999 if (time_field)
1000 b = field_start(time_field, STATUS_FIELD - TIME_FIELD + 1);
1001 else
1002 b = field_start(accept_field, STATUS_FIELD - ACCEPT_FIELD + 1);
1003
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001004 if (unlikely(!*b)) {
Willy Tarreau72c28532009-01-22 18:56:50 +01001005 truncated_line(linenum, line);
1006 continue;
1007 }
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001008
Willy Tarreaud3007ff2011-09-05 02:07:23 +02001009 val = str2ic(b);
1010 if (filter & FILT_ERRORS_ONLY)
1011 test &= (val < 0 || (val >= 500 && val <= 599)) ^ !!(filter & FILT_INVERT_ERRORS);
1012
1013 if (filter & FILT_HTTP_STATUS)
1014 test &= (val >= filt_http_status_low && val <= filt_http_status_high) ^ !!(filter & FILT_INVERT_HTTP_STATUS);
Willy Tarreau72c28532009-01-22 18:56:50 +01001015 }
1016
Willy Tarreau08911ff2011-10-13 13:28:36 +02001017 if (filter & (FILT_QUEUE_ONLY|FILT_QUEUE_SRV_ONLY)) {
1018 /* Check if the server's queue is non-nul */
1019 if (time_field)
1020 b = field_start(time_field, QUEUE_LEN_FIELD - TIME_FIELD + 1);
1021 else
1022 b = field_start(accept_field, QUEUE_LEN_FIELD - ACCEPT_FIELD + 1);
1023
1024 if (unlikely(!*b)) {
1025 truncated_line(linenum, line);
1026 continue;
1027 }
1028
1029 if (*b == '0') {
1030 if (filter & FILT_QUEUE_SRV_ONLY) {
1031 test = 0;
1032 }
1033 else {
1034 do {
1035 b++;
1036 if (*b == '/') {
1037 b++;
1038 break;
1039 }
1040 } while (*b);
1041 test &= ((unsigned char)(*b - '1') < 9);
1042 }
1043 }
1044 }
1045
Hervé COMMOWICK927cddd2011-08-10 17:42:41 +02001046 if (filter & FILT_TERM_CODE_NAME) {
1047 /* only report corresponding termination code name */
1048 if (time_field)
1049 b = field_start(time_field, TERM_CODES_FIELD - TIME_FIELD + 1);
1050 else
1051 b = field_start(accept_field, TERM_CODES_FIELD - ACCEPT_FIELD + 1);
1052
1053 if (unlikely(!*b)) {
1054 truncated_line(linenum, line);
1055 continue;
1056 }
1057
1058 test &= (b[0] == filter_term_code_name[0] && b[1] == filter_term_code_name[1]) ^ !!(filter & FILT_INVERT_TERM_CODE_NAME);
1059 }
1060
1061
Willy Tarreau0f423a72010-05-03 10:50:54 +02001062 test ^= filter_invert;
1063 if (!test)
1064 continue;
1065
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001066 /************** here we process inputs *******************/
Willy Tarreau72c28532009-01-22 18:56:50 +01001067
Willy Tarreau7cf479c2013-02-16 23:49:04 +01001068 if (line_filter) {
1069 if (filter & FILT_COUNT_IP_COUNT)
1070 filter_count_ip(source_field, accept_field, time_field, &t);
1071 else
1072 line_filter(accept_field, time_field, &t);
1073 }
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001074 else
Willy Tarreaua1629a52012-11-13 20:48:15 +01001075 lines_out++; /* FILT_COUNT_ONLY was used, so we're just counting lines */
1076 if (lines_max >= 0 && lines_out >= lines_max)
Willy Tarreau667c9052012-10-10 16:49:28 +02001077 break;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001078 }
Willy Tarreauabe45b62010-10-28 20:33:46 +02001079
Willy Tarreaue1a908c2012-01-03 09:23:03 +01001080 skip_filters:
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001081 /*****************************************************
1082 * Here we've finished reading all input. Depending on the
1083 * filters, we may still have some analysis to run on the
1084 * collected data and to output data in a new format.
1085 *************************************************** */
Willy Tarreau72c28532009-01-22 18:56:50 +01001086
1087 if (t)
1088 free(t);
1089
1090 if (filter & FILT_COUNT_ONLY) {
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001091 printf("%d\n", lines_out);
Willy Tarreau72c28532009-01-22 18:56:50 +01001092 exit(0);
1093 }
1094
Willy Tarreau72c28532009-01-22 18:56:50 +01001095 if (filter & (FILT_ACC_COUNT|FILT_ACC_DELAY)) {
1096 /* sort and count all timers. Output will look like this :
1097 * <accept_date> <delta_ms from previous one> <nb entries>
1098 */
1099 n = eb32_first(&timers[0]);
1100
1101 if (n)
1102 last = n->key;
1103 while (n) {
1104 unsigned int d, h, m, s, ms;
1105
1106 t = container_of(n, struct timer, node);
1107 h = n->key;
1108 d = h - last;
1109 last = h;
1110
1111 if (d >= filter_acc_delay && t->count >= filter_acc_count) {
1112 ms = h % 1000; h = h / 1000;
1113 s = h % 60; h = h / 60;
1114 m = h % 60; h = h / 60;
Willy Tarreau72c28532009-01-22 18:56:50 +01001115 printf("%02d:%02d:%02d.%03d %d %d %d\n", h, m, s, ms, last, d, t->count);
Willy Tarreau667c9052012-10-10 16:49:28 +02001116 lines_out++;
Willy Tarreaua1629a52012-11-13 20:48:15 +01001117 if (lines_max >= 0 && lines_out >= lines_max)
Willy Tarreau667c9052012-10-10 16:49:28 +02001118 break;
Willy Tarreau72c28532009-01-22 18:56:50 +01001119 }
1120 n = eb32_next(n);
1121 }
1122 }
1123 else if (filter & FILT_GRAPH_TIMERS) {
1124 /* sort all timers */
1125 for (f = 0; f < 5; f++) {
1126 struct eb32_node *n;
1127 int val;
1128
1129 val = 0;
1130 n = eb32_first(&timers[f]);
1131 while (n) {
1132 int i;
1133 double d;
1134
1135 t = container_of(n, struct timer, node);
1136 last = n->key;
1137 val = t->count;
1138
1139 i = (last < 0) ? -last : last;
1140 i = fls_auto(i) - QBITS;
1141
1142 if (i > 0)
1143 d = val / (double)(1 << i);
1144 else
1145 d = val;
1146
Willy Tarreaua1629a52012-11-13 20:48:15 +01001147 if (d > 0.0)
Willy Tarreau72c28532009-01-22 18:56:50 +01001148 printf("%d %d %f\n", f, last, d+1.0);
Willy Tarreau72c28532009-01-22 18:56:50 +01001149
1150 n = eb32_next(n);
1151 }
Willy Tarreau214c2032009-02-20 11:02:32 +01001152 }
1153 }
1154 else if (filter & FILT_PERCENTILE) {
1155 /* report timers by percentile :
1156 * <percent> <total> <max_req_time> <max_conn_time> <max_resp_time> <max_data_time>
1157 * We don't count errs.
1158 */
1159 struct eb32_node *n[5];
1160 unsigned long cum[5];
1161 double step;
1162
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001163 if (!lines_out)
Willy Tarreau910ba4b2009-11-17 10:16:19 +01001164 goto empty;
1165
Willy Tarreau214c2032009-02-20 11:02:32 +01001166 for (f = 1; f < 5; f++) {
1167 n[f] = eb32_first(&timers[f]);
1168 cum[f] = container_of(n[f], struct timer, node)->count;
1169 }
1170
1171 for (step = 1; step <= 1000;) {
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001172 unsigned int thres = lines_out * (step / 1000.0);
Willy Tarreau214c2032009-02-20 11:02:32 +01001173
1174 printf("%3.1f %d ", step/10.0, thres);
1175 for (f = 1; f < 5; f++) {
1176 struct eb32_node *next;
1177 while (cum[f] < thres) {
1178 /* need to find other keys */
1179 next = eb32_next(n[f]);
1180 if (!next)
1181 break;
1182 n[f] = next;
1183 cum[f] += container_of(next, struct timer, node)->count;
1184 }
1185
1186 /* value still within $step % of total */
1187 printf("%d ", n[f]->key);
1188 }
1189 putchar('\n');
1190 if (step >= 100 && step < 900)
1191 step += 50; // jump 5% by 5% between those steps.
1192 else if (step >= 20 && step < 980)
1193 step += 10;
1194 else
1195 step += 1;
Willy Tarreau72c28532009-01-22 18:56:50 +01001196 }
1197 }
Willy Tarreau0f423a72010-05-03 10:50:54 +02001198 else if (filter & FILT_COUNT_STATUS) {
1199 /* output all statuses in the form of <status> <occurrences> */
1200 n = eb32_first(&timers[0]);
1201 while (n) {
1202 t = container_of(n, struct timer, node);
1203 printf("%d %d\n", n->key, t->count);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001204 lines_out++;
Willy Tarreaua1629a52012-11-13 20:48:15 +01001205 if (lines_max >= 0 && lines_out >= lines_max)
Willy Tarreau667c9052012-10-10 16:49:28 +02001206 break;
Willy Tarreau0f423a72010-05-03 10:50:54 +02001207 n = eb32_next(n);
1208 }
1209 }
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001210 else if (filter & FILT_COUNT_SRV_STATUS) {
Willy Tarreaud2201062010-05-27 18:17:30 +02001211 struct ebmb_node *srv_node;
1212 struct srv_st *srv;
1213
1214 printf("#srv_name 1xx 2xx 3xx 4xx 5xx other tot_req req_ok pct_ok avg_ct avg_rt\n");
1215
1216 srv_node = ebmb_first(&timers[0]);
1217 while (srv_node) {
1218 int tot_rq;
1219
1220 srv = container_of(srv_node, struct srv_st, node);
1221
1222 tot_rq = 0;
1223 for (f = 0; f <= 5; f++)
1224 tot_rq += srv->st_cnt[f];
1225
1226 printf("%s %d %d %d %d %d %d %d %d %.1f %d %d\n",
1227 srv_node->key, srv->st_cnt[1], srv->st_cnt[2],
1228 srv->st_cnt[3], srv->st_cnt[4], srv->st_cnt[5], srv->st_cnt[0],
1229 tot_rq,
1230 srv->nb_ok, (double)srv->nb_ok * 100.0 / (tot_rq?tot_rq:1),
1231 (int)(srv->cum_ct / (srv->nb_ct?srv->nb_ct:1)), (int)(srv->cum_rt / (srv->nb_rt?srv->nb_rt:1)));
1232 srv_node = ebmb_next(srv_node);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001233 lines_out++;
Willy Tarreaua1629a52012-11-13 20:48:15 +01001234 if (lines_max >= 0 && lines_out >= lines_max)
Willy Tarreau667c9052012-10-10 16:49:28 +02001235 break;
Willy Tarreaud2201062010-05-27 18:17:30 +02001236 }
1237 }
Willy Tarreau8a09b662012-10-10 10:26:22 +02001238 else if (filter & (FILT_COUNT_TERM_CODES|FILT_COUNT_COOK_CODES)) {
Willy Tarreaud8fc1102010-09-12 17:56:16 +02001239 /* output all statuses in the form of <code> <occurrences> */
1240 n = eb32_first(&timers[0]);
1241 while (n) {
1242 t = container_of(n, struct timer, node);
1243 printf("%c%c %d\n", (n->key >> 8), (n->key) & 255, t->count);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001244 lines_out++;
Willy Tarreaua1629a52012-11-13 20:48:15 +01001245 if (lines_max >= 0 && lines_out >= lines_max)
Willy Tarreau667c9052012-10-10 16:49:28 +02001246 break;
Willy Tarreaud8fc1102010-09-12 17:56:16 +02001247 n = eb32_next(n);
1248 }
1249 }
Willy Tarreau7cf479c2013-02-16 23:49:04 +01001250 else if (filter & (FILT_COUNT_URL_ANY|FILT_COUNT_IP_COUNT)) {
Willy Tarreauabe45b62010-10-28 20:33:46 +02001251 struct eb_node *node, *next;
1252
1253 if (!(filter & FILT_COUNT_URL_ONLY)) {
1254 /* we have to sort on another criterion. We'll use timers[1] for the
1255 * destination tree.
1256 */
1257
1258 timers[1] = EB_ROOT; /* reconfigure to accept duplicates */
1259 for (node = eb_first(&timers[0]); node; node = next) {
1260 next = eb_next(node);
1261 eb_delete(node);
1262
1263 ustat = container_of(node, struct url_stat, node.url.node);
1264
Willy Tarreau7cf479c2013-02-16 23:49:04 +01001265 if (filter & (FILT_COUNT_URL_COUNT|FILT_COUNT_IP_COUNT))
Willy Tarreauabe45b62010-10-28 20:33:46 +02001266 ustat->node.val.key = ustat->nb_req;
1267 else if (filter & FILT_COUNT_URL_ERR)
1268 ustat->node.val.key = ustat->nb_err;
1269 else if (filter & FILT_COUNT_URL_TTOT)
1270 ustat->node.val.key = ustat->total_time;
1271 else if (filter & FILT_COUNT_URL_TAVG)
1272 ustat->node.val.key = ustat->nb_req ? ustat->total_time / ustat->nb_req : 0;
1273 else if (filter & FILT_COUNT_URL_TTOTO)
1274 ustat->node.val.key = ustat->total_time_ok;
1275 else if (filter & FILT_COUNT_URL_TAVGO)
1276 ustat->node.val.key = (ustat->nb_req - ustat->nb_err) ? ustat->total_time_ok / (ustat->nb_req - ustat->nb_err) : 0;
Baptiste61aaad02012-09-08 23:10:03 +02001277 else if (filter & FILT_COUNT_URL_BAVG)
1278 ustat->node.val.key = ustat->nb_req ? ustat->total_bytes_sent / ustat->nb_req : 0;
1279 else if (filter & FILT_COUNT_URL_BTOT)
1280 ustat->node.val.key = ustat->total_bytes_sent;
Willy Tarreauabe45b62010-10-28 20:33:46 +02001281 else
1282 ustat->node.val.key = 0;
1283
1284 eb64_insert(&timers[1], &ustat->node.val);
1285 }
1286 /* switch trees */
1287 timers[0] = timers[1];
1288 }
1289
Willy Tarreau7cf479c2013-02-16 23:49:04 +01001290 if (FILT_COUNT_IP_COUNT)
1291 printf("#req err ttot tavg oktot okavg bavg btot src\n");
1292 else
1293 printf("#req err ttot tavg oktot okavg bavg btot url\n");
Willy Tarreauabe45b62010-10-28 20:33:46 +02001294
1295 /* scan the tree in its reverse sorting order */
1296 node = eb_last(&timers[0]);
1297 while (node) {
1298 ustat = container_of(node, struct url_stat, node.url.node);
Baptiste61aaad02012-09-08 23:10:03 +02001299 printf("%d %d %Ld %Ld %Ld %Ld %Ld %Ld %s\n",
Willy Tarreauabe45b62010-10-28 20:33:46 +02001300 ustat->nb_req,
1301 ustat->nb_err,
1302 ustat->total_time,
1303 ustat->nb_req ? ustat->total_time / ustat->nb_req : 0,
1304 ustat->total_time_ok,
1305 (ustat->nb_req - ustat->nb_err) ? ustat->total_time_ok / (ustat->nb_req - ustat->nb_err) : 0,
Baptiste61aaad02012-09-08 23:10:03 +02001306 ustat->nb_req ? ustat->total_bytes_sent / ustat->nb_req : 0,
1307 ustat->total_bytes_sent,
Willy Tarreauabe45b62010-10-28 20:33:46 +02001308 ustat->url);
1309
1310 node = eb_prev(node);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001311 lines_out++;
Willy Tarreaua1629a52012-11-13 20:48:15 +01001312 if (lines_max >= 0 && lines_out >= lines_max)
Willy Tarreau667c9052012-10-10 16:49:28 +02001313 break;
Willy Tarreauabe45b62010-10-28 20:33:46 +02001314 }
1315 }
Willy Tarreaud2201062010-05-27 18:17:30 +02001316
Willy Tarreau910ba4b2009-11-17 10:16:19 +01001317 empty:
Willy Tarreau72c28532009-01-22 18:56:50 +01001318 if (!(filter & FILT_QUIET))
1319 fprintf(stderr, "%d lines in, %d lines out, %d parsing errors\n",
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001320 linenum, lines_out, parse_err);
Willy Tarreau72c28532009-01-22 18:56:50 +01001321 exit(0);
1322}
1323
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001324void filter_output_line(const char *accept_field, const char *time_field, struct timer **tptr)
1325{
1326 puts(line);
1327 lines_out++;
1328}
1329
1330void filter_accept_holes(const char *accept_field, const char *time_field, struct timer **tptr)
1331{
1332 struct timer *t2;
1333 int val;
1334
1335 val = convert_date(accept_field);
1336 if (unlikely(val < 0)) {
1337 truncated_line(linenum, line);
1338 return;
1339 }
1340
1341 t2 = insert_value(&timers[0], tptr, val);
1342 t2->count++;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001343 return;
1344}
1345
1346void filter_count_status(const char *accept_field, const char *time_field, struct timer **tptr)
1347{
1348 struct timer *t2;
1349 const char *b;
1350 int val;
1351
1352 if (time_field)
1353 b = field_start(time_field, STATUS_FIELD - TIME_FIELD + 1);
1354 else
1355 b = field_start(accept_field, STATUS_FIELD - ACCEPT_FIELD + 1);
1356
1357 if (unlikely(!*b)) {
1358 truncated_line(linenum, line);
1359 return;
1360 }
1361
1362 val = str2ic(b);
1363
1364 t2 = insert_value(&timers[0], tptr, val);
1365 t2->count++;
1366}
1367
Willy Tarreau8a09b662012-10-10 10:26:22 +02001368void filter_count_cook_codes(const char *accept_field, const char *time_field, struct timer **tptr)
1369{
1370 struct timer *t2;
1371 const char *b;
1372 int val;
1373
1374 if (time_field)
1375 b = field_start(time_field, TERM_CODES_FIELD - TIME_FIELD + 1);
1376 else
1377 b = field_start(accept_field, TERM_CODES_FIELD - ACCEPT_FIELD + 1);
1378
1379 if (unlikely(!*b)) {
1380 truncated_line(linenum, line);
1381 return;
1382 }
1383
1384 val = 256 * b[2] + b[3];
1385
1386 t2 = insert_value(&timers[0], tptr, val);
1387 t2->count++;
1388}
1389
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001390void filter_count_term_codes(const char *accept_field, const char *time_field, struct timer **tptr)
1391{
1392 struct timer *t2;
1393 const char *b;
1394 int val;
1395
1396 if (time_field)
1397 b = field_start(time_field, TERM_CODES_FIELD - TIME_FIELD + 1);
1398 else
1399 b = field_start(accept_field, TERM_CODES_FIELD - ACCEPT_FIELD + 1);
1400
1401 if (unlikely(!*b)) {
1402 truncated_line(linenum, line);
1403 return;
1404 }
1405
1406 val = 256 * b[0] + b[1];
1407
1408 t2 = insert_value(&timers[0], tptr, val);
1409 t2->count++;
1410}
1411
1412void filter_count_srv_status(const char *accept_field, const char *time_field, struct timer **tptr)
1413{
1414 const char *b, *e, *p;
1415 int f, err, array[5];
1416 struct ebmb_node *srv_node;
1417 struct srv_st *srv;
1418 int val;
1419
1420 /* the server field is before the status field, so let's
1421 * parse them in the proper order.
1422 */
1423 b = field_start(accept_field, SERVER_FIELD - ACCEPT_FIELD + 1);
1424 if (unlikely(!*b)) {
1425 truncated_line(linenum, line);
1426 return;
1427 }
1428
1429 e = field_stop(b + 1); /* we have the server name in [b]..[e-1] */
1430
1431 /* the chance that a server name already exists is extremely high,
1432 * so let's perform a normal lookup first.
1433 */
1434 srv_node = ebst_lookup_len(&timers[0], b, e - b);
1435 srv = container_of(srv_node, struct srv_st, node);
1436
1437 if (!srv_node) {
1438 /* server not yet in the tree, let's create it */
1439 srv = (void *)calloc(1, sizeof(struct srv_st) + e - b + 1);
1440 srv_node = &srv->node;
1441 memcpy(&srv_node->key, b, e - b);
1442 srv_node->key[e - b] = '\0';
1443 ebst_insert(&timers[0], srv_node);
1444 }
1445
1446 /* let's collect the connect and response times */
1447 if (!time_field) {
1448 time_field = field_start(e, TIME_FIELD - SERVER_FIELD);
1449 if (unlikely(!*time_field)) {
1450 truncated_line(linenum, line);
1451 return;
1452 }
1453 }
1454
1455 e = field_stop(time_field + 1);
1456 /* we have field TIME_FIELD in [time_field]..[e-1] */
1457
1458 p = time_field;
1459 err = 0;
1460 f = 0;
1461 while (!SEP(*p)) {
1462 array[f] = str2ic(p);
1463 if (array[f] < 0) {
1464 array[f] = -1;
1465 err = 1;
1466 }
1467 if (++f == 5)
1468 break;
1469 SKIP_CHAR(p, '/');
1470 }
1471
1472 if (unlikely(f < 5)){
1473 parse_err++;
1474 return;
1475 }
1476
1477 /* OK we have our timers in array[2,3] */
1478 if (!err)
1479 srv->nb_ok++;
1480
1481 if (array[2] >= 0) {
1482 srv->cum_ct += array[2];
1483 srv->nb_ct++;
1484 }
1485
1486 if (array[3] >= 0) {
1487 srv->cum_rt += array[3];
1488 srv->nb_rt++;
1489 }
1490
1491 /* we're interested in the 5 HTTP status classes (1xx ... 5xx), and
1492 * the invalid ones which will be reported as 0.
1493 */
1494 b = field_start(e, STATUS_FIELD - TIME_FIELD);
1495 if (unlikely(!*b)) {
1496 truncated_line(linenum, line);
1497 return;
1498 }
1499
1500 val = 0;
1501 if (*b >= '1' && *b <= '5')
1502 val = *b - '0';
1503
1504 srv->st_cnt[val]++;
1505}
1506
1507void filter_count_url(const char *accept_field, const char *time_field, struct timer **tptr)
1508{
1509 struct url_stat *ustat = NULL;
1510 struct ebpt_node *ebpt_old;
1511 const char *b, *e;
1512 int f, err, array[5];
Baptiste61aaad02012-09-08 23:10:03 +02001513 int val;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001514
1515 /* let's collect the response time */
1516 if (!time_field) {
1517 time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1); // avg 115 ns per line
1518 if (unlikely(!*time_field)) {
1519 truncated_line(linenum, line);
1520 return;
1521 }
1522 }
1523
1524 /* we have the field TIME_FIELD starting at <time_field>. We'll
1525 * parse the 5 timers to detect errors, it takes avg 55 ns per line.
1526 */
1527 e = time_field; err = 0; f = 0;
1528 while (!SEP(*e)) {
1529 array[f] = str2ic(e);
1530 if (array[f] < 0) {
1531 array[f] = -1;
1532 err = 1;
1533 }
1534 if (++f == 5)
1535 break;
1536 SKIP_CHAR(e, '/');
1537 }
1538 if (f < 5) {
1539 parse_err++;
1540 return;
1541 }
1542
1543 /* OK we have our timers in array[3], and err is >0 if at
1544 * least one -1 was seen. <e> points to the first char of
1545 * the last timer. Let's prepare a new node with that.
1546 */
1547 if (unlikely(!ustat))
1548 ustat = calloc(1, sizeof(*ustat));
1549
1550 ustat->nb_err = err;
1551 ustat->nb_req = 1;
1552
1553 /* use array[4] = total time in case of error */
1554 ustat->total_time = (array[3] >= 0) ? array[3] : array[4];
1555 ustat->total_time_ok = (array[3] >= 0) ? array[3] : 0;
1556
Baptiste61aaad02012-09-08 23:10:03 +02001557 e = field_start(e, BYTES_SENT_FIELD - TIME_FIELD + 1);
1558 val = str2ic(e);
1559 ustat->total_bytes_sent = val;
1560
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001561 /* the line may be truncated because of a bad request or anything like this,
1562 * without a method. Also, if it does not begin with an quote, let's skip to
1563 * the next field because it's a capture. Let's fall back to the "method" itself
1564 * if there's nothing else.
1565 */
Baptiste61aaad02012-09-08 23:10:03 +02001566 e = field_start(e, METH_FIELD - BYTES_SENT_FIELD + 1);
Willy Tarreau61a40c72011-09-06 08:11:27 +02001567 while (*e != '"' && *e) {
1568 /* Note: some syslog servers escape quotes ! */
1569 if (*e == '\\' && e[1] == '"')
1570 break;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001571 e = field_start(e, 2);
Willy Tarreau61a40c72011-09-06 08:11:27 +02001572 }
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001573
1574 if (unlikely(!*e)) {
1575 truncated_line(linenum, line);
Ilya Shipitsin4473a2e2017-09-22 22:33:16 +05001576 free(ustat);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001577 return;
1578 }
1579
1580 b = field_start(e, URL_FIELD - METH_FIELD + 1); // avg 40 ns per line
1581 if (!*b)
1582 b = e;
1583
1584 /* stop at end of field or first ';' or '?', takes avg 64 ns per line */
1585 e = b;
1586 do {
Willy Tarreau14389e72011-07-10 22:11:17 +02001587 if (*e == ' ' || *e == '?' || *e == ';') {
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001588 *(char *)e = 0;
1589 break;
1590 }
1591 e++;
1592 } while (*e);
1593
1594 /* now instead of copying the URL for a simple lookup, we'll link
1595 * to it from the node we're trying to insert. If it returns a
1596 * different value, it was already there. Otherwise we just have
1597 * to dynamically realloc an entry using strdup().
1598 */
1599 ustat->node.url.key = (char *)b;
1600 ebpt_old = ebis_insert(&timers[0], &ustat->node.url);
1601
1602 if (ebpt_old != &ustat->node.url) {
1603 struct url_stat *ustat_old;
1604 /* node was already there, let's update previous one */
1605 ustat_old = container_of(ebpt_old, struct url_stat, node.url);
1606 ustat_old->nb_req ++;
1607 ustat_old->nb_err += ustat->nb_err;
1608 ustat_old->total_time += ustat->total_time;
1609 ustat_old->total_time_ok += ustat->total_time_ok;
Baptiste61aaad02012-09-08 23:10:03 +02001610 ustat_old->total_bytes_sent += ustat->total_bytes_sent;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001611 } else {
1612 ustat->url = ustat->node.url.key = strdup(ustat->node.url.key);
1613 ustat = NULL; /* node was used */
1614 }
1615}
1616
Willy Tarreau7cf479c2013-02-16 23:49:04 +01001617void filter_count_ip(const char *source_field, const char *accept_field, const char *time_field, struct timer **tptr)
1618{
1619 struct url_stat *ustat = NULL;
1620 struct ebpt_node *ebpt_old;
1621 const char *b, *e;
1622 int f, err, array[5];
1623 int val;
1624
1625 /* let's collect the response time */
1626 if (!time_field) {
1627 time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1); // avg 115 ns per line
1628 if (unlikely(!*time_field)) {
1629 truncated_line(linenum, line);
1630 return;
1631 }
1632 }
1633
1634 /* we have the field TIME_FIELD starting at <time_field>. We'll
1635 * parse the 5 timers to detect errors, it takes avg 55 ns per line.
1636 */
1637 e = time_field; err = 0; f = 0;
1638 while (!SEP(*e)) {
1639 if (f == 0 || f == 4) {
1640 array[f] = str2ic(e);
1641 if (array[f] < 0) {
1642 array[f] = -1;
1643 err = 1;
1644 }
1645 }
1646 if (++f == 5)
1647 break;
1648 SKIP_CHAR(e, '/');
1649 }
1650 if (f < 5) {
1651 parse_err++;
1652 return;
1653 }
1654
1655 /* OK we have our timers in array[0], and err is >0 if at
1656 * least one -1 was seen. <e> points to the first char of
1657 * the last timer. Let's prepare a new node with that.
1658 */
1659 if (unlikely(!ustat))
1660 ustat = calloc(1, sizeof(*ustat));
1661
1662 ustat->nb_err = err;
1663 ustat->nb_req = 1;
1664
1665 /* use array[4] = total time in case of error */
1666 ustat->total_time = (array[0] >= 0) ? array[0] : array[4];
1667 ustat->total_time_ok = (array[0] >= 0) ? array[0] : 0;
1668
1669 e = field_start(e, BYTES_SENT_FIELD - TIME_FIELD + 1);
1670 val = str2ic(e);
1671 ustat->total_bytes_sent = val;
1672
1673 /* the source might be IPv4 or IPv6, so we always strip the port by
1674 * removing the last colon.
1675 */
1676 b = source_field;
1677 e = field_stop(b + 1);
1678 while (e > b && e[-1] != ':')
1679 e--;
1680 *(char *)(e - 1) = '\0';
1681
1682 /* now instead of copying the src for a simple lookup, we'll link
1683 * to it from the node we're trying to insert. If it returns a
1684 * different value, it was already there. Otherwise we just have
1685 * to dynamically realloc an entry using strdup(). We're using the
1686 * <url> field of the node to store the source address.
1687 */
1688 ustat->node.url.key = (char *)b;
1689 ebpt_old = ebis_insert(&timers[0], &ustat->node.url);
1690
1691 if (ebpt_old != &ustat->node.url) {
1692 struct url_stat *ustat_old;
1693 /* node was already there, let's update previous one */
1694 ustat_old = container_of(ebpt_old, struct url_stat, node.url);
1695 ustat_old->nb_req ++;
1696 ustat_old->nb_err += ustat->nb_err;
1697 ustat_old->total_time += ustat->total_time;
1698 ustat_old->total_time_ok += ustat->total_time_ok;
1699 ustat_old->total_bytes_sent += ustat->total_bytes_sent;
1700 } else {
1701 ustat->url = ustat->node.url.key = strdup(ustat->node.url.key);
1702 ustat = NULL; /* node was used */
1703 }
1704}
1705
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001706void filter_graphs(const char *accept_field, const char *time_field, struct timer **tptr)
1707{
1708 struct timer *t2;
1709 const char *e, *p;
1710 int f, err, array[5];
1711
1712 if (!time_field) {
1713 time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1);
1714 if (unlikely(!*time_field)) {
1715 truncated_line(linenum, line);
1716 return;
1717 }
1718 }
1719
1720 e = field_stop(time_field + 1);
1721 /* we have field TIME_FIELD in [time_field]..[e-1] */
1722
1723 p = time_field;
1724 err = 0;
1725 f = 0;
1726 while (!SEP(*p)) {
1727 array[f] = str2ic(p);
1728 if (array[f] < 0) {
1729 array[f] = -1;
1730 err = 1;
1731 }
1732 if (++f == 5)
1733 break;
1734 SKIP_CHAR(p, '/');
1735 }
1736
1737 if (unlikely(f < 5)) {
1738 parse_err++;
1739 return;
1740 }
1741
1742 /* if we find at least one negative time, we count one error
1743 * with a time equal to the total session time. This will
1744 * emphasize quantum timing effects associated to known
1745 * timeouts. Note that on some buggy machines, it is possible
1746 * that the total time is negative, hence the reason to reset
1747 * it.
1748 */
1749
1750 if (filter & FILT_GRAPH_TIMERS) {
1751 if (err) {
1752 if (array[4] < 0)
1753 array[4] = -1;
1754 t2 = insert_timer(&timers[0], tptr, array[4]); // total time
1755 t2->count++;
1756 } else {
1757 int v;
1758
1759 t2 = insert_timer(&timers[1], tptr, array[0]); t2->count++; // req
1760 t2 = insert_timer(&timers[2], tptr, array[2]); t2->count++; // conn
1761 t2 = insert_timer(&timers[3], tptr, array[3]); t2->count++; // resp
1762
1763 v = array[4] - array[0] - array[1] - array[2] - array[3]; // data time
1764 if (v < 0 && !(filter & FILT_QUIET))
1765 fprintf(stderr, "ERR: %s (%d %d %d %d %d => %d)\n",
1766 line, array[0], array[1], array[2], array[3], array[4], v);
1767 t2 = insert_timer(&timers[4], tptr, v); t2->count++;
1768 lines_out++;
1769 }
1770 } else { /* percentile */
1771 if (err) {
1772 if (array[4] < 0)
1773 array[4] = -1;
1774 t2 = insert_value(&timers[0], tptr, array[4]); // total time
1775 t2->count++;
1776 } else {
1777 int v;
1778
1779 t2 = insert_value(&timers[1], tptr, array[0]); t2->count++; // req
1780 t2 = insert_value(&timers[2], tptr, array[2]); t2->count++; // conn
1781 t2 = insert_value(&timers[3], tptr, array[3]); t2->count++; // resp
1782
1783 v = array[4] - array[0] - array[1] - array[2] - array[3]; // data time
1784 if (v < 0 && !(filter & FILT_QUIET))
1785 fprintf(stderr, "ERR: %s (%d %d %d %d %d => %d)\n",
1786 line, array[0], array[1], array[2], array[3], array[4], v);
1787 t2 = insert_value(&timers[4], tptr, v); t2->count++;
1788 lines_out++;
1789 }
1790 }
1791}
1792
1793
Willy Tarreau72c28532009-01-22 18:56:50 +01001794/*
1795 * Local variables:
1796 * c-indent-level: 8
1797 * c-basic-offset: 8
1798 * End:
1799 */