[CONTRIB] halog: report per-server status codes, errors and response times

It's sometimes very useful to be able to monitor a production status in real
time by comparing servers behaviours. Now halog is able to do this when called
with "-srv". It reports various fields for each server found in a log, including
statuses, total reqs, valid reqs, percent of valid reqs, average connection time,
average response time.
diff --git a/contrib/halog/Makefile b/contrib/halog/Makefile
index 4cf6cb5..9a1b79a 100644
--- a/contrib/halog/Makefile
+++ b/contrib/halog/Makefile
@@ -5,10 +5,10 @@
 OBJS     = halog halog64
 
 halog: halog.c fgets2.c
-	$(CC) $(OPTIMIZE) -o $@ $(INCLUDE) $(EBTREE_DIR)/ebtree.c $(EBTREE_DIR)/eb32tree.c $^
+	$(CC) $(OPTIMIZE) -o $@ $(INCLUDE) $(EBTREE_DIR)/ebtree.c $(EBTREE_DIR)/eb32tree.c $(EBTREE_DIR)/ebmbtree.c $(EBTREE_DIR)/ebsttree.c $^
 
 halog64: halog.c fgets2-64.c
-	$(CC) $(OPTIMIZE) -o $@ $(INCLUDE) $(EBTREE_DIR)/ebtree.c $(EBTREE_DIR)/eb32tree.c $^
+	$(CC) $(OPTIMIZE) -o $@ $(INCLUDE) $(EBTREE_DIR)/ebtree.c $(EBTREE_DIR)/eb32tree.c $(EBTREE_DIR)/ebmbtree.c $(EBTREE_DIR)/ebsttree.c $^
 
 clean:
 	rm -vf $(OBJS)
diff --git a/contrib/halog/halog.c b/contrib/halog/halog.c
index 81ffa82..b9da761 100644
--- a/contrib/halog/halog.c
+++ b/contrib/halog/halog.c
@@ -1,7 +1,7 @@
 /*
  * haproxy log time reporter
  *
- * Copyright 2000-2009 Willy Tarreau <w@1wt.eu>
+ * Copyright 2000-2010 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
@@ -28,8 +28,11 @@
 #include <ctype.h>
 
 #include <eb32tree.h>
+#include <ebsttree.h>
 
+#define SOURCE_FIELD 5
 #define ACCEPT_FIELD 6
+#define SERVER_FIELD 8
 #define TIME_FIELD 9
 #define STATUS_FIELD 10
 #define CONN_FIELD 15
@@ -49,6 +52,13 @@
 	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 */
+};
 
 #define FILT_COUNT_ONLY		0x01
 #define FILT_INVERT		0x02
@@ -64,6 +74,7 @@
 #define FILT_INVERT_TIME_RESP  0x400
 
 #define FILT_COUNT_STATUS      0x800
+#define FILT_COUNT_SRV_STATUS 0x1000
 
 unsigned int filter = 0;
 unsigned int filter_invert = 0;
@@ -75,7 +86,7 @@
 {
 	fprintf(stderr,
 		"%s"
-		"Usage: halog [-c] [-v] [-gt] [-pct] [-st] [-s <skip>] [-e|-E] [-rt|-RT <time>] [-ad <delay>] [-ac <count>] < file.log\n"
+		"Usage: halog [-q] [-c] [-v] [-gt] [-pct] [-st] [-srv] [-s <skip>] [-e|-E] [-rt|-RT <time>] [-ad <delay>] [-ac <count>] < file.log\n"
 		"\n",
 		msg ? msg : ""
 		);
@@ -412,6 +423,8 @@
 			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], "-o") == 0) {
 			if (output_file)
 				die("Fatal: output file name already specified.\n");
@@ -606,6 +619,104 @@
 			continue;
 		}
 
+		if (unlikely(filter & FILT_COUNT_SRV_STATUS)) {
+			char *srv_name;
+			struct ebmb_node *srv_node;
+			struct srv_st *srv;
+
+			/* first, let's ensure that the line is a traffic line (beginning
+			 * with an IP address)
+			 */
+			b = field_start(line, SOURCE_FIELD + skip_fields);
+			if (*b < '0' || *b > '9') {
+				parse_err++;
+				continue;
+			}
+
+			/* the server field is before the status field, so let's
+			 * parse them in the proper order.
+			 */
+			b = field_start(b, SERVER_FIELD - SOURCE_FIELD + 1);
+			if (!*b) {
+				truncated_line(linenum, line);
+				continue;
+			}
+
+			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 */
+			b = field_start(e, TIME_FIELD - SERVER_FIELD);
+			if (!*b) {
+				truncated_line(linenum, line);
+				continue;
+			}
+
+			e = field_stop(b + 1);
+			/* we have field TIME_FIELD in [b]..[e-1] */
+
+			p = b;
+			err = 0;
+			for (f = 0; f < 5 && *p; f++) {
+				array[f] = str2ic(p);
+				if (array[f] < 0) {
+					array[f] = -1;
+					err = 1;
+				}
+
+				SKIP_CHAR(p, '/');
+			}
+
+			if (f < 5) {
+				parse_err++;
+				continue;
+			}
+
+			/* 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 (!*b) {
+				truncated_line(linenum, line);
+				continue;
+			}
+
+			val = 0;
+			if (*b >= '1' && *b <= '5')
+				val = *b - '0';
+
+			srv->st_cnt[val]++;
+			continue;
+		}
+
 		/* all other cases mean we just want to count lines */
 		tot++;
 		if (unlikely(!(filter & FILT_COUNT_ONLY)))
@@ -732,6 +843,34 @@
 			n = eb32_next(n);
 		}
 	}
+	else if (unlikely(filter & FILT_COUNT_SRV_STATUS)) {
+		char *srv_name;
+		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 %d %d %d %d %d %d %d %d %.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);
+			tot++;
+		}
+	}
+
  empty:
 	if (!(filter & FILT_QUIET))
 		fprintf(stderr, "%d lines in, %d lines out, %d parsing errors\n",