blob: 47f10a741d2553b33a0bc132ef0bc7560cd9643c [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 Tarreau8d2b7772020-05-27 10:58:19 +020023#include <import/eb32tree.h>
24#include <import/eb64tree.h>
25#include <import/ebistree.h>
26#include <import/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
Tim Duesterhusb09bdee2021-10-18 12:12:02 +0200120#define FILT2_TIMESTAMP 0x01
121#define FILT2_PRESERVE_QUERY 0x02
Olivier Burgarde97b9042014-05-22 16:44:59 +0200122
Willy Tarreau72c28532009-01-22 18:56:50 +0100123unsigned int filter = 0;
Olivier Burgarde97b9042014-05-22 16:44:59 +0200124unsigned int filter2 = 0;
Willy Tarreau72c28532009-01-22 18:56:50 +0100125unsigned int filter_invert = 0;
Willy Tarreau214c2032009-02-20 11:02:32 +0100126const char *line;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200127int linenum = 0;
128int parse_err = 0;
129int lines_out = 0;
Willy Tarreau667c9052012-10-10 16:49:28 +0200130int lines_max = -1;
Willy Tarreau72c28532009-01-22 18:56:50 +0100131
Willy Tarreau214c2032009-02-20 11:02:32 +0100132const char *fgets2(FILE *stream);
Willy Tarreau72c28532009-01-22 18:56:50 +0100133
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200134void filter_count_url(const char *accept_field, const char *time_field, struct timer **tptr);
Willy Tarreau7cf479c2013-02-16 23:49:04 +0100135void 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 +0200136void filter_count_srv_status(const char *accept_field, const char *time_field, struct timer **tptr);
Willy Tarreau8a09b662012-10-10 10:26:22 +0200137void filter_count_cook_codes(const char *accept_field, const char *time_field, struct timer **tptr);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200138void filter_count_term_codes(const char *accept_field, const char *time_field, struct timer **tptr);
139void filter_count_status(const char *accept_field, const char *time_field, struct timer **tptr);
140void filter_graphs(const char *accept_field, const char *time_field, struct timer **tptr);
141void filter_output_line(const char *accept_field, const char *time_field, struct timer **tptr);
142void filter_accept_holes(const char *accept_field, const char *time_field, struct timer **tptr);
143
Willy Tarreau615674c2012-01-23 08:15:51 +0100144void usage(FILE *output, const char *msg)
Willy Tarreau72c28532009-01-22 18:56:50 +0100145{
Willy Tarreau615674c2012-01-23 08:15:51 +0100146 fprintf(output,
Willy Tarreau72c28532009-01-22 18:56:50 +0100147 "%s"
Willy Tarreau615674c2012-01-23 08:15:51 +0100148 "Usage: halog [-h|--help] for long help\n"
Willy Tarreau667c9052012-10-10 16:49:28 +0200149 " halog [-q] [-c] [-m <lines>]\n"
Willy Tarreau7cf479c2013-02-16 23:49:04 +0100150 " {-cc|-gt|-pct|-st|-tc|-srv|-u|-uc|-ue|-ua|-ut|-uao|-uto|-uba|-ubt|-ic}\n"
Tim Duesterhusa8826662021-10-28 16:36:03 +0200151 " [-s <skip>] [-e|-E] [-H] [-rt|-RT <time>] [-ad <delay>] [-ac <count>] [-query]\n"
Olivier Burgarde97b9042014-05-22 16:44:59 +0200152 " [-v] [-Q|-QS] [-tcn|-TCN <termcode>] [ -hs|-HS [min][:[max]] ] [ -time [min][:[max]] ] < log\n"
Willy Tarreau72c28532009-01-22 18:56:50 +0100153 "\n",
154 msg ? msg : ""
155 );
Willy Tarreau615674c2012-01-23 08:15:51 +0100156}
157
158void die(const char *msg)
159{
160 usage(stderr, msg);
Willy Tarreau72c28532009-01-22 18:56:50 +0100161 exit(1);
162}
163
Willy Tarreau615674c2012-01-23 08:15:51 +0100164void help()
165{
166 usage(stdout, NULL);
167 printf(
168 "Input filters (several filters may be combined) :\n"
169 " -H only match lines containing HTTP logs (ignore TCP)\n"
170 " -E only match lines without any error (no 5xx status)\n"
171 " -e only match lines with errors (status 5xx or negative)\n"
172 " -rt|-RT <time> only match response times larger|smaller than <time>\n"
173 " -Q|-QS only match queued requests (any queue|server queue)\n"
174 " -tcn|-TCN <code> only match requests with/without termination code <code>\n"
175 " -hs|-HS <[min][:][max]> only match requests with HTTP status codes within/not\n"
176 " within min..max. Any of them may be omitted. Exact\n"
177 " code is checked for if no ':' is specified.\n"
Olivier Burgarde97b9042014-05-22 16:44:59 +0200178 " -time <[min][:max]> only match requests recorded between timestamps.\n"
179 " Any of them may be omitted.\n"
Willy Tarreau615674c2012-01-23 08:15:51 +0100180 "Modifiers\n"
181 " -v invert the input filtering condition\n"
182 " -q don't report errors/warnings\n"
Willy Tarreau667c9052012-10-10 16:49:28 +0200183 " -m <lines> limit output to the first <lines> lines\n"
Tim Duesterhusaefb6302021-10-28 15:55:49 +0200184 " -s <skip_n_fields> skip n fields from the beginning of a line (default %d)\n"
185 " you can also use -n to start from earlier then field %d\n"
Tim Duesterhusa8826662021-10-28 16:36:03 +0200186 " -query preserve the query string for per-URL (-u*) statistics\n"
Tim Duesterhusaefb6302021-10-28 15:55:49 +0200187 "\n"
Willy Tarreau615674c2012-01-23 08:15:51 +0100188 "Output filters - only one may be used at a time\n"
189 " -c only report the number of lines that would have been printed\n"
190 " -pct output connect and response times percentiles\n"
191 " -st output number of requests per HTTP status code\n"
Willy Tarreau8a09b662012-10-10 10:26:22 +0200192 " -cc output number of requests per cookie code (2 chars)\n"
Willy Tarreau615674c2012-01-23 08:15:51 +0100193 " -tc output number of requests per termination code (2 chars)\n"
194 " -srv output statistics per server (time, requests, errors)\n"
Aleksandar Lazi6112f5c2020-05-15 22:58:30 +0200195 " -ic output statistics per ip count (time, requests, errors)\n"
Willy Tarreau615674c2012-01-23 08:15:51 +0100196 " -u* output statistics per URL (time, requests, errors)\n"
197 " Additional characters indicate the output sorting key :\n"
198 " -u : by URL, -uc : request count, -ue : error count\n"
Willy Tarreau4201df72012-10-10 14:57:35 +0200199 " -ua : average response time, -ut : average total time\n"
Willy Tarreau615674c2012-01-23 08:15:51 +0100200 " -uao, -uto: average times computed on valid ('OK') requests\n"
Tim Duesterhuseb6bf962021-10-28 15:53:37 +0200201 " -uba, -ubt: average bytes returned, total bytes returned\n",
Tim Duesterhusaefb6302021-10-28 15:55:49 +0200202 SOURCE_FIELD,SOURCE_FIELD
Willy Tarreau615674c2012-01-23 08:15:51 +0100203 );
204 exit(0);
205}
206
Willy Tarreau72c28532009-01-22 18:56:50 +0100207
208/* return pointer to first char not part of current field starting at <p>. */
Willy Tarreauf9042062011-09-10 12:26:35 +0200209
210#if defined(__i386__)
211/* this one is always faster on 32-bits */
212static inline const char *field_stop(const char *p)
213{
214 asm(
215 /* Look for spaces */
216 "4: \n\t"
217 "inc %0 \n\t"
218 "cmpb $0x20, -1(%0) \n\t"
219 "ja 4b \n\t"
220 "jz 3f \n\t"
221
222 /* we only get there for control chars 0..31. Leave if we find '\0' */
223 "cmpb $0x0, -1(%0) \n\t"
224 "jnz 4b \n\t"
225
226 /* return %0-1 = position of the last char we checked */
227 "3: \n\t"
228 "dec %0 \n\t"
229 : "=r" (p)
230 : "0" (p)
231 );
232 return p;
233}
234#else
Willy Tarreau72c28532009-01-22 18:56:50 +0100235const char *field_stop(const char *p)
236{
237 unsigned char c;
238
239 while (1) {
240 c = *(p++);
241 if (c > ' ')
242 continue;
Willy Tarreau14389e72011-07-10 22:11:17 +0200243 if (c == ' ' || c == 0)
Willy Tarreau72c28532009-01-22 18:56:50 +0100244 break;
245 }
246 return p - 1;
247}
Willy Tarreauf9042062011-09-10 12:26:35 +0200248#endif
Willy Tarreau72c28532009-01-22 18:56:50 +0100249
250/* return field <field> (starting from 1) in string <p>. Only consider
251 * contiguous spaces (or tabs) as one delimiter. May return pointer to
252 * last char if field is not found. Equivalent to awk '{print $field}'.
253 */
254const char *field_start(const char *p, int field)
255{
Willy Tarreauf9042062011-09-10 12:26:35 +0200256#ifndef PREFER_ASM
Willy Tarreau72c28532009-01-22 18:56:50 +0100257 unsigned char c;
258 while (1) {
259 /* skip spaces */
260 while (1) {
Willy Tarreauf9042062011-09-10 12:26:35 +0200261 c = *(p++);
Willy Tarreau72c28532009-01-22 18:56:50 +0100262 if (c > ' ')
263 break;
Willy Tarreau14389e72011-07-10 22:11:17 +0200264 if (c == ' ')
Willy Tarreauf9042062011-09-10 12:26:35 +0200265 continue;
Willy Tarreau72c28532009-01-22 18:56:50 +0100266 if (!c) /* end of line */
Willy Tarreauf9042062011-09-10 12:26:35 +0200267 return p-1;
Willy Tarreau72c28532009-01-22 18:56:50 +0100268 /* other char => new field */
269 break;
Willy Tarreau72c28532009-01-22 18:56:50 +0100270 }
271
272 /* start of field */
273 field--;
274 if (!field)
Willy Tarreauf9042062011-09-10 12:26:35 +0200275 return p-1;
Willy Tarreau72c28532009-01-22 18:56:50 +0100276
277 /* skip this field */
278 while (1) {
279 c = *(p++);
Willy Tarreau14389e72011-07-10 22:11:17 +0200280 if (c == ' ')
Willy Tarreau72c28532009-01-22 18:56:50 +0100281 break;
Willy Tarreauf9042062011-09-10 12:26:35 +0200282 if (c > ' ')
283 continue;
Willy Tarreau72c28532009-01-22 18:56:50 +0100284 if (c == '\0')
Willy Tarreauf9042062011-09-10 12:26:35 +0200285 return p - 1;
Willy Tarreau72c28532009-01-22 18:56:50 +0100286 }
287 }
Willy Tarreauf9042062011-09-10 12:26:35 +0200288#else
289 /* This version works optimally on i386 and x86_64 but the code above
290 * shows similar performance. However, depending on the version of GCC
291 * used, inlining rules change and it may have difficulties to make
292 * efficient use of this code at other locations and could result in
293 * worse performance (eg: gcc 4.4). You may want to experience.
294 */
295 asm(
296 /* skip spaces */
297 "1: \n\t"
298 "inc %0 \n\t"
299 "cmpb $0x20, -1(%0) \n\t"
300 "ja 2f \n\t"
301 "jz 1b \n\t"
302
303 /* we only get there for control chars 0..31. Leave if we find '\0' */
304 "cmpb $0x0, -1(%0) \n\t"
305 "jz 3f \n\t"
306
307 /* start of field at [%0-1]. Check if we need to skip more fields */
308 "2: \n\t"
309 "dec %1 \n\t"
310 "jz 3f \n\t"
311
312 /* Look for spaces */
313 "4: \n\t"
314 "inc %0 \n\t"
315 "cmpb $0x20, -1(%0) \n\t"
316 "jz 1b \n\t"
317 "ja 4b \n\t"
318
319 /* we only get there for control chars 0..31. Leave if we find '\0' */
320 "cmpb $0x0, -1(%0) \n\t"
321 "jnz 4b \n\t"
322
323 /* return %0-1 = position of the last char we checked */
324 "3: \n\t"
325 "dec %0 \n\t"
326 : "=r" (p)
327 : "r" (field), "0" (p)
328 );
329 return p;
330#endif
Willy Tarreau72c28532009-01-22 18:56:50 +0100331}
332
333/* keep only the <bits> higher bits of <i> */
334static inline unsigned int quantify_u32(unsigned int i, int bits)
335{
336 int high;
337
338 if (!bits)
339 return 0;
340
341 if (i)
342 high = fls_auto(i); // 1 to 32
343 else
344 high = 0;
345
346 if (high <= bits)
347 return i;
348
349 return i & ~((1 << (high - bits)) - 1);
350}
351
352/* keep only the <bits> higher bits of the absolute value of <i>, as well as
353 * its sign. */
354static inline int quantify(int i, int bits)
355{
356 if (i >= 0)
357 return quantify_u32(i, bits);
358 else
359 return -quantify_u32(-i, bits);
360}
361
362/* Insert timer value <v> into tree <r>. A pre-allocated node must be passed
363 * in <alloc>. It may be NULL, in which case the function will allocate it
364 * itself. It will be reset to NULL once consumed. The caller is responsible
365 * for freeing the node once not used anymore. The node where the value was
366 * inserted is returned.
367 */
368struct timer *insert_timer(struct eb_root *r, struct timer **alloc, int v)
369{
370 struct timer *t = *alloc;
371 struct eb32_node *n;
372
373 if (!t) {
374 t = calloc(sizeof(*t), 1);
375 if (unlikely(!t)) {
376 fprintf(stderr, "%s: not enough memory\n", __FUNCTION__);
377 exit(1);
378 }
379 }
380 t->node.key = quantify(v, QBITS); // keep only the higher QBITS bits
381
382 n = eb32i_insert(r, &t->node);
383 if (n == &t->node)
384 t = NULL; /* node inserted, will malloc next time */
385
386 *alloc = t;
387 return container_of(n, struct timer, node);
388}
389
390/* Insert value value <v> into tree <r>. A pre-allocated node must be passed
391 * in <alloc>. It may be NULL, in which case the function will allocate it
392 * itself. It will be reset to NULL once consumed. The caller is responsible
393 * for freeing the node once not used anymore. The node where the value was
394 * inserted is returned.
395 */
396struct timer *insert_value(struct eb_root *r, struct timer **alloc, int v)
397{
398 struct timer *t = *alloc;
399 struct eb32_node *n;
400
401 if (!t) {
402 t = calloc(sizeof(*t), 1);
403 if (unlikely(!t)) {
404 fprintf(stderr, "%s: not enough memory\n", __FUNCTION__);
405 exit(1);
406 }
407 }
408 t->node.key = v;
409
410 n = eb32i_insert(r, &t->node);
411 if (n == &t->node)
412 t = NULL; /* node inserted, will malloc next time */
413
414 *alloc = t;
415 return container_of(n, struct timer, node);
416}
417
418int str2ic(const char *s)
419{
420 int i = 0;
421 int j, k;
422
423 if (*s != '-') {
424 /* positive number */
425 while (1) {
426 j = (*s++) - '0';
427 k = i * 10;
428 if ((unsigned)j > 9)
429 break;
430 i = k + j;
431 }
432 } else {
433 /* negative number */
434 s++;
435 while (1) {
436 j = (*s++) - '0';
437 k = i * 10;
438 if ((unsigned)j > 9)
439 break;
440 i = k - j;
441 }
442 }
443
444 return i;
445}
446
447
448/* Equivalent to strtoul with a length. */
449static inline unsigned int __strl2ui(const char *s, int len)
450{
451 unsigned int i = 0;
452 while (len-- > 0) {
453 i = i * 10 - '0';
454 i += (unsigned char)*s++;
455 }
456 return i;
457}
458
459unsigned int strl2ui(const char *s, int len)
460{
461 return __strl2ui(s, len);
462}
463
464/* Convert "[04/Dec/2008:09:49:40.555]" to an integer equivalent to the time of
465 * the day in milliseconds. It returns -1 for all unparsable values. The parser
466 * looks ugly but gcc emits far better code that way.
467 */
468int convert_date(const char *field)
469{
470 unsigned int h, m, s, ms;
471 unsigned char c;
Ryan O'Hara8cb99932017-12-15 10:21:39 -0600472 const char *e;
Willy Tarreau72c28532009-01-22 18:56:50 +0100473
474 h = m = s = ms = 0;
475 e = field;
476
477 /* skip the date */
478 while (1) {
479 c = *(e++);
480 if (c == ':')
481 break;
482 if (!c)
483 goto out_err;
484 }
485
486 /* hour + ':' */
Willy Tarreau72c28532009-01-22 18:56:50 +0100487 while (1) {
488 c = *(e++) - '0';
489 if (c > 9)
490 break;
491 h = h * 10 + c;
492 }
493 if (c == (unsigned char)(0 - '0'))
494 goto out_err;
495
496 /* minute + ':' */
Willy Tarreau72c28532009-01-22 18:56:50 +0100497 while (1) {
498 c = *(e++) - '0';
499 if (c > 9)
500 break;
501 m = m * 10 + c;
502 }
503 if (c == (unsigned char)(0 - '0'))
504 goto out_err;
505
506 /* second + '.' or ']' */
Willy Tarreau72c28532009-01-22 18:56:50 +0100507 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 + ']' */
Willy Tarreau72c28532009-01-22 18:56:50 +0100519 while (1) {
520 c = *(e++) - '0';
521 if (c > 9)
522 break;
523 ms = ms * 10 + c;
524 }
525 if (c == (unsigned char)(0 - '0'))
526 goto out_err;
527 }
528 return (((h * 60) + m) * 60 + s) * 1000 + ms;
529 out_err:
530 return -1;
531}
532
Olivier Burgarde97b9042014-05-22 16:44:59 +0200533/* Convert "[04/Dec/2008:09:49:40.555]" to an unix timestamp.
534 * It returns -1 for all unparsable values. The parser
535 * looks ugly but gcc emits far better code that way.
536 */
537int convert_date_to_timestamp(const char *field)
538{
539 unsigned int d, mo, y, h, m, s;
540 unsigned char c;
Ryan O'Hara8cb99932017-12-15 10:21:39 -0600541 const char *e;
Olivier Burgarde97b9042014-05-22 16:44:59 +0200542 time_t rawtime;
Willy Tarreau9f66aa92014-05-23 16:36:56 +0200543 static struct tm * timeinfo;
544 static int last_res;
Olivier Burgarde97b9042014-05-22 16:44:59 +0200545
546 d = mo = y = h = m = s = 0;
547 e = field;
548
549 c = *(e++); // remove '['
550 /* day + '/' */
551 while (1) {
552 c = *(e++) - '0';
553 if (c > 9)
554 break;
555 d = d * 10 + c;
556 if (c == (unsigned char)(0 - '0'))
557 goto out_err;
558 }
559
560 /* month + '/' */
561 c = *(e++);
562 if (c =='F') {
563 mo = 2;
564 e = e+3;
565 } else if (c =='S') {
566 mo = 9;
567 e = e+3;
568 } else if (c =='O') {
569 mo = 10;
570 e = e+3;
571 } else if (c =='N') {
572 mo = 11;
573 e = e+3;
574 } else if (c == 'D') {
575 mo = 12;
576 e = e+3;
577 } else if (c == 'A') {
578 c = *(e++);
579 if (c == 'p') {
580 mo = 4;
581 e = e+2;
582 } else if (c == 'u') {
583 mo = 8;
584 e = e+2;
585 } else
586 goto out_err;
587 } else if (c == 'J') {
588 c = *(e++);
589 if (c == 'a') {
590 mo = 1;
591 e = e+2;
592 } else if (c == 'u') {
593 c = *(e++);
594 if (c == 'n') {
595 mo = 6;
596 e = e+1;
597 } else if (c == 'l') {
598 mo = 7;
599 e++;
600 }
601 } else
602 goto out_err;
603 } else if (c == 'M') {
604 e++;
605 c = *(e++);
606 if (c == 'r') {
607 mo = 3;
608 e = e+1;
609 } else if (c == 'y') {
610 mo = 5;
611 e = e+1;
612 } else
613 goto out_err;
614 } else
615 goto out_err;
616
617 /* year + ':' */
618 while (1) {
619 c = *(e++) - '0';
620 if (c > 9)
621 break;
622 y = y * 10 + c;
623 if (c == (unsigned char)(0 - '0'))
624 goto out_err;
625 }
626
627 /* hour + ':' */
Olivier Burgarde97b9042014-05-22 16:44:59 +0200628 while (1) {
629 c = *(e++) - '0';
630 if (c > 9)
631 break;
632 h = h * 10 + c;
633 }
634 if (c == (unsigned char)(0 - '0'))
635 goto out_err;
636
637 /* minute + ':' */
Olivier Burgarde97b9042014-05-22 16:44:59 +0200638 while (1) {
639 c = *(e++) - '0';
640 if (c > 9)
641 break;
642 m = m * 10 + c;
643 }
644 if (c == (unsigned char)(0 - '0'))
645 goto out_err;
646
647 /* second + '.' or ']' */
Olivier Burgarde97b9042014-05-22 16:44:59 +0200648 while (1) {
649 c = *(e++) - '0';
650 if (c > 9)
651 break;
652 s = s * 10 + c;
653 }
654
Willy Tarreau9f66aa92014-05-23 16:36:56 +0200655 if (likely(timeinfo)) {
Willy Tarreau03ca6052020-12-21 08:40:04 +0100656 if ((unsigned)timeinfo->tm_min == m &&
657 (unsigned)timeinfo->tm_hour == h &&
658 (unsigned)timeinfo->tm_mday == d &&
659 (unsigned)timeinfo->tm_mon == mo - 1 &&
660 (unsigned)timeinfo->tm_year == y - 1900)
Willy Tarreau9f66aa92014-05-23 16:36:56 +0200661 return last_res + s;
662 }
663 else {
664 time(&rawtime);
665 timeinfo = localtime(&rawtime);
666 }
Olivier Burgarde97b9042014-05-22 16:44:59 +0200667
Willy Tarreau9f66aa92014-05-23 16:36:56 +0200668 timeinfo->tm_sec = 0;
Olivier Burgarde97b9042014-05-22 16:44:59 +0200669 timeinfo->tm_min = m;
670 timeinfo->tm_hour = h;
671 timeinfo->tm_mday = d;
672 timeinfo->tm_mon = mo - 1;
673 timeinfo->tm_year = y - 1900;
Willy Tarreau9f66aa92014-05-23 16:36:56 +0200674 last_res = mktime(timeinfo);
Olivier Burgarde97b9042014-05-22 16:44:59 +0200675
Willy Tarreau9f66aa92014-05-23 16:36:56 +0200676 return last_res + s;
Olivier Burgarde97b9042014-05-22 16:44:59 +0200677 out_err:
678 return -1;
679}
680
Willy Tarreau72c28532009-01-22 18:56:50 +0100681void truncated_line(int linenum, const char *line)
682{
683 if (!(filter & FILT_QUIET))
684 fprintf(stderr, "Truncated line %d: %s\n", linenum, line);
685}
686
687int main(int argc, char **argv)
688{
Ryan O'Hara8cb99932017-12-15 10:21:39 -0600689 const char *b, *p, *time_field, *accept_field, *source_field;
Hervé COMMOWICK927cddd2011-08-10 17:42:41 +0200690 const char *filter_term_code_name = NULL;
Willy Tarreau72c28532009-01-22 18:56:50 +0100691 const char *output_file = NULL;
Ryan O'Hara8cb99932017-12-15 10:21:39 -0600692 int f, last;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200693 struct timer *t = NULL;
Willy Tarreau72c28532009-01-22 18:56:50 +0100694 struct eb32_node *n;
Willy Tarreauabe45b62010-10-28 20:33:46 +0200695 struct url_stat *ustat = NULL;
Willy Tarreau72c28532009-01-22 18:56:50 +0100696 int val, test;
Willy Tarreauc8746532014-05-28 23:05:07 +0200697 unsigned int uval;
Willy Tarreau03ca6052020-12-21 08:40:04 +0100698 unsigned int filter_acc_delay = 0, filter_acc_count = 0;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200699 int filter_time_resp = 0;
Willy Tarreaud3007ff2011-09-05 02:07:23 +0200700 int filt_http_status_low = 0, filt_http_status_high = 0;
Willy Tarreau03ca6052020-12-21 08:40:04 +0100701 unsigned int filt2_timestamp_low = 0, filt2_timestamp_high = 0;
Willy Tarreau72c28532009-01-22 18:56:50 +0100702 int skip_fields = 1;
703
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200704 void (*line_filter)(const char *accept_field, const char *time_field, struct timer **tptr) = NULL;
705
Willy Tarreau72c28532009-01-22 18:56:50 +0100706 argc--; argv++;
707 while (argc > 0) {
708 if (*argv[0] != '-')
709 break;
710
711 if (strcmp(argv[0], "-ad") == 0) {
712 if (argc < 2) die("missing option for -ad");
713 argc--; argv++;
714 filter |= FILT_ACC_DELAY;
715 filter_acc_delay = atol(*argv);
716 }
717 else if (strcmp(argv[0], "-ac") == 0) {
718 if (argc < 2) die("missing option for -ac");
719 argc--; argv++;
720 filter |= FILT_ACC_COUNT;
721 filter_acc_count = atol(*argv);
722 }
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200723 else if (strcmp(argv[0], "-rt") == 0) {
724 if (argc < 2) die("missing option for -rt");
725 argc--; argv++;
726 filter |= FILT_TIME_RESP;
727 filter_time_resp = atol(*argv);
728 }
729 else if (strcmp(argv[0], "-RT") == 0) {
730 if (argc < 2) die("missing option for -RT");
731 argc--; argv++;
732 filter |= FILT_TIME_RESP | FILT_INVERT_TIME_RESP;
733 filter_time_resp = atol(*argv);
734 }
Willy Tarreau72c28532009-01-22 18:56:50 +0100735 else if (strcmp(argv[0], "-s") == 0) {
736 if (argc < 2) die("missing option for -s");
737 argc--; argv++;
738 skip_fields = atol(*argv);
739 }
Willy Tarreau667c9052012-10-10 16:49:28 +0200740 else if (strcmp(argv[0], "-m") == 0) {
741 if (argc < 2) die("missing option for -m");
742 argc--; argv++;
743 lines_max = atol(*argv);
744 }
Willy Tarreau72c28532009-01-22 18:56:50 +0100745 else if (strcmp(argv[0], "-e") == 0)
746 filter |= FILT_ERRORS_ONLY;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200747 else if (strcmp(argv[0], "-E") == 0)
748 filter |= FILT_ERRORS_ONLY | FILT_INVERT_ERRORS;
Willy Tarreau70c428f2011-07-10 17:27:40 +0200749 else if (strcmp(argv[0], "-H") == 0)
750 filter |= FILT_HTTP_ONLY;
Willy Tarreau08911ff2011-10-13 13:28:36 +0200751 else if (strcmp(argv[0], "-Q") == 0)
752 filter |= FILT_QUEUE_ONLY;
753 else if (strcmp(argv[0], "-QS") == 0)
754 filter |= FILT_QUEUE_SRV_ONLY;
Willy Tarreau72c28532009-01-22 18:56:50 +0100755 else if (strcmp(argv[0], "-c") == 0)
756 filter |= FILT_COUNT_ONLY;
757 else if (strcmp(argv[0], "-q") == 0)
758 filter |= FILT_QUIET;
759 else if (strcmp(argv[0], "-v") == 0)
760 filter_invert = !filter_invert;
761 else if (strcmp(argv[0], "-gt") == 0)
762 filter |= FILT_GRAPH_TIMERS;
Willy Tarreau214c2032009-02-20 11:02:32 +0100763 else if (strcmp(argv[0], "-pct") == 0)
764 filter |= FILT_PERCENTILE;
Willy Tarreau0f423a72010-05-03 10:50:54 +0200765 else if (strcmp(argv[0], "-st") == 0)
766 filter |= FILT_COUNT_STATUS;
Willy Tarreaud2201062010-05-27 18:17:30 +0200767 else if (strcmp(argv[0], "-srv") == 0)
768 filter |= FILT_COUNT_SRV_STATUS;
Willy Tarreau8a09b662012-10-10 10:26:22 +0200769 else if (strcmp(argv[0], "-cc") == 0)
770 filter |= FILT_COUNT_COOK_CODES;
Willy Tarreaud8fc1102010-09-12 17:56:16 +0200771 else if (strcmp(argv[0], "-tc") == 0)
772 filter |= FILT_COUNT_TERM_CODES;
Hervé COMMOWICK927cddd2011-08-10 17:42:41 +0200773 else if (strcmp(argv[0], "-tcn") == 0) {
774 if (argc < 2) die("missing option for -tcn");
775 argc--; argv++;
776 filter |= FILT_TERM_CODE_NAME;
777 filter_term_code_name = *argv;
778 }
779 else if (strcmp(argv[0], "-TCN") == 0) {
780 if (argc < 2) die("missing option for -TCN");
781 argc--; argv++;
782 filter |= FILT_TERM_CODE_NAME | FILT_INVERT_TERM_CODE_NAME;
783 filter_term_code_name = *argv;
784 }
Willy Tarreaud3007ff2011-09-05 02:07:23 +0200785 else if (strcmp(argv[0], "-hs") == 0 || strcmp(argv[0], "-HS") == 0) {
786 char *sep, *str;
787
788 if (argc < 2) die("missing option for -hs/-HS ([min]:[max])");
789 filter |= FILT_HTTP_STATUS;
790 if (argv[0][1] == 'H')
791 filter |= FILT_INVERT_HTTP_STATUS;
792
793 argc--; argv++;
794 str = *argv;
795 sep = strchr(str, ':'); /* [min]:[max] */
796 if (!sep)
797 sep = str; /* make max point to min */
798 else
799 *sep++ = 0;
800 filt_http_status_low = *str ? atol(str) : 0;
801 filt_http_status_high = *sep ? atol(sep) : 65535;
802 }
Olivier Burgarde97b9042014-05-22 16:44:59 +0200803 else if (strcmp(argv[0], "-time") == 0) {
804 char *sep, *str;
805
806 if (argc < 2) die("missing option for -time ([min]:[max])");
807 filter2 |= FILT2_TIMESTAMP;
808
809 argc--; argv++;
810 str = *argv;
811 sep = strchr(str, ':'); /* [min]:[max] */
812 filt2_timestamp_low = *str ? atol(str) : 0;
813 if (!sep)
814 filt2_timestamp_high = 0xFFFFFFFF;
815 else
816 filt2_timestamp_high = atol(++sep);
817 }
Willy Tarreauabe45b62010-10-28 20:33:46 +0200818 else if (strcmp(argv[0], "-u") == 0)
819 filter |= FILT_COUNT_URL_ONLY;
820 else if (strcmp(argv[0], "-uc") == 0)
821 filter |= FILT_COUNT_URL_COUNT;
822 else if (strcmp(argv[0], "-ue") == 0)
823 filter |= FILT_COUNT_URL_ERR;
824 else if (strcmp(argv[0], "-ua") == 0)
825 filter |= FILT_COUNT_URL_TAVG;
826 else if (strcmp(argv[0], "-ut") == 0)
827 filter |= FILT_COUNT_URL_TTOT;
828 else if (strcmp(argv[0], "-uao") == 0)
829 filter |= FILT_COUNT_URL_TAVGO;
830 else if (strcmp(argv[0], "-uto") == 0)
831 filter |= FILT_COUNT_URL_TTOTO;
Baptiste61aaad02012-09-08 23:10:03 +0200832 else if (strcmp(argv[0], "-uba") == 0)
833 filter |= FILT_COUNT_URL_BAVG;
834 else if (strcmp(argv[0], "-ubt") == 0)
835 filter |= FILT_COUNT_URL_BTOT;
Tim Duesterhusa8826662021-10-28 16:36:03 +0200836 else if (strcmp(argv[0], "-query") == 0)
Tim Duesterhusb09bdee2021-10-18 12:12:02 +0200837 filter2 |= FILT2_PRESERVE_QUERY;
Willy Tarreau7cf479c2013-02-16 23:49:04 +0100838 else if (strcmp(argv[0], "-ic") == 0)
839 filter |= FILT_COUNT_IP_COUNT;
Willy Tarreau72c28532009-01-22 18:56:50 +0100840 else if (strcmp(argv[0], "-o") == 0) {
841 if (output_file)
842 die("Fatal: output file name already specified.\n");
843 if (argc < 2)
844 die("Fatal: missing output file name.\n");
845 output_file = argv[1];
846 }
Willy Tarreau615674c2012-01-23 08:15:51 +0100847 else if (strcmp(argv[0], "-h") == 0 || strcmp(argv[0], "--help") == 0)
848 help();
Willy Tarreau72c28532009-01-22 18:56:50 +0100849 argc--;
850 argv++;
851 }
852
853 if (!filter)
854 die("No action specified.\n");
855
856 if (filter & FILT_ACC_COUNT && !filter_acc_count)
857 filter_acc_count=1;
858
859 if (filter & FILT_ACC_DELAY && !filter_acc_delay)
860 filter_acc_delay = 1;
861
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200862
863 /* by default, all lines are printed */
864 line_filter = filter_output_line;
865 if (filter & (FILT_ACC_COUNT|FILT_ACC_DELAY))
866 line_filter = filter_accept_holes;
867 else if (filter & (FILT_GRAPH_TIMERS|FILT_PERCENTILE))
868 line_filter = filter_graphs;
869 else if (filter & FILT_COUNT_STATUS)
870 line_filter = filter_count_status;
Willy Tarreau8a09b662012-10-10 10:26:22 +0200871 else if (filter & FILT_COUNT_COOK_CODES)
872 line_filter = filter_count_cook_codes;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200873 else if (filter & FILT_COUNT_TERM_CODES)
874 line_filter = filter_count_term_codes;
875 else if (filter & FILT_COUNT_SRV_STATUS)
876 line_filter = filter_count_srv_status;
877 else if (filter & FILT_COUNT_URL_ANY)
878 line_filter = filter_count_url;
879 else if (filter & FILT_COUNT_ONLY)
880 line_filter = NULL;
Willy Tarreau72c28532009-01-22 18:56:50 +0100881
Willy Tarreauf8c95d22012-06-12 09:16:56 +0200882#if defined(POSIX_FADV_SEQUENTIAL)
883 /* around 20% performance improvement is observed on Linux with this
Joseph Herlant42172bd2018-11-09 18:02:35 -0800884 * on cold-cache. Surprisingly, WILLNEED is less performant. Don't
Willy Tarreauf8c95d22012-06-12 09:16:56 +0200885 * use NOREUSE as it flushes the cache and prevents easy data
886 * manipulation on logs!
887 */
888 posix_fadvise(0, 0, 0, POSIX_FADV_SEQUENTIAL);
889#endif
890
Willy Tarreaua1629a52012-11-13 20:48:15 +0100891 if (!line_filter && /* FILT_COUNT_ONLY ( see above), and no input filter (see below) */
Olivier Burgarde97b9042014-05-22 16:44:59 +0200892 !(filter & (FILT_HTTP_ONLY|FILT_TIME_RESP|FILT_ERRORS_ONLY|FILT_HTTP_STATUS|FILT_QUEUE_ONLY|FILT_QUEUE_SRV_ONLY|FILT_TERM_CODE_NAME)) &&
893 !(filter2 & (FILT2_TIMESTAMP))) {
Willy Tarreaua1629a52012-11-13 20:48:15 +0100894 /* read the whole file at once first, ignore it if inverted output */
Willy Tarreaue1a908c2012-01-03 09:23:03 +0100895 if (!filter_invert)
Willy Tarreaua1629a52012-11-13 20:48:15 +0100896 while ((lines_max < 0 || lines_out < lines_max) && fgets2(stdin) != NULL)
Willy Tarreaue1a908c2012-01-03 09:23:03 +0100897 lines_out++;
898
899 goto skip_filters;
900 }
901
Willy Tarreau214c2032009-02-20 11:02:32 +0100902 while ((line = fgets2(stdin)) != NULL) {
Willy Tarreau72c28532009-01-22 18:56:50 +0100903 linenum++;
Willy Tarreau26deaf52011-07-10 19:47:48 +0200904 time_field = NULL; accept_field = NULL;
Willy Tarreau7cf479c2013-02-16 23:49:04 +0100905 source_field = NULL;
Willy Tarreau72c28532009-01-22 18:56:50 +0100906
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200907 test = 1;
Willy Tarreau26deaf52011-07-10 19:47:48 +0200908
909 /* for any line we process, we first ensure that there is a field
910 * looking like the accept date field (beginning with a '[').
911 */
Willy Tarreau7cf479c2013-02-16 23:49:04 +0100912 if (filter & FILT_COUNT_IP_COUNT) {
913 /* we need the IP first */
914 source_field = field_start(line, SOURCE_FIELD + skip_fields);
915 accept_field = field_start(source_field, ACCEPT_FIELD - SOURCE_FIELD + 1);
916 }
917 else
918 accept_field = field_start(line, ACCEPT_FIELD + skip_fields);
919
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200920 if (unlikely(*accept_field != '[')) {
Willy Tarreau26deaf52011-07-10 19:47:48 +0200921 parse_err++;
922 continue;
923 }
924
925 /* the day of month field is begin 01 and 31 */
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200926 if (accept_field[1] < '0' || accept_field[1] > '3') {
Willy Tarreau26deaf52011-07-10 19:47:48 +0200927 parse_err++;
928 continue;
929 }
930
Olivier Burgarde97b9042014-05-22 16:44:59 +0200931 if (filter2 & FILT2_TIMESTAMP) {
932 uval = convert_date_to_timestamp(accept_field);
933 test &= (uval>=filt2_timestamp_low && uval<=filt2_timestamp_high) ;
934 }
935
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200936 if (filter & FILT_HTTP_ONLY) {
Willy Tarreau70c428f2011-07-10 17:27:40 +0200937 /* only report lines with at least 4 timers */
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200938 if (!time_field) {
Willy Tarreau26deaf52011-07-10 19:47:48 +0200939 time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200940 if (unlikely(!*time_field)) {
Willy Tarreau26deaf52011-07-10 19:47:48 +0200941 truncated_line(linenum, line);
942 continue;
943 }
Willy Tarreau70c428f2011-07-10 17:27:40 +0200944 }
945
Ryan O'Hara8cb99932017-12-15 10:21:39 -0600946 field_stop(time_field + 1);
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200947 /* we have field TIME_FIELD in [time_field]..[e-1] */
948 p = time_field;
Willy Tarreau70c428f2011-07-10 17:27:40 +0200949 f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200950 while (!SEP(*p)) {
Willy Tarreau70c428f2011-07-10 17:27:40 +0200951 if (++f == 4)
952 break;
953 SKIP_CHAR(p, '/');
954 }
955 test &= (f >= 4);
956 }
957
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200958 if (filter & FILT_TIME_RESP) {
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200959 int tps;
960
961 /* only report lines with response times larger than filter_time_resp */
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200962 if (!time_field) {
Willy Tarreau26deaf52011-07-10 19:47:48 +0200963 time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200964 if (unlikely(!*time_field)) {
Willy Tarreau26deaf52011-07-10 19:47:48 +0200965 truncated_line(linenum, line);
966 continue;
967 }
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200968 }
969
Ryan O'Hara8cb99932017-12-15 10:21:39 -0600970 field_stop(time_field + 1);
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200971 /* we have field TIME_FIELD in [time_field]..[e-1], let's check only the response time */
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200972
Willy Tarreau758a6ea2011-07-10 18:53:44 +0200973 p = time_field;
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200974 f = 0;
Willy Tarreaudf6f0d12011-07-10 18:15:08 +0200975 while (!SEP(*p)) {
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200976 tps = str2ic(p);
977 if (tps < 0) {
978 tps = -1;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200979 }
Willy Tarreau24bcb4f2010-10-28 20:39:50 +0200980 if (++f == 4)
981 break;
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200982 SKIP_CHAR(p, '/');
983 }
984
Willy Tarreaua2b39fb2011-07-10 21:39:35 +0200985 if (unlikely(f < 4)) {
Willy Tarreau5bdfd962009-10-14 15:16:29 +0200986 parse_err++;
987 continue;
988 }
989
990 test &= (tps >= filter_time_resp) ^ !!(filter & FILT_INVERT_TIME_RESP);
991 }
992
Willy Tarreaud3007ff2011-09-05 02:07:23 +0200993 if (filter & (FILT_ERRORS_ONLY | FILT_HTTP_STATUS)) {
994 /* Check both error codes (-1, 5xx) and status code ranges */
Willy Tarreau26deaf52011-07-10 19:47:48 +0200995 if (time_field)
996 b = field_start(time_field, STATUS_FIELD - TIME_FIELD + 1);
997 else
998 b = field_start(accept_field, STATUS_FIELD - ACCEPT_FIELD + 1);
999
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001000 if (unlikely(!*b)) {
Willy Tarreau72c28532009-01-22 18:56:50 +01001001 truncated_line(linenum, line);
1002 continue;
1003 }
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001004
Willy Tarreaud3007ff2011-09-05 02:07:23 +02001005 val = str2ic(b);
1006 if (filter & FILT_ERRORS_ONLY)
1007 test &= (val < 0 || (val >= 500 && val <= 599)) ^ !!(filter & FILT_INVERT_ERRORS);
1008
1009 if (filter & FILT_HTTP_STATUS)
1010 test &= (val >= filt_http_status_low && val <= filt_http_status_high) ^ !!(filter & FILT_INVERT_HTTP_STATUS);
Willy Tarreau72c28532009-01-22 18:56:50 +01001011 }
1012
Willy Tarreau08911ff2011-10-13 13:28:36 +02001013 if (filter & (FILT_QUEUE_ONLY|FILT_QUEUE_SRV_ONLY)) {
1014 /* Check if the server's queue is non-nul */
1015 if (time_field)
1016 b = field_start(time_field, QUEUE_LEN_FIELD - TIME_FIELD + 1);
1017 else
1018 b = field_start(accept_field, QUEUE_LEN_FIELD - ACCEPT_FIELD + 1);
1019
1020 if (unlikely(!*b)) {
1021 truncated_line(linenum, line);
1022 continue;
1023 }
1024
1025 if (*b == '0') {
1026 if (filter & FILT_QUEUE_SRV_ONLY) {
1027 test = 0;
1028 }
1029 else {
1030 do {
1031 b++;
1032 if (*b == '/') {
1033 b++;
1034 break;
1035 }
1036 } while (*b);
1037 test &= ((unsigned char)(*b - '1') < 9);
1038 }
1039 }
1040 }
1041
Hervé COMMOWICK927cddd2011-08-10 17:42:41 +02001042 if (filter & FILT_TERM_CODE_NAME) {
1043 /* only report corresponding termination code name */
1044 if (time_field)
1045 b = field_start(time_field, TERM_CODES_FIELD - TIME_FIELD + 1);
1046 else
1047 b = field_start(accept_field, TERM_CODES_FIELD - ACCEPT_FIELD + 1);
1048
1049 if (unlikely(!*b)) {
1050 truncated_line(linenum, line);
1051 continue;
1052 }
1053
1054 test &= (b[0] == filter_term_code_name[0] && b[1] == filter_term_code_name[1]) ^ !!(filter & FILT_INVERT_TERM_CODE_NAME);
1055 }
1056
1057
Willy Tarreau0f423a72010-05-03 10:50:54 +02001058 test ^= filter_invert;
1059 if (!test)
1060 continue;
1061
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001062 /************** here we process inputs *******************/
Willy Tarreau72c28532009-01-22 18:56:50 +01001063
Willy Tarreau7cf479c2013-02-16 23:49:04 +01001064 if (line_filter) {
1065 if (filter & FILT_COUNT_IP_COUNT)
1066 filter_count_ip(source_field, accept_field, time_field, &t);
1067 else
1068 line_filter(accept_field, time_field, &t);
1069 }
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001070 else
Willy Tarreaua1629a52012-11-13 20:48:15 +01001071 lines_out++; /* FILT_COUNT_ONLY was used, so we're just counting lines */
1072 if (lines_max >= 0 && lines_out >= lines_max)
Willy Tarreau667c9052012-10-10 16:49:28 +02001073 break;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001074 }
Willy Tarreauabe45b62010-10-28 20:33:46 +02001075
Willy Tarreaue1a908c2012-01-03 09:23:03 +01001076 skip_filters:
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001077 /*****************************************************
1078 * Here we've finished reading all input. Depending on the
1079 * filters, we may still have some analysis to run on the
1080 * collected data and to output data in a new format.
1081 *************************************************** */
Willy Tarreau72c28532009-01-22 18:56:50 +01001082
1083 if (t)
1084 free(t);
1085
1086 if (filter & FILT_COUNT_ONLY) {
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001087 printf("%d\n", lines_out);
Willy Tarreau72c28532009-01-22 18:56:50 +01001088 exit(0);
1089 }
1090
Willy Tarreau72c28532009-01-22 18:56:50 +01001091 if (filter & (FILT_ACC_COUNT|FILT_ACC_DELAY)) {
1092 /* sort and count all timers. Output will look like this :
1093 * <accept_date> <delta_ms from previous one> <nb entries>
1094 */
1095 n = eb32_first(&timers[0]);
1096
1097 if (n)
1098 last = n->key;
1099 while (n) {
1100 unsigned int d, h, m, s, ms;
1101
1102 t = container_of(n, struct timer, node);
1103 h = n->key;
1104 d = h - last;
1105 last = h;
1106
1107 if (d >= filter_acc_delay && t->count >= filter_acc_count) {
1108 ms = h % 1000; h = h / 1000;
1109 s = h % 60; h = h / 60;
1110 m = h % 60; h = h / 60;
Willy Tarreau72c28532009-01-22 18:56:50 +01001111 printf("%02d:%02d:%02d.%03d %d %d %d\n", h, m, s, ms, last, d, t->count);
Willy Tarreau667c9052012-10-10 16:49:28 +02001112 lines_out++;
Willy Tarreaua1629a52012-11-13 20:48:15 +01001113 if (lines_max >= 0 && lines_out >= lines_max)
Willy Tarreau667c9052012-10-10 16:49:28 +02001114 break;
Willy Tarreau72c28532009-01-22 18:56:50 +01001115 }
1116 n = eb32_next(n);
1117 }
1118 }
1119 else if (filter & FILT_GRAPH_TIMERS) {
1120 /* sort all timers */
1121 for (f = 0; f < 5; f++) {
1122 struct eb32_node *n;
1123 int val;
1124
1125 val = 0;
1126 n = eb32_first(&timers[f]);
1127 while (n) {
1128 int i;
1129 double d;
1130
1131 t = container_of(n, struct timer, node);
1132 last = n->key;
1133 val = t->count;
1134
1135 i = (last < 0) ? -last : last;
1136 i = fls_auto(i) - QBITS;
1137
1138 if (i > 0)
1139 d = val / (double)(1 << i);
1140 else
1141 d = val;
1142
Willy Tarreaua1629a52012-11-13 20:48:15 +01001143 if (d > 0.0)
Willy Tarreau72c28532009-01-22 18:56:50 +01001144 printf("%d %d %f\n", f, last, d+1.0);
Willy Tarreau72c28532009-01-22 18:56:50 +01001145
1146 n = eb32_next(n);
1147 }
Willy Tarreau214c2032009-02-20 11:02:32 +01001148 }
1149 }
1150 else if (filter & FILT_PERCENTILE) {
1151 /* report timers by percentile :
1152 * <percent> <total> <max_req_time> <max_conn_time> <max_resp_time> <max_data_time>
1153 * We don't count errs.
1154 */
1155 struct eb32_node *n[5];
1156 unsigned long cum[5];
1157 double step;
1158
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001159 if (!lines_out)
Willy Tarreau910ba4b2009-11-17 10:16:19 +01001160 goto empty;
1161
Willy Tarreau214c2032009-02-20 11:02:32 +01001162 for (f = 1; f < 5; f++) {
1163 n[f] = eb32_first(&timers[f]);
1164 cum[f] = container_of(n[f], struct timer, node)->count;
1165 }
1166
1167 for (step = 1; step <= 1000;) {
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001168 unsigned int thres = lines_out * (step / 1000.0);
Willy Tarreau214c2032009-02-20 11:02:32 +01001169
1170 printf("%3.1f %d ", step/10.0, thres);
1171 for (f = 1; f < 5; f++) {
1172 struct eb32_node *next;
1173 while (cum[f] < thres) {
1174 /* need to find other keys */
1175 next = eb32_next(n[f]);
1176 if (!next)
1177 break;
1178 n[f] = next;
1179 cum[f] += container_of(next, struct timer, node)->count;
1180 }
1181
1182 /* value still within $step % of total */
1183 printf("%d ", n[f]->key);
1184 }
1185 putchar('\n');
1186 if (step >= 100 && step < 900)
1187 step += 50; // jump 5% by 5% between those steps.
1188 else if (step >= 20 && step < 980)
1189 step += 10;
1190 else
1191 step += 1;
Willy Tarreau72c28532009-01-22 18:56:50 +01001192 }
1193 }
Willy Tarreau0f423a72010-05-03 10:50:54 +02001194 else if (filter & FILT_COUNT_STATUS) {
1195 /* output all statuses in the form of <status> <occurrences> */
1196 n = eb32_first(&timers[0]);
1197 while (n) {
1198 t = container_of(n, struct timer, node);
1199 printf("%d %d\n", n->key, t->count);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001200 lines_out++;
Willy Tarreaua1629a52012-11-13 20:48:15 +01001201 if (lines_max >= 0 && lines_out >= lines_max)
Willy Tarreau667c9052012-10-10 16:49:28 +02001202 break;
Willy Tarreau0f423a72010-05-03 10:50:54 +02001203 n = eb32_next(n);
1204 }
1205 }
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001206 else if (filter & FILT_COUNT_SRV_STATUS) {
Willy Tarreaud2201062010-05-27 18:17:30 +02001207 struct ebmb_node *srv_node;
1208 struct srv_st *srv;
1209
1210 printf("#srv_name 1xx 2xx 3xx 4xx 5xx other tot_req req_ok pct_ok avg_ct avg_rt\n");
1211
1212 srv_node = ebmb_first(&timers[0]);
1213 while (srv_node) {
1214 int tot_rq;
1215
1216 srv = container_of(srv_node, struct srv_st, node);
1217
1218 tot_rq = 0;
1219 for (f = 0; f <= 5; f++)
1220 tot_rq += srv->st_cnt[f];
1221
1222 printf("%s %d %d %d %d %d %d %d %d %.1f %d %d\n",
1223 srv_node->key, srv->st_cnt[1], srv->st_cnt[2],
1224 srv->st_cnt[3], srv->st_cnt[4], srv->st_cnt[5], srv->st_cnt[0],
1225 tot_rq,
1226 srv->nb_ok, (double)srv->nb_ok * 100.0 / (tot_rq?tot_rq:1),
1227 (int)(srv->cum_ct / (srv->nb_ct?srv->nb_ct:1)), (int)(srv->cum_rt / (srv->nb_rt?srv->nb_rt:1)));
1228 srv_node = ebmb_next(srv_node);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001229 lines_out++;
Willy Tarreaua1629a52012-11-13 20:48:15 +01001230 if (lines_max >= 0 && lines_out >= lines_max)
Willy Tarreau667c9052012-10-10 16:49:28 +02001231 break;
Willy Tarreaud2201062010-05-27 18:17:30 +02001232 }
1233 }
Willy Tarreau8a09b662012-10-10 10:26:22 +02001234 else if (filter & (FILT_COUNT_TERM_CODES|FILT_COUNT_COOK_CODES)) {
Willy Tarreaud8fc1102010-09-12 17:56:16 +02001235 /* output all statuses in the form of <code> <occurrences> */
1236 n = eb32_first(&timers[0]);
1237 while (n) {
1238 t = container_of(n, struct timer, node);
1239 printf("%c%c %d\n", (n->key >> 8), (n->key) & 255, t->count);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001240 lines_out++;
Willy Tarreaua1629a52012-11-13 20:48:15 +01001241 if (lines_max >= 0 && lines_out >= lines_max)
Willy Tarreau667c9052012-10-10 16:49:28 +02001242 break;
Willy Tarreaud8fc1102010-09-12 17:56:16 +02001243 n = eb32_next(n);
1244 }
1245 }
Willy Tarreau7cf479c2013-02-16 23:49:04 +01001246 else if (filter & (FILT_COUNT_URL_ANY|FILT_COUNT_IP_COUNT)) {
Willy Tarreauabe45b62010-10-28 20:33:46 +02001247 struct eb_node *node, *next;
1248
1249 if (!(filter & FILT_COUNT_URL_ONLY)) {
1250 /* we have to sort on another criterion. We'll use timers[1] for the
1251 * destination tree.
1252 */
1253
1254 timers[1] = EB_ROOT; /* reconfigure to accept duplicates */
1255 for (node = eb_first(&timers[0]); node; node = next) {
1256 next = eb_next(node);
1257 eb_delete(node);
1258
1259 ustat = container_of(node, struct url_stat, node.url.node);
1260
Willy Tarreau7cf479c2013-02-16 23:49:04 +01001261 if (filter & (FILT_COUNT_URL_COUNT|FILT_COUNT_IP_COUNT))
Willy Tarreauabe45b62010-10-28 20:33:46 +02001262 ustat->node.val.key = ustat->nb_req;
1263 else if (filter & FILT_COUNT_URL_ERR)
1264 ustat->node.val.key = ustat->nb_err;
1265 else if (filter & FILT_COUNT_URL_TTOT)
1266 ustat->node.val.key = ustat->total_time;
1267 else if (filter & FILT_COUNT_URL_TAVG)
1268 ustat->node.val.key = ustat->nb_req ? ustat->total_time / ustat->nb_req : 0;
1269 else if (filter & FILT_COUNT_URL_TTOTO)
1270 ustat->node.val.key = ustat->total_time_ok;
1271 else if (filter & FILT_COUNT_URL_TAVGO)
1272 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 +02001273 else if (filter & FILT_COUNT_URL_BAVG)
1274 ustat->node.val.key = ustat->nb_req ? ustat->total_bytes_sent / ustat->nb_req : 0;
1275 else if (filter & FILT_COUNT_URL_BTOT)
1276 ustat->node.val.key = ustat->total_bytes_sent;
Willy Tarreauabe45b62010-10-28 20:33:46 +02001277 else
1278 ustat->node.val.key = 0;
1279
1280 eb64_insert(&timers[1], &ustat->node.val);
1281 }
1282 /* switch trees */
1283 timers[0] = timers[1];
1284 }
1285
Willy Tarreau7cf479c2013-02-16 23:49:04 +01001286 if (FILT_COUNT_IP_COUNT)
1287 printf("#req err ttot tavg oktot okavg bavg btot src\n");
1288 else
1289 printf("#req err ttot tavg oktot okavg bavg btot url\n");
Willy Tarreauabe45b62010-10-28 20:33:46 +02001290
1291 /* scan the tree in its reverse sorting order */
1292 node = eb_last(&timers[0]);
1293 while (node) {
1294 ustat = container_of(node, struct url_stat, node.url.node);
Willy Tarreau2df860c2020-12-21 08:29:09 +01001295 printf("%d %d %llu %llu %llu %llu %llu %llu %s\n",
Willy Tarreauabe45b62010-10-28 20:33:46 +02001296 ustat->nb_req,
1297 ustat->nb_err,
1298 ustat->total_time,
1299 ustat->nb_req ? ustat->total_time / ustat->nb_req : 0,
1300 ustat->total_time_ok,
1301 (ustat->nb_req - ustat->nb_err) ? ustat->total_time_ok / (ustat->nb_req - ustat->nb_err) : 0,
Baptiste61aaad02012-09-08 23:10:03 +02001302 ustat->nb_req ? ustat->total_bytes_sent / ustat->nb_req : 0,
1303 ustat->total_bytes_sent,
Willy Tarreauabe45b62010-10-28 20:33:46 +02001304 ustat->url);
1305
1306 node = eb_prev(node);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001307 lines_out++;
Willy Tarreaua1629a52012-11-13 20:48:15 +01001308 if (lines_max >= 0 && lines_out >= lines_max)
Willy Tarreau667c9052012-10-10 16:49:28 +02001309 break;
Willy Tarreauabe45b62010-10-28 20:33:46 +02001310 }
1311 }
Willy Tarreaud2201062010-05-27 18:17:30 +02001312
Willy Tarreau910ba4b2009-11-17 10:16:19 +01001313 empty:
Willy Tarreau72c28532009-01-22 18:56:50 +01001314 if (!(filter & FILT_QUIET))
1315 fprintf(stderr, "%d lines in, %d lines out, %d parsing errors\n",
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001316 linenum, lines_out, parse_err);
Willy Tarreau72c28532009-01-22 18:56:50 +01001317 exit(0);
1318}
1319
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001320void filter_output_line(const char *accept_field, const char *time_field, struct timer **tptr)
1321{
1322 puts(line);
1323 lines_out++;
1324}
1325
1326void filter_accept_holes(const char *accept_field, const char *time_field, struct timer **tptr)
1327{
1328 struct timer *t2;
1329 int val;
1330
1331 val = convert_date(accept_field);
1332 if (unlikely(val < 0)) {
1333 truncated_line(linenum, line);
1334 return;
1335 }
1336
1337 t2 = insert_value(&timers[0], tptr, val);
1338 t2->count++;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001339 return;
1340}
1341
1342void filter_count_status(const char *accept_field, const char *time_field, struct timer **tptr)
1343{
1344 struct timer *t2;
1345 const char *b;
1346 int val;
1347
1348 if (time_field)
1349 b = field_start(time_field, STATUS_FIELD - TIME_FIELD + 1);
1350 else
1351 b = field_start(accept_field, STATUS_FIELD - ACCEPT_FIELD + 1);
1352
1353 if (unlikely(!*b)) {
1354 truncated_line(linenum, line);
1355 return;
1356 }
1357
1358 val = str2ic(b);
1359
1360 t2 = insert_value(&timers[0], tptr, val);
1361 t2->count++;
1362}
1363
Willy Tarreau8a09b662012-10-10 10:26:22 +02001364void filter_count_cook_codes(const char *accept_field, const char *time_field, struct timer **tptr)
1365{
1366 struct timer *t2;
1367 const char *b;
1368 int val;
1369
1370 if (time_field)
1371 b = field_start(time_field, TERM_CODES_FIELD - TIME_FIELD + 1);
1372 else
1373 b = field_start(accept_field, TERM_CODES_FIELD - ACCEPT_FIELD + 1);
1374
1375 if (unlikely(!*b)) {
1376 truncated_line(linenum, line);
1377 return;
1378 }
1379
1380 val = 256 * b[2] + b[3];
1381
1382 t2 = insert_value(&timers[0], tptr, val);
1383 t2->count++;
1384}
1385
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001386void filter_count_term_codes(const char *accept_field, const char *time_field, struct timer **tptr)
1387{
1388 struct timer *t2;
1389 const char *b;
1390 int val;
1391
1392 if (time_field)
1393 b = field_start(time_field, TERM_CODES_FIELD - TIME_FIELD + 1);
1394 else
1395 b = field_start(accept_field, TERM_CODES_FIELD - ACCEPT_FIELD + 1);
1396
1397 if (unlikely(!*b)) {
1398 truncated_line(linenum, line);
1399 return;
1400 }
1401
1402 val = 256 * b[0] + b[1];
1403
1404 t2 = insert_value(&timers[0], tptr, val);
1405 t2->count++;
1406}
1407
1408void filter_count_srv_status(const char *accept_field, const char *time_field, struct timer **tptr)
1409{
1410 const char *b, *e, *p;
1411 int f, err, array[5];
1412 struct ebmb_node *srv_node;
1413 struct srv_st *srv;
1414 int val;
1415
1416 /* the server field is before the status field, so let's
1417 * parse them in the proper order.
1418 */
1419 b = field_start(accept_field, SERVER_FIELD - ACCEPT_FIELD + 1);
1420 if (unlikely(!*b)) {
1421 truncated_line(linenum, line);
1422 return;
1423 }
1424
1425 e = field_stop(b + 1); /* we have the server name in [b]..[e-1] */
1426
1427 /* the chance that a server name already exists is extremely high,
1428 * so let's perform a normal lookup first.
1429 */
1430 srv_node = ebst_lookup_len(&timers[0], b, e - b);
1431 srv = container_of(srv_node, struct srv_st, node);
1432
1433 if (!srv_node) {
1434 /* server not yet in the tree, let's create it */
1435 srv = (void *)calloc(1, sizeof(struct srv_st) + e - b + 1);
1436 srv_node = &srv->node;
1437 memcpy(&srv_node->key, b, e - b);
1438 srv_node->key[e - b] = '\0';
1439 ebst_insert(&timers[0], srv_node);
1440 }
1441
1442 /* let's collect the connect and response times */
1443 if (!time_field) {
1444 time_field = field_start(e, TIME_FIELD - SERVER_FIELD);
1445 if (unlikely(!*time_field)) {
1446 truncated_line(linenum, line);
1447 return;
1448 }
1449 }
1450
1451 e = field_stop(time_field + 1);
1452 /* we have field TIME_FIELD in [time_field]..[e-1] */
1453
1454 p = time_field;
1455 err = 0;
1456 f = 0;
1457 while (!SEP(*p)) {
1458 array[f] = str2ic(p);
1459 if (array[f] < 0) {
1460 array[f] = -1;
1461 err = 1;
1462 }
1463 if (++f == 5)
1464 break;
1465 SKIP_CHAR(p, '/');
1466 }
1467
1468 if (unlikely(f < 5)){
1469 parse_err++;
1470 return;
1471 }
1472
1473 /* OK we have our timers in array[2,3] */
1474 if (!err)
1475 srv->nb_ok++;
1476
1477 if (array[2] >= 0) {
1478 srv->cum_ct += array[2];
1479 srv->nb_ct++;
1480 }
1481
1482 if (array[3] >= 0) {
1483 srv->cum_rt += array[3];
1484 srv->nb_rt++;
1485 }
1486
1487 /* we're interested in the 5 HTTP status classes (1xx ... 5xx), and
1488 * the invalid ones which will be reported as 0.
1489 */
1490 b = field_start(e, STATUS_FIELD - TIME_FIELD);
1491 if (unlikely(!*b)) {
1492 truncated_line(linenum, line);
1493 return;
1494 }
1495
1496 val = 0;
1497 if (*b >= '1' && *b <= '5')
1498 val = *b - '0';
1499
1500 srv->st_cnt[val]++;
1501}
1502
1503void filter_count_url(const char *accept_field, const char *time_field, struct timer **tptr)
1504{
1505 struct url_stat *ustat = NULL;
1506 struct ebpt_node *ebpt_old;
1507 const char *b, *e;
1508 int f, err, array[5];
Baptiste61aaad02012-09-08 23:10:03 +02001509 int val;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001510
1511 /* let's collect the response time */
1512 if (!time_field) {
1513 time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1); // avg 115 ns per line
1514 if (unlikely(!*time_field)) {
1515 truncated_line(linenum, line);
1516 return;
1517 }
1518 }
1519
1520 /* we have the field TIME_FIELD starting at <time_field>. We'll
1521 * parse the 5 timers to detect errors, it takes avg 55 ns per line.
1522 */
1523 e = time_field; err = 0; f = 0;
1524 while (!SEP(*e)) {
1525 array[f] = str2ic(e);
1526 if (array[f] < 0) {
1527 array[f] = -1;
1528 err = 1;
1529 }
1530 if (++f == 5)
1531 break;
1532 SKIP_CHAR(e, '/');
1533 }
1534 if (f < 5) {
1535 parse_err++;
1536 return;
1537 }
1538
1539 /* OK we have our timers in array[3], and err is >0 if at
1540 * least one -1 was seen. <e> points to the first char of
1541 * the last timer. Let's prepare a new node with that.
1542 */
1543 if (unlikely(!ustat))
1544 ustat = calloc(1, sizeof(*ustat));
1545
1546 ustat->nb_err = err;
1547 ustat->nb_req = 1;
1548
1549 /* use array[4] = total time in case of error */
1550 ustat->total_time = (array[3] >= 0) ? array[3] : array[4];
1551 ustat->total_time_ok = (array[3] >= 0) ? array[3] : 0;
1552
Baptiste61aaad02012-09-08 23:10:03 +02001553 e = field_start(e, BYTES_SENT_FIELD - TIME_FIELD + 1);
1554 val = str2ic(e);
1555 ustat->total_bytes_sent = val;
1556
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001557 /* the line may be truncated because of a bad request or anything like this,
1558 * without a method. Also, if it does not begin with an quote, let's skip to
1559 * the next field because it's a capture. Let's fall back to the "method" itself
1560 * if there's nothing else.
1561 */
Baptiste61aaad02012-09-08 23:10:03 +02001562 e = field_start(e, METH_FIELD - BYTES_SENT_FIELD + 1);
Willy Tarreau61a40c72011-09-06 08:11:27 +02001563 while (*e != '"' && *e) {
1564 /* Note: some syslog servers escape quotes ! */
1565 if (*e == '\\' && e[1] == '"')
1566 break;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001567 e = field_start(e, 2);
Willy Tarreau61a40c72011-09-06 08:11:27 +02001568 }
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001569
1570 if (unlikely(!*e)) {
1571 truncated_line(linenum, line);
Ilya Shipitsin4473a2e2017-09-22 22:33:16 +05001572 free(ustat);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001573 return;
1574 }
1575
1576 b = field_start(e, URL_FIELD - METH_FIELD + 1); // avg 40 ns per line
1577 if (!*b)
1578 b = e;
1579
1580 /* stop at end of field or first ';' or '?', takes avg 64 ns per line */
1581 e = b;
1582 do {
Tim Duesterhusb09bdee2021-10-18 12:12:02 +02001583 if (*e == ' '||
1584 (!(filter2 & FILT2_PRESERVE_QUERY) && (*e == '?' || *e == ';'))) {
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001585 *(char *)e = 0;
1586 break;
1587 }
1588 e++;
1589 } while (*e);
1590
1591 /* now instead of copying the URL for a simple lookup, we'll link
1592 * to it from the node we're trying to insert. If it returns a
1593 * different value, it was already there. Otherwise we just have
1594 * to dynamically realloc an entry using strdup().
1595 */
1596 ustat->node.url.key = (char *)b;
1597 ebpt_old = ebis_insert(&timers[0], &ustat->node.url);
1598
1599 if (ebpt_old != &ustat->node.url) {
1600 struct url_stat *ustat_old;
1601 /* node was already there, let's update previous one */
1602 ustat_old = container_of(ebpt_old, struct url_stat, node.url);
1603 ustat_old->nb_req ++;
1604 ustat_old->nb_err += ustat->nb_err;
1605 ustat_old->total_time += ustat->total_time;
1606 ustat_old->total_time_ok += ustat->total_time_ok;
Baptiste61aaad02012-09-08 23:10:03 +02001607 ustat_old->total_bytes_sent += ustat->total_bytes_sent;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001608 } else {
1609 ustat->url = ustat->node.url.key = strdup(ustat->node.url.key);
1610 ustat = NULL; /* node was used */
1611 }
1612}
1613
Willy Tarreau7cf479c2013-02-16 23:49:04 +01001614void filter_count_ip(const char *source_field, const char *accept_field, const char *time_field, struct timer **tptr)
1615{
1616 struct url_stat *ustat = NULL;
1617 struct ebpt_node *ebpt_old;
1618 const char *b, *e;
1619 int f, err, array[5];
1620 int val;
1621
1622 /* let's collect the response time */
1623 if (!time_field) {
1624 time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1); // avg 115 ns per line
1625 if (unlikely(!*time_field)) {
1626 truncated_line(linenum, line);
1627 return;
1628 }
1629 }
1630
1631 /* we have the field TIME_FIELD starting at <time_field>. We'll
1632 * parse the 5 timers to detect errors, it takes avg 55 ns per line.
1633 */
1634 e = time_field; err = 0; f = 0;
1635 while (!SEP(*e)) {
1636 if (f == 0 || f == 4) {
1637 array[f] = str2ic(e);
1638 if (array[f] < 0) {
1639 array[f] = -1;
1640 err = 1;
1641 }
1642 }
1643 if (++f == 5)
1644 break;
1645 SKIP_CHAR(e, '/');
1646 }
1647 if (f < 5) {
1648 parse_err++;
1649 return;
1650 }
1651
1652 /* OK we have our timers in array[0], and err is >0 if at
1653 * least one -1 was seen. <e> points to the first char of
1654 * the last timer. Let's prepare a new node with that.
1655 */
1656 if (unlikely(!ustat))
1657 ustat = calloc(1, sizeof(*ustat));
1658
1659 ustat->nb_err = err;
1660 ustat->nb_req = 1;
1661
1662 /* use array[4] = total time in case of error */
1663 ustat->total_time = (array[0] >= 0) ? array[0] : array[4];
1664 ustat->total_time_ok = (array[0] >= 0) ? array[0] : 0;
1665
1666 e = field_start(e, BYTES_SENT_FIELD - TIME_FIELD + 1);
1667 val = str2ic(e);
1668 ustat->total_bytes_sent = val;
1669
1670 /* the source might be IPv4 or IPv6, so we always strip the port by
1671 * removing the last colon.
1672 */
1673 b = source_field;
1674 e = field_stop(b + 1);
1675 while (e > b && e[-1] != ':')
1676 e--;
1677 *(char *)(e - 1) = '\0';
1678
1679 /* now instead of copying the src for a simple lookup, we'll link
1680 * to it from the node we're trying to insert. If it returns a
1681 * different value, it was already there. Otherwise we just have
1682 * to dynamically realloc an entry using strdup(). We're using the
1683 * <url> field of the node to store the source address.
1684 */
1685 ustat->node.url.key = (char *)b;
1686 ebpt_old = ebis_insert(&timers[0], &ustat->node.url);
1687
1688 if (ebpt_old != &ustat->node.url) {
1689 struct url_stat *ustat_old;
1690 /* node was already there, let's update previous one */
1691 ustat_old = container_of(ebpt_old, struct url_stat, node.url);
1692 ustat_old->nb_req ++;
1693 ustat_old->nb_err += ustat->nb_err;
1694 ustat_old->total_time += ustat->total_time;
1695 ustat_old->total_time_ok += ustat->total_time_ok;
1696 ustat_old->total_bytes_sent += ustat->total_bytes_sent;
1697 } else {
1698 ustat->url = ustat->node.url.key = strdup(ustat->node.url.key);
1699 ustat = NULL; /* node was used */
1700 }
1701}
1702
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001703void filter_graphs(const char *accept_field, const char *time_field, struct timer **tptr)
1704{
1705 struct timer *t2;
Ryan O'Hara8cb99932017-12-15 10:21:39 -06001706 const char *p;
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001707 int f, err, array[5];
1708
1709 if (!time_field) {
1710 time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1);
1711 if (unlikely(!*time_field)) {
1712 truncated_line(linenum, line);
1713 return;
1714 }
1715 }
1716
Ryan O'Hara8cb99932017-12-15 10:21:39 -06001717 field_stop(time_field + 1);
Willy Tarreaua2b39fb2011-07-10 21:39:35 +02001718 /* we have field TIME_FIELD in [time_field]..[e-1] */
1719
1720 p = time_field;
1721 err = 0;
1722 f = 0;
1723 while (!SEP(*p)) {
1724 array[f] = str2ic(p);
1725 if (array[f] < 0) {
1726 array[f] = -1;
1727 err = 1;
1728 }
1729 if (++f == 5)
1730 break;
1731 SKIP_CHAR(p, '/');
1732 }
1733
1734 if (unlikely(f < 5)) {
1735 parse_err++;
1736 return;
1737 }
1738
1739 /* if we find at least one negative time, we count one error
1740 * with a time equal to the total session time. This will
1741 * emphasize quantum timing effects associated to known
1742 * timeouts. Note that on some buggy machines, it is possible
1743 * that the total time is negative, hence the reason to reset
1744 * it.
1745 */
1746
1747 if (filter & FILT_GRAPH_TIMERS) {
1748 if (err) {
1749 if (array[4] < 0)
1750 array[4] = -1;
1751 t2 = insert_timer(&timers[0], tptr, array[4]); // total time
1752 t2->count++;
1753 } else {
1754 int v;
1755
1756 t2 = insert_timer(&timers[1], tptr, array[0]); t2->count++; // req
1757 t2 = insert_timer(&timers[2], tptr, array[2]); t2->count++; // conn
1758 t2 = insert_timer(&timers[3], tptr, array[3]); t2->count++; // resp
1759
1760 v = array[4] - array[0] - array[1] - array[2] - array[3]; // data time
1761 if (v < 0 && !(filter & FILT_QUIET))
1762 fprintf(stderr, "ERR: %s (%d %d %d %d %d => %d)\n",
1763 line, array[0], array[1], array[2], array[3], array[4], v);
1764 t2 = insert_timer(&timers[4], tptr, v); t2->count++;
1765 lines_out++;
1766 }
1767 } else { /* percentile */
1768 if (err) {
1769 if (array[4] < 0)
1770 array[4] = -1;
1771 t2 = insert_value(&timers[0], tptr, array[4]); // total time
1772 t2->count++;
1773 } else {
1774 int v;
1775
1776 t2 = insert_value(&timers[1], tptr, array[0]); t2->count++; // req
1777 t2 = insert_value(&timers[2], tptr, array[2]); t2->count++; // conn
1778 t2 = insert_value(&timers[3], tptr, array[3]); t2->count++; // resp
1779
1780 v = array[4] - array[0] - array[1] - array[2] - array[3]; // data time
1781 if (v < 0 && !(filter & FILT_QUIET))
1782 fprintf(stderr, "ERR: %s (%d %d %d %d %d => %d)\n",
1783 line, array[0], array[1], array[2], array[3], array[4], v);
1784 t2 = insert_value(&timers[4], tptr, v); t2->count++;
1785 lines_out++;
1786 }
1787 }
1788}
1789
1790
Willy Tarreau72c28532009-01-22 18:56:50 +01001791/*
1792 * Local variables:
1793 * c-indent-level: 8
1794 * c-basic-offset: 8
1795 * End:
1796 */