diff options
Diffstat (limited to '')
-rw-r--r-- | admin/halog/README | 4 | ||||
-rw-r--r-- | admin/halog/fgets2.c | 267 | ||||
-rw-r--r-- | admin/halog/halog.c | 1910 |
3 files changed, 2181 insertions, 0 deletions
diff --git a/admin/halog/README b/admin/halog/README new file mode 100644 index 0000000..ff1bb12 --- /dev/null +++ b/admin/halog/README @@ -0,0 +1,4 @@ +This needs to be built from the top makefile, for example : + + make admin/halog/halog + diff --git a/admin/halog/fgets2.c b/admin/halog/fgets2.c new file mode 100644 index 0000000..7fbe16b --- /dev/null +++ b/admin/halog/fgets2.c @@ -0,0 +1,267 @@ +/* + * fast fgets() replacement for log parsing + * + * Copyright 2000-2012 Willy Tarreau <w@1wt.eu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This function manages its own buffer and returns a pointer to that buffer + * in order to avoid expensive memory copies. It also checks for line breaks + * 32 or 64 bits at a time. It could be improved a lot using mmap() but we + * would not be allowed to replace trailing \n with zeroes and we would be + * limited to small log files on 32-bit machines. + * + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> + +#ifndef FGETS2_BUFSIZE +#define FGETS2_BUFSIZE (256*1024) +#endif + +/* memchr() is faster in glibc with SSE since commit 093ecf92998de2 */ +#if defined(__x86_64__) && defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 15)) +#define USE_MEMCHR +#endif + +/* return non-zero if the integer contains at least one zero byte */ +static inline __attribute__((unused)) unsigned int has_zero32(unsigned int x) +{ + unsigned int y; + + /* Principle: we want to perform 4 tests on one 32-bit int at once. For + * this, we have to simulate an SIMD instruction which we don't have by + * default. The principle is that a zero byte is the only one which + * will cause a 1 to appear on the upper bit of a byte/word/etc... when + * we subtract 1. So we can detect a zero byte if a one appears at any + * of the bits 7, 15, 23 or 31 where it was not. It takes only one + * instruction to test for the presence of any of these bits, but it is + * still complex to check for their initial absence. Thus, we'll + * proceed differently : we first save and clear only those bits, then + * we check in the final result if one of them is present and was not. + * The order of operations below is important to save registers and + * tests. The result is used as a boolean, so the last test must apply + * on the constant so that it can efficiently be inlined. + */ +#if defined(__i386__) + /* gcc on x86 loves copying registers over and over even on code that + * simple, so let's do it by hand to prevent it from doing so :-( + */ + asm("lea -0x01010101(%0),%1\n" + "not %0\n" + "and %1,%0\n" + : "=a" (x), "=r"(y) + : "0" (x) + ); + return x & 0x80808080; +#else + y = x - 0x01010101; /* generate a carry */ + x = ~x & y; /* clear the bits that were already set */ + return x & 0x80808080; +#endif +} + +/* return non-zero if the argument contains at least one zero byte. See principle above. */ +static inline __attribute__((unused)) unsigned long long has_zero64(unsigned long long x) +{ + unsigned long long y; + + y = x - 0x0101010101010101ULL; /* generate a carry */ + y &= ~x; /* clear the bits that were already set */ + return y & 0x8080808080808080ULL; +} + +static inline __attribute__((unused)) unsigned long has_zero(unsigned long x) +{ + return (sizeof(x) == 8) ? has_zero64(x) : has_zero32(x); +} + +/* find a '\n' between <next> and <end>. Warning: may read slightly past <end>. + * If no '\n' is found, <end> is returned. + */ +static char *find_lf(char *next, char *end) +{ +#if defined USE_MEMCHR + /* some recent libc use platform-specific optimizations to provide more + * efficient byte search than below (eg: glibc 2.11 on x86_64). + */ + next = memchr(next, '\n', end - next); + if (!next) + next = end; +#else + if (sizeof(long) == 4) { /* 32-bit system */ + /* this is a speed-up, we read 32 bits at once and check for an + * LF character there. We stop if found then continue one at a + * time. + */ + while (next < end && (((unsigned long)next) & 3) && *next != '\n') + next++; + + /* Now next is multiple of 4 or equal to end. We know we can safely + * read up to 32 bytes past end if needed because they're allocated. + */ + while (next < end) { + if (has_zero32(*(unsigned int *)next ^ 0x0A0A0A0A)) + break; + next += 4; + if (has_zero32(*(unsigned int *)next ^ 0x0A0A0A0A)) + break; + next += 4; + if (has_zero32(*(unsigned int *)next ^ 0x0A0A0A0A)) + break; + next += 4; + if (has_zero32(*(unsigned int *)next ^ 0x0A0A0A0A)) + break; + next += 4; + if (has_zero32(*(unsigned int *)next ^ 0x0A0A0A0A)) + break; + next += 4; + if (has_zero32(*(unsigned int *)next ^ 0x0A0A0A0A)) + break; + next += 4; + if (has_zero32(*(unsigned int *)next ^ 0x0A0A0A0A)) + break; + next += 4; + if (has_zero32(*(unsigned int *)next ^ 0x0A0A0A0A)) + break; + next += 4; + } + } + else { /* 64-bit system */ + /* this is a speed-up, we read 64 bits at once and check for an + * LF character there. We stop if found then continue one at a + * time. + */ + if (next <= end) { + /* max 3 bytes tested here */ + while ((((unsigned long)next) & 3) && *next != '\n') + next++; + + /* maybe we have can skip 4 more bytes */ + if ((((unsigned long)next) & 4) && !has_zero32(*(unsigned int *)next ^ 0x0A0A0A0AU)) + next += 4; + } + + /* now next is multiple of 8 or equal to end */ + while (next <= (end-68)) { + if (has_zero64(*(unsigned long long *)next ^ 0x0A0A0A0A0A0A0A0AULL)) + break; + next += 8; + if (has_zero64(*(unsigned long long *)next ^ 0x0A0A0A0A0A0A0A0AULL)) + break; + next += 8; + if (has_zero64(*(unsigned long long *)next ^ 0x0A0A0A0A0A0A0A0AULL)) + break; + next += 8; + if (has_zero64(*(unsigned long long *)next ^ 0x0A0A0A0A0A0A0A0AULL)) + break; + next += 8; + if (has_zero64(*(unsigned long long *)next ^ 0x0A0A0A0A0A0A0A0AULL)) + break; + next += 8; + if (has_zero64(*(unsigned long long *)next ^ 0x0A0A0A0A0A0A0A0AULL)) + break; + next += 8; + if (has_zero64(*(unsigned long long *)next ^ 0x0A0A0A0A0A0A0A0AULL)) + break; + next += 8; + if (has_zero64(*(unsigned long long *)next ^ 0x0A0A0A0A0A0A0A0AULL)) + break; + next += 8; + } + + /* maybe we can skip 4 more bytes */ + if (!has_zero32(*(unsigned int *)next ^ 0x0A0A0A0AU)) + next += 4; + } + + /* We finish if needed : if <next> is below <end>, it means we + * found an LF in one of the 4 following bytes. + */ + while (next < end) { + if (*next == '\n') + break; + next++; + } +#endif + return next; +} + +const char *fgets2(FILE *stream) +{ + static char buffer[FGETS2_BUFSIZE + 68]; /* Note: +32 is enough on 32-bit systems */ + static char *end = buffer; + static char *line = buffer; + char *next; + int ret; + + next = line; + + while (1) { + next = find_lf(next, end); + if (next < end) { + const char *start = line; + *next = '\0'; + line = next + 1; + return start; + } + + /* we found an incomplete line. First, let's move the + * remaining part of the buffer to the beginning, then + * try to complete the buffer with a new read. We can't + * rely on <next> anymore because it went past <end>. + */ + if (line > buffer) { + if (end != line) + memmove(buffer, line, end - line); + end = buffer + (end - line); + next = end; + line = buffer; + } else { + if (end == buffer + FGETS2_BUFSIZE) + return NULL; + } + + ret = read(fileno(stream), end, buffer + FGETS2_BUFSIZE - end); + + if (ret <= 0) { + if (end == line) + return NULL; + + *end = '\0'; + end = line; /* ensure we stop next time */ + return line; + } + + end += ret; + *end = '\n'; /* make parser stop ASAP */ + /* search for '\n' again */ + } +} + +#ifdef BENCHMARK +int main() { + const char *p; + unsigned int lines = 0; + + while ((p=fgets2(stdin))) + lines++; + printf("lines=%u\n", lines); + return 0; +} +#endif diff --git a/admin/halog/halog.c b/admin/halog/halog.c new file mode 100644 index 0000000..45eec75 --- /dev/null +++ b/admin/halog/halog.c @@ -0,0 +1,1910 @@ +/* + * haproxy log statistics reporter + * + * Copyright 2000-2012 Willy Tarreau <w@1wt.eu> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <time.h> + +#include <haproxy/compiler.h> + +#include <import/eb32tree.h> +#include <import/eb64tree.h> +#include <import/ebistree.h> +#include <import/ebsttree.h> + +#define SOURCE_FIELD 5 +#define ACCEPT_FIELD 6 +#define SERVER_FIELD 8 +#define TIME_FIELD 9 +#define STATUS_FIELD 10 +#define BYTES_SENT_FIELD 11 +#define TERM_CODES_FIELD 14 +#define CONN_FIELD 15 +#define QUEUE_LEN_FIELD 16 +#define METH_FIELD 17 +#define URL_FIELD 18 +#define MAXLINE 16384 +#define QBITS 4 + +#define SEP(c) ((unsigned char)(c) <= ' ') +#define SKIP_CHAR(p,c) do { while (1) { int __c = (unsigned char)*p++; if (__c == c) break; if (__c <= ' ') { p--; break; } } } while (0) + +/* [0] = err/date, [1] = req, [2] = conn, [3] = resp, [4] = data */ +static struct eb_root timers[5] = { + EB_ROOT_UNIQUE, EB_ROOT_UNIQUE, EB_ROOT_UNIQUE, + EB_ROOT_UNIQUE, EB_ROOT_UNIQUE, +}; + +struct timer { + struct eb32_node node; + unsigned int count; +}; + +struct srv_st { + unsigned int st_cnt[6]; /* 0xx to 5xx */ + unsigned int nb_ct, nb_rt, nb_ok; + unsigned long long cum_ct, cum_rt; + struct ebmb_node node; + /* don't put anything else here, the server name will be there */ +}; + +struct url_stat { + union { + struct ebpt_node url; + struct eb64_node val; + } node; + char *url; + unsigned long long total_time; /* sum(all reqs' times) */ + unsigned long long total_time_ok; /* sum(all OK reqs' times) */ + unsigned long long total_bytes_sent; /* sum(all bytes sent) */ + unsigned int nb_err, nb_req; +}; + +#define FILT_COUNT_ONLY 0x01 +#define FILT_INVERT 0x02 +#define FILT_QUIET 0x04 +#define FILT_ERRORS_ONLY 0x08 +#define FILT_ACC_DELAY 0x10 +#define FILT_ACC_COUNT 0x20 +#define FILT_GRAPH_TIMERS 0x40 +#define FILT_PERCENTILE 0x80 +#define FILT_TIME_RESP 0x100 + +#define FILT_INVERT_ERRORS 0x200 +#define FILT_INVERT_TIME_RESP 0x400 + +#define FILT_COUNT_STATUS 0x800 +#define FILT_COUNT_SRV_STATUS 0x1000 +#define FILT_COUNT_TERM_CODES 0x2000 + +#define FILT_COUNT_URL_ONLY 0x004000 +#define FILT_COUNT_URL_COUNT 0x008000 +#define FILT_COUNT_URL_ERR 0x010000 +#define FILT_COUNT_URL_TTOT 0x020000 +#define FILT_COUNT_URL_TAVG 0x040000 +#define FILT_COUNT_URL_TTOTO 0x080000 +#define FILT_COUNT_URL_TAVGO 0x100000 + +#define FILT_HTTP_ONLY 0x200000 +#define FILT_TERM_CODE_NAME 0x400000 +#define FILT_INVERT_TERM_CODE_NAME 0x800000 + +#define FILT_HTTP_STATUS 0x1000000 +#define FILT_INVERT_HTTP_STATUS 0x2000000 +#define FILT_QUEUE_ONLY 0x4000000 +#define FILT_QUEUE_SRV_ONLY 0x8000000 + +#define FILT_COUNT_URL_BAVG 0x10000000 +#define FILT_COUNT_URL_BTOT 0x20000000 + +#define FILT_COUNT_URL_ANY (FILT_COUNT_URL_ONLY|FILT_COUNT_URL_COUNT|FILT_COUNT_URL_ERR| \ + FILT_COUNT_URL_TTOT|FILT_COUNT_URL_TAVG|FILT_COUNT_URL_TTOTO|FILT_COUNT_URL_TAVGO| \ + FILT_COUNT_URL_BAVG|FILT_COUNT_URL_BTOT) + +#define FILT_COUNT_COOK_CODES 0x40000000 +#define FILT_COUNT_IP_COUNT 0x80000000 + +#define FILT2_TIMESTAMP 0x01 +#define FILT2_PRESERVE_QUERY 0x02 +#define FILT2_EXTRACT_CAPTURE 0x04 + +unsigned int filter = 0; +unsigned int filter2 = 0; +unsigned int filter_invert = 0; +const char *line; +int linenum = 0; +int parse_err = 0; +int lines_out = 0; +int lines_max = -1; + +const char *fgets2(FILE *stream); + +void filter_count_url(const char *accept_field, const char *time_field, struct timer **tptr); +void filter_count_ip(const char *source_field, const char *accept_field, const char *time_field, struct timer **tptr); +void filter_count_srv_status(const char *accept_field, const char *time_field, struct timer **tptr); +void filter_count_cook_codes(const char *accept_field, const char *time_field, struct timer **tptr); +void filter_count_term_codes(const char *accept_field, const char *time_field, struct timer **tptr); +void filter_count_status(const char *accept_field, const char *time_field, struct timer **tptr); +void filter_graphs(const char *accept_field, const char *time_field, struct timer **tptr); +void filter_output_line(const char *accept_field, const char *time_field, struct timer **tptr); +void filter_extract_capture(const char *accept_field, const char *time_field, unsigned int, unsigned int); +void filter_accept_holes(const char *accept_field, const char *time_field, struct timer **tptr); + +void usage(FILE *output, const char *msg) +{ + fprintf(output, + "%s" + "Usage:\n" + " halog [-h|--help] for long help\n" + " halog [input_filters]* [modifiers]* [output_format] < log\n" + " inp = [-e|-E] [-H] [-Q|-QS] [-rt|-RT <time>] [-ad <delay>] [-ac <count>]\n" + " [-hs|-HS [min][:[max]]] [-tcn|-TCN <termcode>] [-time [min][:[max]]]\n" + " mod = [-q] [-v] [-m <lines>] [-s <skipflds>] [-query]\n" + " out = {-c|-u|-uc|-ue|-ua|-ut|-uao|-uto|-uba|-ubt|-hdr <block>:<field>|\n" + " -cc|-gt|-pct|-st|-tc|-srv|-ic}\n" + "\n", + msg ? msg : "" + ); +} + +void die(const char *msg) +{ + usage(stderr, msg); + exit(1); +} + +void help() +{ + usage(stdout, NULL); + printf( + "Input filters - several filters may be combined\n" + " -H only match lines containing HTTP logs (ignore TCP)\n" + " -E only match lines without any error (no 5xx status)\n" + " -e only match lines with errors (status 5xx or negative)\n" + " -rt|-RT <time> only match response times larger|smaller than <time>\n" + " -Q|-QS only match queued requests (any queue|server queue)\n" + " -tcn|-TCN <code> only match requests with/without termination code <code>\n" + " -hs|-HS <[min][:][max]> only match requests with HTTP status codes within/not\n" + " within min..max. Any of them may be omitted. Exact\n" + " code is checked for if no ':' is specified.\n" + " -time <[min][:max]> only match requests recorded between timestamps.\n" + " Any of them may be omitted.\n" + "Modifiers\n" + " -v invert the input filtering condition\n" + " -q don't report errors/warnings\n" + " -m <lines> limit output to the first <lines> lines\n" + " -s <skip_n_fields> skip n fields from the beginning of a line (default %d)\n" + " you can also use -n to start from earlier then field %d\n" + " -query preserve the query string for per-URL (-u*) statistics\n" + "\n" + "Output format - only one may be used at a time\n" + " -c only report the number of lines that would have been printed\n" + " -pct output connect and response times percentiles\n" + " -st output number of requests per HTTP status code\n" + " -cc output number of requests per cookie code (2 chars)\n" + " -tc output number of requests per termination code (2 chars)\n" + " -srv output statistics per server (time, requests, errors)\n" + " -ic output statistics per ip count (time, requests, errors)\n" + " -u* output statistics per URL (time, requests, errors)\n" + " Additional characters indicate the output sorting key :\n" + " -u : by URL, -uc : request count, -ue : error count\n" + " -ua : average response time, -ut : average total time\n" + " -uao, -uto: average times computed on valid ('OK') requests\n" + " -uba, -ubt: average bytes returned, total bytes returned\n" + " -hdr output captured header at the given <block>:<field>\n", + (int)SOURCE_FIELD, (int)SOURCE_FIELD + ); + exit(0); +} + + +/* return pointer to first char not part of current field starting at <p>. */ + +#if defined(__i386__) +/* this one is always faster on 32-bits */ +static inline const char *field_stop(const char *p) +{ + asm( + /* Look for spaces */ + "4: \n\t" + "inc %0 \n\t" + "cmpb $0x20, -1(%0) \n\t" + "ja 4b \n\t" + "jz 3f \n\t" + + /* we only get there for control chars 0..31. Leave if we find '\0' */ + "cmpb $0x0, -1(%0) \n\t" + "jnz 4b \n\t" + + /* return %0-1 = position of the last char we checked */ + "3: \n\t" + "dec %0 \n\t" + : "=r" (p) + : "0" (p) + ); + return p; +} +#else +const char *field_stop(const char *p) +{ + unsigned char c; + + while (1) { + c = *(p++); + if (c > ' ') + continue; + if (c == ' ' || c == 0) + break; + } + return p - 1; +} +#endif + +/* return non-zero if the argument contains at least one zero byte. See principle above. */ +static inline __attribute__((unused)) unsigned long long has_zero64(unsigned long long x) +{ + unsigned long long y; + + y = x - 0x0101010101010101ULL; /* generate a carry */ + y &= ~x; /* clear the bits that were already set */ + return y & 0x8080808080808080ULL; +} + +/* return field <field> (starting from 1) in string <p>. Only consider + * contiguous spaces (or tabs) as one delimiter. May return pointer to + * last char if field is not found. Equivalent to awk '{print $field}'. + */ +const char *field_start(const char *p, int field) +{ +#ifndef PREFER_ASM + unsigned char c; + while (1) { + /* skip spaces */ + while (1) { + c = *(p++); + if (!c) /* end of line */ + return p-1; + if (c == ' ') + continue; + /* other char => new field */ + break; + } + + /* start of field */ + field--; + if (!field) + return p-1; + + /* skip this field */ + while (1) { +#if defined(HA_UNALIGNED_LE64) + unsigned long long l = *(unsigned long long *)p; + if (!has_zero64(l)) { + l ^= 0x2020202020202020; + l = has_zero64(l); + if (!l) { + p += 8; + continue; + } + /* there is at least one space, find it and + * skip it now. The lowest byte in <l> with + * a 0x80 is the right one, but checking for + * it remains slower than testing each byte, + * probably due to the numerous short fields. + */ + while (*(p++) != ' ') + ; + break; + } +#endif + c = *(p++); + if (c == '\0') + return p - 1; + if (c == ' ') + break; + } + } +#else + /* This version works optimally on i386 and x86_64 but the code above + * shows similar performance. However, depending on the version of GCC + * used, inlining rules change and it may have difficulties to make + * efficient use of this code at other locations and could result in + * worse performance (eg: gcc 4.4). You may want to experience. + */ + asm( + /* skip spaces */ + "1: \n\t" + "inc %0 \n\t" + "cmpb $0x20, -1(%0) \n\t" + "ja 2f \n\t" + "jz 1b \n\t" + + /* we only get there for control chars 0..31. Leave if we find '\0' */ + "cmpb $0x0, -1(%0) \n\t" + "jz 3f \n\t" + + /* start of field at [%0-1]. Check if we need to skip more fields */ + "2: \n\t" + "dec %1 \n\t" + "jz 3f \n\t" + + /* Look for spaces */ + "4: \n\t" + "inc %0 \n\t" + "cmpb $0x20, -1(%0) \n\t" + "jz 1b \n\t" + "ja 4b \n\t" + + /* we only get there for control chars 0..31. Leave if we find '\0' */ + "cmpb $0x0, -1(%0) \n\t" + "jnz 4b \n\t" + + /* return %0-1 = position of the last char we checked */ + "3: \n\t" + "dec %0 \n\t" + : "=r" (p) + : "r" (field), "0" (p) + ); + return p; +#endif +} + +/* keep only the <bits> higher bits of <i> */ +static inline unsigned int quantify_u32(unsigned int i, int bits) +{ + int high; + + if (!bits) + return 0; + + if (i) + high = fls_auto(i); // 1 to 32 + else + high = 0; + + if (high <= bits) + return i; + + return i & ~((1 << (high - bits)) - 1); +} + +/* keep only the <bits> higher bits of the absolute value of <i>, as well as + * its sign. */ +static inline int quantify(int i, int bits) +{ + if (i >= 0) + return quantify_u32(i, bits); + else + return -quantify_u32(-i, bits); +} + +/* Insert timer value <v> into tree <r>. A pre-allocated node must be passed + * in <alloc>. It may be NULL, in which case the function will allocate it + * itself. It will be reset to NULL once consumed. The caller is responsible + * for freeing the node once not used anymore. The node where the value was + * inserted is returned. + */ +struct timer *insert_timer(struct eb_root *r, struct timer **alloc, int v) +{ + struct timer *t = *alloc; + struct eb32_node *n; + + if (!t) { + t = calloc(sizeof(*t), 1); + if (unlikely(!t)) { + fprintf(stderr, "%s: not enough memory\n", __FUNCTION__); + exit(1); + } + } + t->node.key = quantify(v, QBITS); // keep only the higher QBITS bits + + n = eb32i_insert(r, &t->node); + if (n == &t->node) + t = NULL; /* node inserted, will malloc next time */ + + *alloc = t; + return container_of(n, struct timer, node); +} + +/* Insert value value <v> into tree <r>. A pre-allocated node must be passed + * in <alloc>. It may be NULL, in which case the function will allocate it + * itself. It will be reset to NULL once consumed. The caller is responsible + * for freeing the node once not used anymore. The node where the value was + * inserted is returned. + */ +struct timer *insert_value(struct eb_root *r, struct timer **alloc, int v) +{ + struct timer *t = *alloc; + struct eb32_node *n; + + if (!t) { + t = calloc(sizeof(*t), 1); + if (unlikely(!t)) { + fprintf(stderr, "%s: not enough memory\n", __FUNCTION__); + exit(1); + } + } + t->node.key = v; + + n = eb32i_insert(r, &t->node); + if (n == &t->node) + t = NULL; /* node inserted, will malloc next time */ + + *alloc = t; + return container_of(n, struct timer, node); +} + +int str2ic(const char *s) +{ + int i = 0; + int j, k; + + if (*s != '-') { + /* positive number */ + while (1) { + j = (*s++) - '0'; + k = i * 10; + if ((unsigned)j > 9) + break; + i = k + j; + } + } else { + /* negative number */ + s++; + while (1) { + j = (*s++) - '0'; + k = i * 10; + if ((unsigned)j > 9) + break; + i = k - j; + } + } + + return i; +} + + +/* Convert "[04/Dec/2008:09:49:40.555]" to an integer equivalent to the time of + * the day in milliseconds. It returns -1 for all unparsable values. The parser + * looks ugly but gcc emits far better code that way. + */ +int convert_date(const char *field) +{ + unsigned int h, m, s, ms; + unsigned char c; + const char *e; + + h = m = s = ms = 0; + e = field; + + /* skip the date */ + while (1) { + c = *(e++); + if (c == ':') + break; + if (!c) + goto out_err; + } + + /* hour + ':' */ + while (1) { + c = *(e++) - '0'; + if (c > 9) + break; + h = h * 10 + c; + } + if (c == (unsigned char)(0 - '0')) + goto out_err; + + /* minute + ':' */ + while (1) { + c = *(e++) - '0'; + if (c > 9) + break; + m = m * 10 + c; + } + if (c == (unsigned char)(0 - '0')) + goto out_err; + + /* second + '.' or ']' */ + while (1) { + c = *(e++) - '0'; + if (c > 9) + break; + s = s * 10 + c; + } + if (c == (unsigned char)(0 - '0')) + goto out_err; + + /* if there's a '.', we have milliseconds */ + if (c == (unsigned char)('.' - '0')) { + /* millisecond second + ']' */ + while (1) { + c = *(e++) - '0'; + if (c > 9) + break; + ms = ms * 10 + c; + } + if (c == (unsigned char)(0 - '0')) + goto out_err; + } + return (((h * 60) + m) * 60 + s) * 1000 + ms; + out_err: + return -1; +} + +/* Convert "[04/Dec/2008:09:49:40.555]" to an unix timestamp. + * It returns -1 for all unparsable values. The parser + * looks ugly but gcc emits far better code that way. + */ +int convert_date_to_timestamp(const char *field) +{ + unsigned int d, mo, y, h, m, s; + unsigned char c; + const char *e; + time_t rawtime; + static struct tm * timeinfo; + static int last_res; + + d = mo = y = h = m = s = 0; + e = field; + + e++; // remove '[' + + /* day + '/' */ + while (1) { + c = *(e++) - '0'; + if (c > 9) + break; + d = d * 10 + c; + if (c == (unsigned char)(0 - '0')) + goto out_err; + } + + /* month + '/' */ + c = *(e++); + if (c =='F') { + mo = 2; + e = e+3; + } else if (c =='S') { + mo = 9; + e = e+3; + } else if (c =='O') { + mo = 10; + e = e+3; + } else if (c =='N') { + mo = 11; + e = e+3; + } else if (c == 'D') { + mo = 12; + e = e+3; + } else if (c == 'A') { + c = *(e++); + if (c == 'p') { + mo = 4; + e = e+2; + } else if (c == 'u') { + mo = 8; + e = e+2; + } else + goto out_err; + } else if (c == 'J') { + c = *(e++); + if (c == 'a') { + mo = 1; + e = e+2; + } else if (c == 'u') { + c = *(e++); + if (c == 'n') { + mo = 6; + e = e+1; + } else if (c == 'l') { + mo = 7; + e++; + } + } else + goto out_err; + } else if (c == 'M') { + e++; + c = *(e++); + if (c == 'r') { + mo = 3; + e = e+1; + } else if (c == 'y') { + mo = 5; + e = e+1; + } else + goto out_err; + } else + goto out_err; + + /* year + ':' */ + while (1) { + c = *(e++) - '0'; + if (c > 9) + break; + y = y * 10 + c; + if (c == (unsigned char)(0 - '0')) + goto out_err; + } + + /* hour + ':' */ + while (1) { + c = *(e++) - '0'; + if (c > 9) + break; + h = h * 10 + c; + } + if (c == (unsigned char)(0 - '0')) + goto out_err; + + /* minute + ':' */ + while (1) { + c = *(e++) - '0'; + if (c > 9) + break; + m = m * 10 + c; + } + if (c == (unsigned char)(0 - '0')) + goto out_err; + + /* second + '.' or ']' */ + while (1) { + c = *(e++) - '0'; + if (c > 9) + break; + s = s * 10 + c; + } + + if (likely(timeinfo)) { + if ((unsigned)timeinfo->tm_min == m && + (unsigned)timeinfo->tm_hour == h && + (unsigned)timeinfo->tm_mday == d && + (unsigned)timeinfo->tm_mon == mo - 1 && + (unsigned)timeinfo->tm_year == y - 1900) + return last_res + s; + } + else { + time(&rawtime); + timeinfo = localtime(&rawtime); + } + + timeinfo->tm_sec = 0; + timeinfo->tm_min = m; + timeinfo->tm_hour = h; + timeinfo->tm_mday = d; + timeinfo->tm_mon = mo - 1; + timeinfo->tm_year = y - 1900; + last_res = mktime(timeinfo); + + return last_res + s; + out_err: + return -1; +} + +void truncated_line(int linenum, const char *line) +{ + if (!(filter & FILT_QUIET)) + fprintf(stderr, "Truncated line %d: %s\n", linenum, line); +} + +int main(int argc, char **argv) +{ + const char *b, *p, *time_field, *accept_field, *source_field; + const char *filter_term_code_name = NULL; + const char *output_file = NULL; + int f, last; + struct timer *t = NULL; + struct eb32_node *n; + struct url_stat *ustat = NULL; + int val, test; + unsigned int uval; + unsigned int filter_acc_delay = 0, filter_acc_count = 0; + int filter_time_resp = 0; + int filt_http_status_low = 0, filt_http_status_high = 0; + unsigned int filt2_timestamp_low = 0, filt2_timestamp_high = 0; + unsigned int filt2_capture_block = 0, filt2_capture_field = 0; + int skip_fields = 1; + + void (*line_filter)(const char *accept_field, const char *time_field, struct timer **tptr) = NULL; + + argc--; argv++; + while (argc > 0) { + if (*argv[0] != '-') + break; + + if (strcmp(argv[0], "-ad") == 0) { + if (argc < 2) die("missing option for -ad\n"); + argc--; argv++; + filter |= FILT_ACC_DELAY; + filter_acc_delay = atol(*argv); + } + else if (strcmp(argv[0], "-ac") == 0) { + if (argc < 2) die("missing option for -ac\n"); + argc--; argv++; + filter |= FILT_ACC_COUNT; + filter_acc_count = atol(*argv); + } + else if (strcmp(argv[0], "-rt") == 0) { + if (argc < 2) die("missing option for -rt\n"); + argc--; argv++; + filter |= FILT_TIME_RESP; + filter_time_resp = atol(*argv); + } + else if (strcmp(argv[0], "-RT") == 0) { + if (argc < 2) die("missing option for -RT\n"); + argc--; argv++; + filter |= FILT_TIME_RESP | FILT_INVERT_TIME_RESP; + filter_time_resp = atol(*argv); + } + else if (strcmp(argv[0], "-s") == 0) { + if (argc < 2) die("missing option for -s\n"); + argc--; argv++; + skip_fields = atol(*argv); + } + else if (strcmp(argv[0], "-m") == 0) { + if (argc < 2) die("missing option for -m\n"); + argc--; argv++; + lines_max = atol(*argv); + } + else if (strcmp(argv[0], "-e") == 0) + filter |= FILT_ERRORS_ONLY; + else if (strcmp(argv[0], "-E") == 0) + filter |= FILT_ERRORS_ONLY | FILT_INVERT_ERRORS; + else if (strcmp(argv[0], "-H") == 0) + filter |= FILT_HTTP_ONLY; + else if (strcmp(argv[0], "-Q") == 0) + filter |= FILT_QUEUE_ONLY; + else if (strcmp(argv[0], "-QS") == 0) + filter |= FILT_QUEUE_SRV_ONLY; + else if (strcmp(argv[0], "-c") == 0) + filter |= FILT_COUNT_ONLY; + else if (strcmp(argv[0], "-q") == 0) + filter |= FILT_QUIET; + else if (strcmp(argv[0], "-v") == 0) + filter_invert = !filter_invert; + else if (strcmp(argv[0], "-gt") == 0) + filter |= FILT_GRAPH_TIMERS; + else if (strcmp(argv[0], "-pct") == 0) + filter |= FILT_PERCENTILE; + else if (strcmp(argv[0], "-st") == 0) + filter |= FILT_COUNT_STATUS; + else if (strcmp(argv[0], "-srv") == 0) + filter |= FILT_COUNT_SRV_STATUS; + else if (strcmp(argv[0], "-cc") == 0) + filter |= FILT_COUNT_COOK_CODES; + else if (strcmp(argv[0], "-tc") == 0) + filter |= FILT_COUNT_TERM_CODES; + else if (strcmp(argv[0], "-tcn") == 0) { + if (argc < 2) die("missing option for -tcn\n"); + argc--; argv++; + filter |= FILT_TERM_CODE_NAME; + filter_term_code_name = *argv; + } + else if (strcmp(argv[0], "-TCN") == 0) { + if (argc < 2) die("missing option for -TCN\n"); + argc--; argv++; + filter |= FILT_TERM_CODE_NAME | FILT_INVERT_TERM_CODE_NAME; + filter_term_code_name = *argv; + } + else if (strcmp(argv[0], "-hs") == 0 || strcmp(argv[0], "-HS") == 0) { + char *sep, *str; + + if (argc < 2) die("missing option for -hs/-HS ([min]:[max])\n"); + filter |= FILT_HTTP_STATUS; + if (argv[0][1] == 'H') + filter |= FILT_INVERT_HTTP_STATUS; + + argc--; argv++; + str = *argv; + sep = strchr(str, ':'); /* [min]:[max] */ + if (!sep) + sep = str; /* make max point to min */ + else + *sep++ = 0; + filt_http_status_low = *str ? atol(str) : 0; + filt_http_status_high = *sep ? atol(sep) : 65535; + } + else if (strcmp(argv[0], "-time") == 0) { + char *sep, *str; + + if (argc < 2) die("missing option for -time ([min]:[max])\n"); + filter2 |= FILT2_TIMESTAMP; + + argc--; argv++; + str = *argv; + sep = strchr(str, ':'); /* [min]:[max] */ + filt2_timestamp_low = *str ? atol(str) : 0; + if (!sep) + filt2_timestamp_high = 0xFFFFFFFF; + else + filt2_timestamp_high = atol(++sep); + } + else if (strcmp(argv[0], "-u") == 0) + filter |= FILT_COUNT_URL_ONLY; + else if (strcmp(argv[0], "-uc") == 0) + filter |= FILT_COUNT_URL_COUNT; + else if (strcmp(argv[0], "-ue") == 0) + filter |= FILT_COUNT_URL_ERR; + else if (strcmp(argv[0], "-ua") == 0) + filter |= FILT_COUNT_URL_TAVG; + else if (strcmp(argv[0], "-ut") == 0) + filter |= FILT_COUNT_URL_TTOT; + else if (strcmp(argv[0], "-uao") == 0) + filter |= FILT_COUNT_URL_TAVGO; + else if (strcmp(argv[0], "-uto") == 0) + filter |= FILT_COUNT_URL_TTOTO; + else if (strcmp(argv[0], "-uba") == 0) + filter |= FILT_COUNT_URL_BAVG; + else if (strcmp(argv[0], "-ubt") == 0) + filter |= FILT_COUNT_URL_BTOT; + else if (strcmp(argv[0], "-query") == 0) + filter2 |= FILT2_PRESERVE_QUERY; + else if (strcmp(argv[0], "-ic") == 0) + filter |= FILT_COUNT_IP_COUNT; + else if (strcmp(argv[0], "-hdr") == 0) { + char *sep, *str; + + if (argc < 2) die("missing option for -hdr (<block>:<field>)\n"); + filter2 |= FILT2_EXTRACT_CAPTURE; + + argc--; argv++; + str = *argv; + sep = strchr(str, ':'); + if (!sep) + die("missing colon in -hdr (<block>:<field>)\n"); + else + *sep++ = 0; + + filt2_capture_block = *str ? atol(str) : 1; + filt2_capture_field = *sep ? atol(sep) : 1; + + if (filt2_capture_block < 1 || filt2_capture_field < 1) + die("block and field must be at least 1 for -hdr (<block>:<field>)\n"); + } + else if (strcmp(argv[0], "-o") == 0) { + if (output_file) + die("Fatal: output file name already specified.\n"); + if (argc < 2) + die("Fatal: missing output file name.\n"); + output_file = argv[1]; + } + else if (strcmp(argv[0], "-h") == 0 || strcmp(argv[0], "--help") == 0) + help(); + argc--; + argv++; + } + + if (!filter && !filter2) + die("No action specified.\n"); + + if (filter & FILT_ACC_COUNT && !filter_acc_count) + filter_acc_count=1; + + if (filter & FILT_ACC_DELAY && !filter_acc_delay) + filter_acc_delay = 1; + + + /* by default, all lines are printed */ + line_filter = filter_output_line; + if (filter & (FILT_ACC_COUNT|FILT_ACC_DELAY)) + line_filter = filter_accept_holes; + else if (filter & (FILT_GRAPH_TIMERS|FILT_PERCENTILE)) + line_filter = filter_graphs; + else if (filter & FILT_COUNT_STATUS) + line_filter = filter_count_status; + else if (filter & FILT_COUNT_COOK_CODES) + line_filter = filter_count_cook_codes; + else if (filter & FILT_COUNT_TERM_CODES) + line_filter = filter_count_term_codes; + else if (filter & FILT_COUNT_SRV_STATUS) + line_filter = filter_count_srv_status; + else if (filter & FILT_COUNT_URL_ANY) + line_filter = filter_count_url; + else if (filter & FILT_COUNT_ONLY) + line_filter = NULL; + +#if defined(POSIX_FADV_SEQUENTIAL) + /* around 20% performance improvement is observed on Linux with this + * on cold-cache. Surprisingly, WILLNEED is less performant. Don't + * use NOREUSE as it flushes the cache and prevents easy data + * manipulation on logs! + */ + posix_fadvise(0, 0, 0, POSIX_FADV_SEQUENTIAL); +#endif + + if (!line_filter && /* FILT_COUNT_ONLY ( see above), and no input filter (see below) */ + !(filter & (FILT_HTTP_ONLY|FILT_TIME_RESP|FILT_ERRORS_ONLY|FILT_HTTP_STATUS|FILT_QUEUE_ONLY|FILT_QUEUE_SRV_ONLY|FILT_TERM_CODE_NAME)) && + !(filter2 & (FILT2_TIMESTAMP))) { + /* read the whole file at once first, ignore it if inverted output */ + if (!filter_invert) + while ((lines_max < 0 || lines_out < lines_max) && fgets2(stdin) != NULL) + lines_out++; + + goto skip_filters; + } + + while ((line = fgets2(stdin)) != NULL) { + linenum++; + time_field = NULL; accept_field = NULL; + source_field = NULL; + + test = 1; + + /* for any line we process, we first ensure that there is a field + * looking like the accept date field (beginning with a '['). + */ + if (filter & FILT_COUNT_IP_COUNT) { + /* we need the IP first */ + source_field = field_start(line, SOURCE_FIELD + skip_fields); + accept_field = field_start(source_field, ACCEPT_FIELD - SOURCE_FIELD + 1); + } + else + accept_field = field_start(line, ACCEPT_FIELD + skip_fields); + + if (unlikely(*accept_field != '[')) { + parse_err++; + continue; + } + + /* the day of month field is begin 01 and 31 */ + if (accept_field[1] < '0' || accept_field[1] > '3') { + parse_err++; + continue; + } + + if (filter2 & FILT2_TIMESTAMP) { + uval = convert_date_to_timestamp(accept_field); + test &= (uval>=filt2_timestamp_low && uval<=filt2_timestamp_high) ; + } + + if (filter & FILT_HTTP_ONLY) { + /* only report lines with at least 4 timers */ + if (!time_field) { + time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1); + if (unlikely(!*time_field)) { + truncated_line(linenum, line); + continue; + } + } + + field_stop(time_field + 1); + /* we have field TIME_FIELD in [time_field]..[e-1] */ + p = time_field; + f = 0; + while (!SEP(*p)) { + if (++f == 4) + break; + SKIP_CHAR(p, '/'); + } + test &= (f >= 4); + } + + if (filter & FILT_TIME_RESP) { + int tps; + + /* only report lines with response times larger than filter_time_resp */ + if (!time_field) { + time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1); + if (unlikely(!*time_field)) { + truncated_line(linenum, line); + continue; + } + } + + field_stop(time_field + 1); + /* we have field TIME_FIELD in [time_field]..[e-1], let's check only the response time */ + + p = time_field; + f = 0; + while (!SEP(*p)) { + tps = str2ic(p); + if (tps < 0) { + tps = -1; + } + if (++f == 4) + break; + SKIP_CHAR(p, '/'); + } + + if (unlikely(f < 4)) { + parse_err++; + continue; + } + + test &= (tps >= filter_time_resp) ^ !!(filter & FILT_INVERT_TIME_RESP); + } + + if (filter & (FILT_ERRORS_ONLY | FILT_HTTP_STATUS)) { + /* Check both error codes (-1, 5xx) and status code ranges */ + if (time_field) + b = field_start(time_field, STATUS_FIELD - TIME_FIELD + 1); + else + b = field_start(accept_field, STATUS_FIELD - ACCEPT_FIELD + 1); + + if (unlikely(!*b)) { + truncated_line(linenum, line); + continue; + } + + val = str2ic(b); + if (filter & FILT_ERRORS_ONLY) + test &= (val < 0 || (val >= 500 && val <= 599)) ^ !!(filter & FILT_INVERT_ERRORS); + + if (filter & FILT_HTTP_STATUS) + test &= (val >= filt_http_status_low && val <= filt_http_status_high) ^ !!(filter & FILT_INVERT_HTTP_STATUS); + } + + if (filter & (FILT_QUEUE_ONLY|FILT_QUEUE_SRV_ONLY)) { + /* Check if the server's queue is non-nul */ + if (time_field) + b = field_start(time_field, QUEUE_LEN_FIELD - TIME_FIELD + 1); + else + b = field_start(accept_field, QUEUE_LEN_FIELD - ACCEPT_FIELD + 1); + + if (unlikely(!*b)) { + truncated_line(linenum, line); + continue; + } + + if (*b == '0') { + if (filter & FILT_QUEUE_SRV_ONLY) { + test = 0; + } + else { + do { + b++; + if (*b == '/') { + b++; + break; + } + } while (*b); + test &= ((unsigned char)(*b - '1') < 9); + } + } + } + + if (filter & FILT_TERM_CODE_NAME) { + /* only report corresponding termination code name */ + if (time_field) + b = field_start(time_field, TERM_CODES_FIELD - TIME_FIELD + 1); + else + b = field_start(accept_field, TERM_CODES_FIELD - ACCEPT_FIELD + 1); + + if (unlikely(!*b)) { + truncated_line(linenum, line); + continue; + } + + test &= (b[0] == filter_term_code_name[0] && b[1] == filter_term_code_name[1]) ^ !!(filter & FILT_INVERT_TERM_CODE_NAME); + } + + + test ^= filter_invert; + if (!test) + continue; + + /************** here we process inputs *******************/ + + if (line_filter) { + if (filter & FILT_COUNT_IP_COUNT) + filter_count_ip(source_field, accept_field, time_field, &t); + else if (filter2 & FILT2_EXTRACT_CAPTURE) + filter_extract_capture(accept_field, time_field, filt2_capture_block, filt2_capture_field); + else + line_filter(accept_field, time_field, &t); + } + else + lines_out++; /* FILT_COUNT_ONLY was used, so we're just counting lines */ + if (lines_max >= 0 && lines_out >= lines_max) + break; + } + + skip_filters: + /***************************************************** + * Here we've finished reading all input. Depending on the + * filters, we may still have some analysis to run on the + * collected data and to output data in a new format. + *************************************************** */ + + if (t) + free(t); + + if (filter & FILT_COUNT_ONLY) { + printf("%d\n", lines_out); + exit(0); + } + + if (filter & (FILT_ACC_COUNT|FILT_ACC_DELAY)) { + /* sort and count all timers. Output will look like this : + * <accept_date> <delta_ms from previous one> <nb entries> + */ + n = eb32_first(&timers[0]); + + if (n) + last = n->key; + while (n) { + unsigned int d, h, m, s, ms; + + t = container_of(n, struct timer, node); + h = n->key; + d = h - last; + last = h; + + if (d >= filter_acc_delay && t->count >= filter_acc_count) { + ms = h % 1000; h = h / 1000; + s = h % 60; h = h / 60; + m = h % 60; h = h / 60; + printf("%02u:%02u:%02u.%03u %d %u %u\n", h, m, s, ms, last, d, t->count); + lines_out++; + if (lines_max >= 0 && lines_out >= lines_max) + break; + } + n = eb32_next(n); + } + } + else if (filter & FILT_GRAPH_TIMERS) { + /* sort all timers */ + for (f = 0; f < 5; f++) { + struct eb32_node *n; + + n = eb32_first(&timers[f]); + while (n) { + int i; + double d; + int val; + + t = container_of(n, struct timer, node); + last = n->key; + val = t->count; + + i = (last < 0) ? -last : last; + i = fls_auto(i) - QBITS; + + if (i > 0) + d = val / (double)(1 << i); + else + d = val; + + if (d > 0.0) + printf("%d %d %f\n", f, last, d+1.0); + + n = eb32_next(n); + } + } + } + else if (filter & FILT_PERCENTILE) { + /* report timers by percentile : + * <percent> <total> <max_req_time> <max_conn_time> <max_resp_time> <max_data_time> + * We don't count errs. + */ + struct eb32_node *n[5]; + unsigned long cum[5]; + double step; + + if (!lines_out) + goto empty; + + for (f = 1; f < 5; f++) { + n[f] = eb32_first(&timers[f]); + cum[f] = container_of(n[f], struct timer, node)->count; + } + + for (step = 1; step <= 1000;) { + unsigned int thres = lines_out * (step / 1000.0); + + printf("%3.1f %u ", step/10.0, thres); + for (f = 1; f < 5; f++) { + struct eb32_node *next; + while (cum[f] < thres) { + /* need to find other keys */ + next = eb32_next(n[f]); + if (!next) + break; + n[f] = next; + cum[f] += container_of(next, struct timer, node)->count; + } + + /* value still within $step % of total */ + printf("%d ", n[f]->key); + } + putchar('\n'); + if (step >= 100 && step < 900) + step += 50; // jump 5% by 5% between those steps. + else if (step >= 20 && step < 980) + step += 10; + else + step += 1; + } + } + else if (filter & FILT_COUNT_STATUS) { + /* output all statuses in the form of <status> <occurrences> */ + n = eb32_first(&timers[0]); + while (n) { + t = container_of(n, struct timer, node); + printf("%d %u\n", n->key, t->count); + lines_out++; + if (lines_max >= 0 && lines_out >= lines_max) + break; + n = eb32_next(n); + } + } + else if (filter & FILT_COUNT_SRV_STATUS) { + struct ebmb_node *srv_node; + struct srv_st *srv; + + printf("#srv_name 1xx 2xx 3xx 4xx 5xx other tot_req req_ok pct_ok avg_ct avg_rt\n"); + + srv_node = ebmb_first(&timers[0]); + while (srv_node) { + int tot_rq; + + srv = container_of(srv_node, struct srv_st, node); + + tot_rq = 0; + for (f = 0; f <= 5; f++) + tot_rq += srv->st_cnt[f]; + + printf("%s %u %u %u %u %u %u %d %u %.1f %d %d\n", + srv_node->key, srv->st_cnt[1], srv->st_cnt[2], + srv->st_cnt[3], srv->st_cnt[4], srv->st_cnt[5], srv->st_cnt[0], + tot_rq, + srv->nb_ok, (double)srv->nb_ok * 100.0 / (tot_rq?tot_rq:1), + (int)(srv->cum_ct / (srv->nb_ct?srv->nb_ct:1)), (int)(srv->cum_rt / (srv->nb_rt?srv->nb_rt:1))); + srv_node = ebmb_next(srv_node); + lines_out++; + if (lines_max >= 0 && lines_out >= lines_max) + break; + } + } + else if (filter & (FILT_COUNT_TERM_CODES|FILT_COUNT_COOK_CODES)) { + /* output all statuses in the form of <code> <occurrences> */ + n = eb32_first(&timers[0]); + while (n) { + t = container_of(n, struct timer, node); + printf("%c%c %u\n", (n->key >> 8), (n->key) & 255, t->count); + lines_out++; + if (lines_max >= 0 && lines_out >= lines_max) + break; + n = eb32_next(n); + } + } + else if (filter & (FILT_COUNT_URL_ANY|FILT_COUNT_IP_COUNT)) { + struct eb_node *node, *next; + + if (!(filter & FILT_COUNT_URL_ONLY)) { + /* we have to sort on another criterion. We'll use timers[1] for the + * destination tree. + */ + + timers[1] = EB_ROOT; /* reconfigure to accept duplicates */ + for (node = eb_first(&timers[0]); node; node = next) { + next = eb_next(node); + eb_delete(node); + + ustat = container_of(node, struct url_stat, node.url.node); + + if (filter & (FILT_COUNT_URL_COUNT|FILT_COUNT_IP_COUNT)) + ustat->node.val.key = ustat->nb_req; + else if (filter & FILT_COUNT_URL_ERR) + ustat->node.val.key = ustat->nb_err; + else if (filter & FILT_COUNT_URL_TTOT) + ustat->node.val.key = ustat->total_time; + else if (filter & FILT_COUNT_URL_TAVG) + ustat->node.val.key = ustat->nb_req ? ustat->total_time / ustat->nb_req : 0; + else if (filter & FILT_COUNT_URL_TTOTO) + ustat->node.val.key = ustat->total_time_ok; + else if (filter & FILT_COUNT_URL_TAVGO) + ustat->node.val.key = (ustat->nb_req - ustat->nb_err) ? ustat->total_time_ok / (ustat->nb_req - ustat->nb_err) : 0; + else if (filter & FILT_COUNT_URL_BAVG) + ustat->node.val.key = ustat->nb_req ? ustat->total_bytes_sent / ustat->nb_req : 0; + else if (filter & FILT_COUNT_URL_BTOT) + ustat->node.val.key = ustat->total_bytes_sent; + else + ustat->node.val.key = 0; + + eb64_insert(&timers[1], &ustat->node.val); + } + /* switch trees */ + timers[0] = timers[1]; + } + + if (FILT_COUNT_IP_COUNT) + printf("#req err ttot tavg oktot okavg bavg btot src\n"); + else + printf("#req err ttot tavg oktot okavg bavg btot url\n"); + + /* scan the tree in its reverse sorting order */ + node = eb_last(&timers[0]); + while (node) { + ustat = container_of(node, struct url_stat, node.url.node); + printf("%u %u %llu %llu %llu %llu %llu %llu %s\n", + ustat->nb_req, + ustat->nb_err, + ustat->total_time, + ustat->nb_req ? ustat->total_time / ustat->nb_req : 0, + ustat->total_time_ok, + (ustat->nb_req - ustat->nb_err) ? ustat->total_time_ok / (ustat->nb_req - ustat->nb_err) : 0, + ustat->nb_req ? ustat->total_bytes_sent / ustat->nb_req : 0, + ustat->total_bytes_sent, + ustat->url); + + node = eb_prev(node); + lines_out++; + if (lines_max >= 0 && lines_out >= lines_max) + break; + } + } + + empty: + if (!(filter & FILT_QUIET)) + fprintf(stderr, "%d lines in, %d lines out, %d parsing errors\n", + linenum, lines_out, parse_err); + exit(0); +} + +void filter_output_line(const char *accept_field, const char *time_field, struct timer **tptr) +{ + puts(line); + lines_out++; +} + +void filter_extract_capture(const char *accept_field, const char *time_field, unsigned int block, unsigned int field) +{ + const char *e, *f; + + if (time_field) + e = field_start(time_field, METH_FIELD - TIME_FIELD + 1); + else + e = field_start(accept_field, METH_FIELD - ACCEPT_FIELD + 1); + + while (block-- > 0) { + /* Scan until the start of a capture block ('{') until the URL ('"'). */ + while ((*e != '"' && *e != '{') && *e) { + /* Note: some syslog servers escape quotes ! */ + if (*e == '\\' && e[1] == '"') + break; + + e = field_start(e, 2); + } + + if (unlikely(!*e)) { + truncated_line(linenum, line); + return; + } + + /* We reached the URL, no more captures will follow. */ + if (*e != '{') { + puts(""); + lines_out++; + return; + } + + /* e points the the opening brace of the capture block. */ + + e++; + } + + /* We are in the first field of the selected capture block. */ + + while (--field > 0) { + while ((*e != '|' && *e != '}') && *e) + e++; + + if (unlikely(!*e)) { + truncated_line(linenum, line); + return; + } + + if (*e != '|') { + puts(""); + lines_out++; + return; + } + + /* e points to the pipe. */ + + e++; + } + + f = e; + + while ((*f != '|' && *f != '}') && *f) + f++; + + if (unlikely(!*f)) { + truncated_line(linenum, line); + return; + } + + fwrite(e, f - e, 1, stdout); + putchar('\n'); + lines_out++; +} + +void filter_accept_holes(const char *accept_field, const char *time_field, struct timer **tptr) +{ + struct timer *t2; + int val; + + val = convert_date(accept_field); + if (unlikely(val < 0)) { + truncated_line(linenum, line); + return; + } + + t2 = insert_value(&timers[0], tptr, val); + t2->count++; + return; +} + +void filter_count_status(const char *accept_field, const char *time_field, struct timer **tptr) +{ + struct timer *t2; + const char *b; + int val; + + if (time_field) + b = field_start(time_field, STATUS_FIELD - TIME_FIELD + 1); + else + b = field_start(accept_field, STATUS_FIELD - ACCEPT_FIELD + 1); + + if (unlikely(!*b)) { + truncated_line(linenum, line); + return; + } + + val = str2ic(b); + + t2 = insert_value(&timers[0], tptr, val); + t2->count++; +} + +void filter_count_cook_codes(const char *accept_field, const char *time_field, struct timer **tptr) +{ + struct timer *t2; + const char *b; + int val; + + if (time_field) + b = field_start(time_field, TERM_CODES_FIELD - TIME_FIELD + 1); + else + b = field_start(accept_field, TERM_CODES_FIELD - ACCEPT_FIELD + 1); + + if (unlikely(!*b)) { + truncated_line(linenum, line); + return; + } + + val = 256 * b[2] + b[3]; + + t2 = insert_value(&timers[0], tptr, val); + t2->count++; +} + +void filter_count_term_codes(const char *accept_field, const char *time_field, struct timer **tptr) +{ + struct timer *t2; + const char *b; + int val; + + if (time_field) + b = field_start(time_field, TERM_CODES_FIELD - TIME_FIELD + 1); + else + b = field_start(accept_field, TERM_CODES_FIELD - ACCEPT_FIELD + 1); + + if (unlikely(!*b)) { + truncated_line(linenum, line); + return; + } + + val = 256 * b[0] + b[1]; + + t2 = insert_value(&timers[0], tptr, val); + t2->count++; +} + +void filter_count_srv_status(const char *accept_field, const char *time_field, struct timer **tptr) +{ + const char *b, *e, *p; + int f, err, array[5]; + struct ebmb_node *srv_node; + struct srv_st *srv; + int val; + + /* the server field is before the status field, so let's + * parse them in the proper order. + */ + b = field_start(accept_field, SERVER_FIELD - ACCEPT_FIELD + 1); + if (unlikely(!*b)) { + truncated_line(linenum, line); + return; + } + + e = field_stop(b + 1); /* we have the server name in [b]..[e-1] */ + + /* the chance that a server name already exists is extremely high, + * so let's perform a normal lookup first. + */ + srv_node = ebst_lookup_len(&timers[0], b, e - b); + srv = container_of(srv_node, struct srv_st, node); + + if (!srv_node) { + /* server not yet in the tree, let's create it */ + srv = (void *)calloc(1, sizeof(struct srv_st) + e - b + 1); + srv_node = &srv->node; + memcpy(&srv_node->key, b, e - b); + srv_node->key[e - b] = '\0'; + ebst_insert(&timers[0], srv_node); + } + + /* let's collect the connect and response times */ + if (!time_field) { + time_field = field_start(e, TIME_FIELD - SERVER_FIELD); + if (unlikely(!*time_field)) { + truncated_line(linenum, line); + return; + } + } + + e = field_stop(time_field + 1); + /* we have field TIME_FIELD in [time_field]..[e-1] */ + + p = time_field; + err = 0; + f = 0; + while (!SEP(*p)) { + array[f] = str2ic(p); + if (array[f] < 0) { + array[f] = -1; + err = 1; + } + if (++f == 5) + break; + SKIP_CHAR(p, '/'); + } + + if (unlikely(f < 5)){ + parse_err++; + return; + } + + /* OK we have our timers in array[2,3] */ + if (!err) + srv->nb_ok++; + + if (array[2] >= 0) { + srv->cum_ct += array[2]; + srv->nb_ct++; + } + + if (array[3] >= 0) { + srv->cum_rt += array[3]; + srv->nb_rt++; + } + + /* we're interested in the 5 HTTP status classes (1xx ... 5xx), and + * the invalid ones which will be reported as 0. + */ + b = field_start(e, STATUS_FIELD - TIME_FIELD); + if (unlikely(!*b)) { + truncated_line(linenum, line); + return; + } + + val = 0; + if (*b >= '1' && *b <= '5') + val = *b - '0'; + + srv->st_cnt[val]++; +} + +void filter_count_url(const char *accept_field, const char *time_field, struct timer **tptr) +{ + struct url_stat *ustat = NULL; + struct ebpt_node *ebpt_old; + const char *b, *e; + int f, err, array[5]; + int val; + + /* let's collect the response time */ + if (!time_field) { + time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1); // avg 115 ns per line + if (unlikely(!*time_field)) { + truncated_line(linenum, line); + return; + } + } + + /* we have the field TIME_FIELD starting at <time_field>. We'll + * parse the 5 timers to detect errors, it takes avg 55 ns per line. + */ + e = time_field; err = 0; f = 0; + while (!SEP(*e)) { + array[f] = str2ic(e); + if (array[f] < 0) { + array[f] = -1; + err = 1; + } + if (++f == 5) + break; + SKIP_CHAR(e, '/'); + } + if (f < 5) { + parse_err++; + return; + } + + /* OK we have our timers in array[3], and err is >0 if at + * least one -1 was seen. <e> points to the first char of + * the last timer. Let's prepare a new node with that. + */ + if (unlikely(!ustat)) + ustat = calloc(1, sizeof(*ustat)); + + ustat->nb_err = err; + ustat->nb_req = 1; + + /* use array[4] = total time in case of error */ + ustat->total_time = (array[3] >= 0) ? array[3] : array[4]; + ustat->total_time_ok = (array[3] >= 0) ? array[3] : 0; + + e = field_start(e, BYTES_SENT_FIELD - TIME_FIELD + 1); + val = str2ic(e); + ustat->total_bytes_sent = val; + + /* the line may be truncated because of a bad request or anything like this, + * without a method. Also, if it does not begin with an quote, let's skip to + * the next field because it's a capture. Let's fall back to the "method" itself + * if there's nothing else. + */ + e = field_start(e, METH_FIELD - BYTES_SENT_FIELD + 1); + while (*e != '"' && *e) { + /* Note: some syslog servers escape quotes ! */ + if (*e == '\\' && e[1] == '"') + break; + e = field_start(e, 2); + } + + if (unlikely(!*e)) { + truncated_line(linenum, line); + free(ustat); + return; + } + + b = field_start(e, URL_FIELD - METH_FIELD + 1); // avg 40 ns per line + if (!*b) + b = e; + + /* stop at end of field or first ';' or '?', takes avg 64 ns per line */ + e = b; + do { + if (*e == ' '|| + (!(filter2 & FILT2_PRESERVE_QUERY) && (*e == '?' || *e == ';'))) { + *(char *)e = 0; + break; + } + e++; + } while (*e); + + /* now instead of copying the URL for a simple lookup, we'll link + * to it from the node we're trying to insert. If it returns a + * different value, it was already there. Otherwise we just have + * to dynamically realloc an entry using strdup(). + */ + ustat->node.url.key = (char *)b; + ebpt_old = ebis_insert(&timers[0], &ustat->node.url); + + if (ebpt_old != &ustat->node.url) { + struct url_stat *ustat_old; + /* node was already there, let's update previous one */ + ustat_old = container_of(ebpt_old, struct url_stat, node.url); + ustat_old->nb_req ++; + ustat_old->nb_err += ustat->nb_err; + ustat_old->total_time += ustat->total_time; + ustat_old->total_time_ok += ustat->total_time_ok; + ustat_old->total_bytes_sent += ustat->total_bytes_sent; + } else { + ustat->url = ustat->node.url.key = strdup(ustat->node.url.key); + ustat = NULL; /* node was used */ + } +} + +void filter_count_ip(const char *source_field, const char *accept_field, const char *time_field, struct timer **tptr) +{ + struct url_stat *ustat = NULL; + struct ebpt_node *ebpt_old; + const char *b, *e; + int f, err, array[5]; + int val; + + /* let's collect the response time */ + if (!time_field) { + time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1); // avg 115 ns per line + if (unlikely(!*time_field)) { + truncated_line(linenum, line); + return; + } + } + + /* we have the field TIME_FIELD starting at <time_field>. We'll + * parse the 5 timers to detect errors, it takes avg 55 ns per line. + */ + e = time_field; err = 0; f = 0; + while (!SEP(*e)) { + if (f == 0 || f == 4) { + array[f] = str2ic(e); + if (array[f] < 0) { + array[f] = -1; + err = 1; + } + } + if (++f == 5) + break; + SKIP_CHAR(e, '/'); + } + if (f < 5) { + parse_err++; + return; + } + + /* OK we have our timers in array[0], and err is >0 if at + * least one -1 was seen. <e> points to the first char of + * the last timer. Let's prepare a new node with that. + */ + if (unlikely(!ustat)) + ustat = calloc(1, sizeof(*ustat)); + + ustat->nb_err = err; + ustat->nb_req = 1; + + /* use array[4] = total time in case of error */ + ustat->total_time = (array[0] >= 0) ? array[0] : array[4]; + ustat->total_time_ok = (array[0] >= 0) ? array[0] : 0; + + e = field_start(e, BYTES_SENT_FIELD - TIME_FIELD + 1); + val = str2ic(e); + ustat->total_bytes_sent = val; + + /* the source might be IPv4 or IPv6, so we always strip the port by + * removing the last colon. + */ + b = source_field; + e = field_stop(b + 1); + while (e > b && e[-1] != ':') + e--; + *(char *)(e - 1) = '\0'; + + /* now instead of copying the src for a simple lookup, we'll link + * to it from the node we're trying to insert. If it returns a + * different value, it was already there. Otherwise we just have + * to dynamically realloc an entry using strdup(). We're using the + * <url> field of the node to store the source address. + */ + ustat->node.url.key = (char *)b; + ebpt_old = ebis_insert(&timers[0], &ustat->node.url); + + if (ebpt_old != &ustat->node.url) { + struct url_stat *ustat_old; + /* node was already there, let's update previous one */ + ustat_old = container_of(ebpt_old, struct url_stat, node.url); + ustat_old->nb_req ++; + ustat_old->nb_err += ustat->nb_err; + ustat_old->total_time += ustat->total_time; + ustat_old->total_time_ok += ustat->total_time_ok; + ustat_old->total_bytes_sent += ustat->total_bytes_sent; + } else { + ustat->url = ustat->node.url.key = strdup(ustat->node.url.key); + ustat = NULL; /* node was used */ + } +} + +void filter_graphs(const char *accept_field, const char *time_field, struct timer **tptr) +{ + struct timer *t2; + const char *p; + int f, err, array[5]; + + if (!time_field) { + time_field = field_start(accept_field, TIME_FIELD - ACCEPT_FIELD + 1); + if (unlikely(!*time_field)) { + truncated_line(linenum, line); + return; + } + } + + field_stop(time_field + 1); + /* we have field TIME_FIELD in [time_field]..[e-1] */ + + p = time_field; + err = 0; + f = 0; + while (!SEP(*p)) { + array[f] = str2ic(p); + if (array[f] < 0) { + array[f] = -1; + err = 1; + } + if (++f == 5) + break; + SKIP_CHAR(p, '/'); + } + + if (unlikely(f < 5)) { + parse_err++; + return; + } + + /* if we find at least one negative time, we count one error + * with a time equal to the total session time. This will + * emphasize quantum timing effects associated to known + * timeouts. Note that on some buggy machines, it is possible + * that the total time is negative, hence the reason to reset + * it. + */ + + if (filter & FILT_GRAPH_TIMERS) { + if (err) { + if (array[4] < 0) + array[4] = -1; + t2 = insert_timer(&timers[0], tptr, array[4]); // total time + t2->count++; + } else { + int v; + + t2 = insert_timer(&timers[1], tptr, array[0]); t2->count++; // req + t2 = insert_timer(&timers[2], tptr, array[2]); t2->count++; // conn + t2 = insert_timer(&timers[3], tptr, array[3]); t2->count++; // resp + + v = array[4] - array[0] - array[1] - array[2] - array[3]; // data time + if (v < 0 && !(filter & FILT_QUIET)) + fprintf(stderr, "ERR: %s (%d %d %d %d %d => %d)\n", + line, array[0], array[1], array[2], array[3], array[4], v); + t2 = insert_timer(&timers[4], tptr, v); t2->count++; + lines_out++; + } + } else { /* percentile */ + if (err) { + if (array[4] < 0) + array[4] = -1; + t2 = insert_value(&timers[0], tptr, array[4]); // total time + t2->count++; + } else { + int v; + + t2 = insert_value(&timers[1], tptr, array[0]); t2->count++; // req + t2 = insert_value(&timers[2], tptr, array[2]); t2->count++; // conn + t2 = insert_value(&timers[3], tptr, array[3]); t2->count++; // resp + + v = array[4] - array[0] - array[1] - array[2] - array[3]; // data time + if (v < 0 && !(filter & FILT_QUIET)) + fprintf(stderr, "ERR: %s (%d %d %d %d %d => %d)\n", + line, array[0], array[1], array[2], array[3], array[4], v); + t2 = insert_value(&timers[4], tptr, v); t2->count++; + lines_out++; + } + } +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ |