Browse code

refactor, multiple clamd support. documentation, and detailview to come later.

git-svn: trunk@4383

Török Edvin authored on 2008/11/12 21:06:15
Showing 4 changed files
... ...
@@ -1,3 +1,9 @@
1
+Wed Nov 12 13:06:33 EET 2008 (edwin)
2
+------------------------------------
3
+ * contrib/clamdtop/Makefile, contrib/clamdtop/TODO,
4
+ contrib/clamdtop/clamdtop.c: refactor, multiple clamd support.
5
+ documentation, and detailview to come later.
6
+
1 7
 Wed Nov 12 11:21:45 CET 2008 (acab)
2 8
 -----------------------------------
3 9
  * docs/man/clamav-milter.8.in: avoid hyphen-used-as-minus-sign warnings
... ...
@@ -1,8 +1,7 @@
1
-CFLAGS=-O2 -g -Wall -W -Wshadow
1
+CFLAGS=-g -Wall -W -Wshadow -Wformat=2 -O2
2 2
 CC=gcc
3 3
 OBJS=clamdtop.o
4
-LDFLAGS=
5
-CPPFLAGS=
4
+CPPFLAGS=-D_FORTIFY_SOURCE=2
6 5
 
7 6
 ifeq ($(MSYSTEM),MINGW32)
8 7
 LIBS=-lpdcurses -lws2_32
... ...
@@ -11,7 +10,7 @@ LIBS=-lncurses
11 11
 endif
12 12
 
13 13
 clamdtop: $(OBJS) Makefile
14
-	$(CC) $(CPPFLAGS) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS) $(LIBS)
14
+	$(CC) $(OBJS) -o $@ $(LDFLAGS) $(LIBS)
15 15
 
16 16
 clean:
17 17
 	rm -f clamdtop $(OBJS)
... ...
@@ -1,12 +1,9 @@
1
-monitor more than one remote clamd
1
+select one clamd when monitoring more, and show details for that only
2
+better handling for connection errors (inform user within an ncurses window)
2 3
 write a manpage
3 4
 consider writing a GUI using Tk 8.5 (version 8.5 has much nicer widgets than 8.4)
4 5
 integrate into toplevel clamav, and build if ncurses is available (optional)
5 6
 figure out minimum version of ncurses required, I used 5.5 during development.
6
-show mempool statistics too.
7
-reconnect after a DB reload (we get a broken pipe)
8 7
 describe what the various fields mean, like what live/idle threads mean and why
9 8
 describe why the multiscan view is split
10 9
 explain what queue means, and why it is high with multiscan
11
-figure a solution to represent multiple multiscan commands (though why would
12
-somebody run 2 in parallel?)
... ...
@@ -19,13 +19,17 @@
19 19
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 20
  *  MA 02110-1301, USA.
21 21
  */
22
+#define _GNU_SOURCE
23
+#define __EXTENSIONS
24
+#define GCC_PRINTF
25
+#define GCC_SCANF
22 26
 #include <unistd.h>
23 27
 #include <stdio.h>
24 28
 #include <stdlib.h>
25 29
 #include <string.h>
26 30
 #include <stdint.h>
27 31
 #include <sys/types.h>
28
-#include <ncurses.h>
32
+#include <curses.h>
29 33
 #include <time.h>
30 34
 #include <ctype.h>
31 35
 #include <signal.h>
... ...
@@ -45,6 +49,49 @@
45 45
 #include <assert.h>
46 46
 #include <errno.h>
47 47
 
48
+/* Types, prototypes and globals*/
49
+typedef struct connection {
50
+	int sd;
51
+	const char *remote;
52
+	int tcp;
53
+	struct timeval tv_conn;
54
+	char *version;
55
+} conn_t;
56
+
57
+struct global_stats {
58
+	struct task *tasks;
59
+	ssize_t n;
60
+	struct stats *all_stats;
61
+	size_t num_clamd;
62
+	conn_t *conn;
63
+};
64
+
65
+struct stats {
66
+	const char *remote;
67
+	char *engine_version;
68
+	char *db_version;
69
+	struct tm db_time;
70
+	const char *version;
71
+	uint8_t conn_hr, conn_min, conn_sec;
72
+	/* threads - primary */
73
+	unsigned prim_live, prim_idle, prim_max;
74
+	/* threads - sum */
75
+	unsigned live, idle, max;
76
+	/* queue */
77
+	unsigned biggest_queue, current_q;
78
+	double mem;/* in megabytes */
79
+	unsigned long lheapu, lmmapu, ltotalu, ltotalf, lreleasable, lpoolu, lpoolt;
80
+	unsigned pools_cnt;
81
+};
82
+
83
+static void cleanup(void);
84
+static int send_string_noreconn(conn_t *conn, const char *cmd);
85
+static void send_string(conn_t *conn, const char *cmd);
86
+static void read_version(conn_t *conn);
87
+
88
+static struct global_stats global;
89
+static int curses_inited = 1;
90
+static int maxystats=0;
48 91
 /* ---------------------- NCurses routines -----------------*/
49 92
 enum colors {
50 93
 	header_color=1,
... ...
@@ -57,8 +104,6 @@ enum colors {
57 57
 	red_color,
58 58
 };
59 59
 
60
-
61
-
62 60
 #define UPDATE_INTERVAL 2
63 61
 #define MIN_INTERVAL 1
64 62
 
... ...
@@ -69,7 +114,6 @@ enum colors {
69 69
 #define DESCR_ATTR COLOR_PAIR(descr_color)
70 70
 
71 71
 static WINDOW *header_window = NULL;
72
-static WINDOW *version_window = NULL;
73 72
 static WINDOW *stats_head_window = NULL;
74 73
 static WINDOW *stats_window = NULL;
75 74
 static WINDOW *status_bar_window = NULL;
... ...
@@ -78,8 +122,20 @@ static WINDOW *mem_window = NULL;
78 78
 static const char *status_bar_keys[10];
79 79
 static unsigned maxy=0, maxx=0;
80 80
 static char *queue_header = NULL;
81
-static const char *exit_reason = NULL;
82
-#define CMDHEAD "COMMAND        TIME QUEUED   FILE"
81
+static char *clamd_header = NULL;
82
+
83
+#define CMDHEAD " COMMAND        TIME QUEUED   FILE"
84
+#define CMDHEAD2 "NO COMMAND     TIME QUEUED   FILE"
85
+
86
+/*
87
+ * CLAMD - which local/remote clamd this is
88
+ * CONNTIM - since when we are connected (TODO: zeroed at reconnect)
89
+ * QUEUE   - no of items in queue (total)
90
+ * MAXQUEUE - max no of items in queue observed
91
+ * LIVETHR - sum of live threads
92
+ * IDLETHR - sum of idle threads
93
+ */
94
+#define SUMHEAD "NO CONNTIME LIV IDL QUEUE  MAXQ   MEM HOST           ENGINE DBVER DBTIME"
83 95
 
84 96
 static void resize(void)
85 97
 {
... ...
@@ -91,14 +147,23 @@ static void resize(void)
91 91
 	maxx = new_maxx;
92 92
 	maxy = new_maxy;
93 93
 	free(queue_header);
94
+	free(clamd_header);
94 95
 	queue_header = malloc(maxx + 1);
95
-	if(!queue_header)
96
+	clamd_header = malloc(maxx + 1);
97
+	if(!queue_header || !clamd_header) {
98
+		fprintf(stderr,"Out of memory\n");
96 99
 		exit(1);
100
+	}
101
+	strncpy(queue_header, global.num_clamd>1 ? CMDHEAD2 : CMDHEAD, maxx);
102
+	strncpy(clamd_header, SUMHEAD, maxx);
97 103
 	queue_header[maxx] = '\0';
98
-	strncpy(queue_header, CMDHEAD, maxx);
104
+	clamd_header[maxx] = '\0';
99 105
 	p = queue_header + strlen(queue_header);
100 106
 	while(p < queue_header+maxx)
101 107
 		*p++ = ' ';
108
+	p = clamd_header + strlen(clamd_header);
109
+	while(p < clamd_header+maxx)
110
+		*p++ = ' ';
102 111
 }
103 112
 
104 113
 static void rm_windows(void)
... ...
@@ -111,10 +176,6 @@ static void rm_windows(void)
111 111
 		delwin(mem_window);
112 112
 		mem_window = NULL;
113 113
 	}
114
-	if(version_window) {
115
-		delwin(version_window);
116
-		version_window = NULL;
117
-	}
118 114
 	if(stats_window) {
119 115
 		delwin(stats_window);
120 116
 		stats_window = NULL;
... ...
@@ -129,30 +190,20 @@ static void rm_windows(void)
129 129
 	}
130 130
 }
131 131
 
132
-static int normal_exit = 0;
133
-
134
-static void cleanup_ncurses(void)
135
-{
136
-	werase(status_bar_window);
137
-	wrefresh(status_bar_window);
138
-	rm_windows();
139
-	endwin();
140
-	if(!normal_exit)
141
-		printf("Abnormal program termination %s\n",
142
-				exit_reason ? exit_reason : "");
143
-}
144 132
 
145
-static void init_windows(void)
133
+static void init_windows(int num_clamd)
146 134
 {
147 135
 	resize();
148 136
 
149 137
 	rm_windows();
138
+	/* non-overlapping windows */
150 139
 	header_window = subwin(stdscr, 1, maxx, 0, 0);
151
-	version_window = subwin(stdscr, 1, maxx, 1, 0);
152
-	stats_head_window = subwin(stdscr, 5, maxx-48, 3, 0);
153
-	stats_window = subwin(stdscr, maxy-9, maxx, 7, 0);
154
-	mem_window = subwin(stdscr, 6, 48, 3, maxx-48);
155
-	status_bar_window = subwin(stdscr, 1i, maxx, maxy-1, 0);
140
+	stats_head_window = subwin(stdscr, num_clamd+1, maxx, 1, 0);
141
+	maxystats = maxy-num_clamd-3;
142
+	stats_window = subwin(stdscr, maxystats, maxx, num_clamd+2, 0);
143
+	status_bar_window = subwin(stdscr, 1, maxx, maxy-1, 0);
144
+	/* memwindow overlaps, used only in details mode */
145
+	mem_window = derwin(stats_window, 6, 48, 0, maxx-48);
156 146
 	touchwin(stdscr);
157 147
 	werase(stdscr);
158 148
 	refresh();
... ...
@@ -161,9 +212,11 @@ static void init_windows(void)
161 161
 	status_bar_keys[1] = "R - reset bar maximums";
162 162
 }
163 163
 
164
-static void init_ncurses(void)
164
+static void init_ncurses(int num_clamd)
165 165
 {
166 166
 	initscr();
167
+	curses_inited = 1;
168
+
167 169
 	start_color();
168 170
 	keypad(stdscr, TRUE);	/* enable keyboard mapping */
169 171
 	nonl();			/* tell curses not to do NL->CR/NL on output */
... ...
@@ -181,9 +234,7 @@ static void init_ncurses(void)
181 181
 	init_pair(dim_color, COLOR_GREEN, DEFAULT_COLOR);
182 182
 	init_pair(red_color, COLOR_RED, DEFAULT_COLOR);
183 183
 
184
-	atexit(cleanup_ncurses);
185
-
186
-	init_windows();
184
+	init_windows(num_clamd);
187 185
 }
188 186
 
189 187
 static void win_start(WINDOW *win, enum colors col)
... ...
@@ -208,23 +259,27 @@ static void  print_colored(WINDOW *win, const char *p)
208 208
 	}
209 209
 }
210 210
 
211
-static char *clamd_version = NULL;
212
-
213 211
 static void header(void)
214 212
 {
215 213
 	size_t i, x=0;
216 214
 	time_t t;
217 215
 
216
+
218 217
 	win_start(header_window, header_color);
219 218
 	mvwprintw(header_window, 0, 0, "  ClamdTOP version 0.1   ");
219
+/*	mvwprintw(header_window, 0, 0, "      __              ____          ");
220
+	mvwprintw(header_window, 1, 0, " ____/ /__ ___ _  ___/ / /____  ___ ");
221
+	mvwprintw(header_window, 2, 0, "/ __/ / _ `/  ' \\/ _  / __/ _ \\/ _ \\");
222
+	mvwprintw(header_window, 3, 0, "\\__/_/\\_,_/_/_/_/\\_,_/\\__/\\___/ .__/");
223
+	mvwprintw(header_window, 4, 0,"                             /_/    ");*/
220 224
 	time(&t);
221 225
 	wprintw(header_window, "%s", ctime(&t));
222 226
 	wrefresh(header_window);
223 227
 
224
-	win_start(version_window, version_color);
228
+/*	win_start(version_window, version_color);
225 229
 	mvwprintw(version_window, 0, 0, "Connected to: ");
226 230
 	print_colored(version_window, clamd_version ? clamd_version : "Unknown");
227
-	wrefresh(version_window);
231
+	wrefresh(version_window);*/
228 232
 
229 233
 	werase(status_bar_window);
230 234
 	for(i=0;i<sizeof(status_bar_keys)/sizeof(status_bar_keys[0]);i++) {
... ...
@@ -268,9 +323,75 @@ static void show_bar(WINDOW *win, size_t i, unsigned live, unsigned idle,
268 268
 	}
269 269
 }
270 270
 
271
+/* --------------------- Error handling ---------------------*/
272
+static int normal_exit = 0;
273
+static const char *exit_reason = NULL;
274
+static const char *exit_func = NULL;
275
+static unsigned exit_line = 0;
276
+
277
+static void cleanup(void)
278
+{
279
+	unsigned i;
280
+	if (curses_inited) {
281
+		werase(status_bar_window);
282
+		wrefresh(status_bar_window);
283
+		rm_windows();
284
+		endwin();
285
+	}
286
+	curses_inited = 0;
287
+	for (i=0;i<global.num_clamd;i++) {
288
+		if (global.conn[i].sd)
289
+			send_string_noreconn(&global.conn[i], "END\n");
290
+		close(global.conn[i].sd);
291
+		free(global.conn[i].version);
292
+	}
293
+	free(global.all_stats);
294
+	free(global.conn);
295
+	free(queue_header);
296
+	free(clamd_header);
297
+	if(!normal_exit) {
298
+		fprintf(stderr, "Abnormal program termination");
299
+	        if (exit_reason) fprintf(stderr, ": %s",exit_reason);
300
+		if (exit_func) fprintf(stderr, " in %s", exit_func);
301
+		if (exit_line) fprintf(stderr, " at line %u", exit_line);
302
+		fputc('\n',stderr);
303
+	}
304
+}
305
+
306
+enum exit_reason {
307
+	FAIL_INITIAL_CONN=1,
308
+	OUT_OF_MEMORY,
309
+	RECONNECT_FAIL
310
+};
311
+
312
+static void exit_program(enum exit_reason reason, const char *func, unsigned line)
313
+{
314
+	switch(reason) {
315
+		case FAIL_INITIAL_CONN:
316
+			exit_reason = "Unable to connect to all clamds";
317
+			break;
318
+		case OUT_OF_MEMORY:
319
+			exit_reason = "Out of memory";
320
+			break;
321
+		case RECONNECT_FAIL:
322
+			exit_reason = "Failed to reconnect to clamd after connection was lost";
323
+			break;
324
+		default:
325
+			exit_reason = "Unknown";
326
+			break;
327
+	}
328
+	exit_func = func;
329
+	exit_line = line;
330
+	exit(reason);
331
+}
332
+
333
+#define EXIT_PROGRAM(r) exit_program(r, __PRETTY_FUNCTION__, __LINE__);
334
+#define OOM_CHECK(p) do { if (!p) EXIT_PROGRAM(OUT_OF_MEMORY); } while (0)
335
+
271 336
 struct task {
272 337
 	char *line;
273 338
 	double tim;
339
+	int clamd_no;
274 340
 };
275 341
 
276 342
 static int tasks_compare(const void *a, const void *b)
... ...
@@ -285,21 +406,16 @@ static int tasks_compare(const void *a, const void *b)
285 285
 }
286 286
 
287 287
 /* ----------- Socket routines ----------------------- */
288
-typedef struct connection {
289
-	int sd;
290
-	const char *remote;
291
-} conn_t;
292
-
293 288
 static void print_con_error(const char *fmt, ...)
294 289
 {
295 290
 	va_list ap;
296 291
 	va_start(ap, fmt);
297
-	if (version_window) {
292
+/*	if (version_window) {
298 293
 		werase(version_window);
299 294
 		wmove(version_window, 0, 0);
300 295
 		vwprintw(version_window, fmt, ap);
301 296
 		wrefresh(version_window);
302
-	} else
297
+	} else*/
303 298
 		vfprintf(stderr, fmt, ap);
304 299
 	va_end(ap);
305 300
 }
... ...
@@ -308,6 +424,7 @@ static void print_con_error(const char *fmt, ...)
308 308
 static int make_connection(const char *soname, conn_t *conn)
309 309
 {
310 310
 	int s;
311
+	conn->tcp = 0;
311 312
 #ifdef _WIN32
312 313
     {
313 314
 #else
... ...
@@ -333,6 +450,7 @@ static int make_connection(const char *soname, conn_t *conn)
333 333
 		unsigned port = 0;
334 334
 		char *name, *pt = strdup(soname);
335 335
 		const char *host = pt;
336
+		conn->tcp=1;
336 337
 		name = strchr(pt, ':');
337 338
 		if(name) {
338 339
 			*name++ = '\0';
... ...
@@ -362,28 +480,43 @@ static int make_connection(const char *soname, conn_t *conn)
362 362
 	}
363 363
 	conn->remote = soname;
364 364
 	conn->sd = s;
365
+	gettimeofday(&conn->tv_conn, NULL);
366
+	send_string(conn, "SESSION\nVERSION\n");
367
+	read_version(conn);
365 368
 	return 0;
366 369
 }
367 370
 
368
-static void reconnect(conn_t *conn)
369
-{
370
-	print_con_error("%s: %s", conn->remote, strerror(errno));
371
-	if (make_connection(conn->remote, conn) < 0) {
372
-		print_con_error("Unable to reconnect to %s: %s", conn->remote, strerror(errno));
373
-		exit(3);
374
-	}
375
-}
371
+static void reconnect(conn_t *conn);
376 372
 
377
-static void send_string(conn_t *conn, const char *cmd)
373
+static int send_string_noreconn(conn_t *conn, const char *cmd)
378 374
 {
379 375
 	assert(cmd);
380 376
 	assert(conn && conn->sd > 0);
377
+	return send(conn->sd, cmd, strlen(cmd), 0);
378
+}
381 379
 
382
-	while(send(conn->sd, cmd, strlen(cmd), 0) == -1) {
380
+static void send_string(conn_t *conn, const char *cmd)
381
+{
382
+	while(send_string_noreconn(conn, cmd) == -1) {
383 383
 		reconnect(conn);
384 384
 	}
385 385
 }
386 386
 
387
+static int tries = 0;
388
+static void reconnect(conn_t *conn)
389
+{
390
+	if(++tries > 3) {
391
+		EXIT_PROGRAM(RECONNECT_FAIL);
392
+	}
393
+	print_con_error("%s: %s", conn->remote, strerror(errno));
394
+	if (make_connection(conn->remote, conn) < 0) {
395
+		print_con_error("Unable to reconnect to %s: %s", conn->remote, strerror(errno));
396
+		exit(3);
397
+	}
398
+	free(conn->version);
399
+	tries = 0;
400
+}
401
+
387 402
 static int recv_line(conn_t *conn, char *buf, size_t len)
388 403
 {
389 404
 	assert(len > 0);
... ...
@@ -420,11 +553,52 @@ static int recv_line(conn_t *conn, char *buf, size_t len)
420 420
 	return 1;
421 421
 }
422 422
 
423
+static void output_queue(size_t line, ssize_t max)
424
+{
425
+	ssize_t i;
426
+	struct task *tasks = global.tasks;
427
+	assert(tasks);
428
+	wattron(stats_window, COLOR_PAIR(queue_header_color));
429
+	mvwprintw(stats_window, line++, 0, "%s", queue_header);
430
+	wattroff(stats_window, COLOR_PAIR(queue_header_color));
431
+	if (max >= global.n)
432
+		max = global.n;
433
+	else
434
+		--max;
435
+	if (max < 0) max = 0;
436
+	for(i=0;i<max;i++) {
437
+		char *cmde = strchr(tasks[i].line, ' ');
438
+		if(cmde) {
439
+			char cmd[16];
440
+			const char *filstart = strchr(cmde + 1, ' ');
441
+			strncpy(cmd, tasks[i].line, sizeof(cmd)-1);
442
+			cmd[15]='\0';
443
+			if (tasks[i].line+15 > cmde)
444
+				cmd[cmde - tasks[i].line] = '\0';
445
+			if(filstart) {
446
+				++filstart;
447
+				if (global.num_clamd>1)
448
+					mvwprintw(stats_window, line + i, 0, "%2u %s", tasks[i].clamd_no, cmd + 1);
449
+				else
450
+					mvwprintw(stats_window, line + i, 0, " %s", cmd + 1);
451
+				mvwprintw(stats_window, line + i, 15, "%10.03fs", tasks[i].tim);
452
+				mvwprintw(stats_window, line + i, 30, "%s",filstart);
453
+			}
454
+		}
455
+	}
456
+	if (max < global.n) {
457
+		/* in summary mode we can only show a max amount of tasks */
458
+		wattron(stats_window, A_DIM | COLOR_PAIR(header_color));
459
+		mvwprintw(stats_window, line+i, 0, "*** %u more task(s) not shown ***", (unsigned)(global.n - max));
460
+		wattroff(stats_window, A_DIM | COLOR_PAIR(header_color));
461
+	}
462
+}
463
+
423 464
 /* ---------------------- stats parsing routines ------------------- */
424
-static size_t parse_queue(conn_t *conn, size_t line, char* buf, size_t len)
465
+
466
+
467
+static void parse_queue(conn_t *conn, char* buf, size_t len, unsigned idx)
425 468
 {
426
-	struct task *tasks = NULL;
427
-	size_t n = 0, i;
428 469
 	do {
429 470
 		double tim;
430 471
 		const char *t = strchr(buf, ' ');
... ...
@@ -432,220 +606,407 @@ static size_t parse_queue(conn_t *conn, size_t line, char* buf, size_t len)
432 432
 			continue;
433 433
 		if(sscanf(t,"%lf", &tim) != 1)
434 434
 			continue;
435
-		++n;
436
-		tasks = realloc(tasks, sizeof(*tasks)*n);
437
-		if(!tasks) {
435
+		++global.n;
436
+		global.tasks = realloc(global.tasks, sizeof(*global.tasks)*global.n);
437
+		if(!global.tasks) {
438
+			fprintf(stderr, "Out of memory\n");
438 439
 			/* OOM */
439 440
 			exit(1);
440 441
 		}
441
-		tasks[n-1].line = strdup(buf);
442
-		if(!tasks[n-1].line)
442
+		global.tasks[global.n-1].line = strdup(buf);
443
+		if(!global.tasks[global.n-1].line) {
444
+			fprintf(stderr, "Out of memory\n");
443 445
 			exit(1);
444
-		tasks[n-1].tim  = tim;
445
-	} while (recv_line(conn, buf, len) && buf[0] == '\t' && strcmp("END\n", buf) != 0);
446
-	qsort(tasks, n, sizeof(*tasks), tasks_compare);
447
-	wattron(stats_window, COLOR_PAIR(queue_header_color));
448
-	mvwprintw(stats_window, line++, 0, queue_header);
449
-	wattroff(stats_window, COLOR_PAIR(queue_header_color));
450
-	for(i=0;i<n;i++) {
451
-		char *cmde = strchr(tasks[i].line, ' ');
452
-		if(cmde) {
453
-			const char *filstart = strchr(cmde + 1, ' ');
454
-			*cmde = '\0';
455
-			if(filstart) {
456
-				++filstart;
457
-				mvwprintw(stats_window, line + i, 0, " %s", tasks[i].line + 1);
458
-				mvwprintw(stats_window, line + i, 15, "%10.03fs", tasks[i].tim);
459
-				mvwprintw(stats_window, line + i, 30, "%s",filstart);
460
-			}
461 446
 		}
462
-		free(tasks[i].line);
463
-	}
464
-	free(tasks);
465
-	return line + i + 1;
447
+		global.tasks[global.n-1].tim  = tim;
448
+		global.tasks[global.n-1].clamd_no = idx;
449
+	} while (recv_line(conn, buf, len) && buf[0] == '\t' && strcmp("END\n", buf) != 0);
466 450
 }
467 451
 
468 452
 static unsigned biggest_queue = 1, biggest_mem = 0;
469 453
 
470
-static void output_memstats(const char* line)
454
+static void output_memstats(struct stats *stats)
471 455
 {
472 456
 	char buf[128];
457
+	unsigned long totalmem;
473 458
 	int blink = 0;
474
-	double heapu, mmapu, totalu, totalf, releasable, pools_used, pools_total;
475
-	unsigned pools_cnt;
476
-	unsigned long lheapu, lmmapu, ltotalu, ltotalf, lreleasable, totalmem, lpoolu, lpoolt;
477 459
 
478
-	if(sscanf(line, " heap %lfM mmap %lfM used %lfM free %lfM releasable %lfM pools %u pools_used %lfM pools_total %lfM",
479
-			&heapu, &mmapu, &totalu, &totalf, &releasable, &pools_cnt, &pools_used, &pools_total) != 8)
480
-		return;
481
-	lheapu = heapu*1000;
482
-	lmmapu = mmapu*1000;
483
-	ltotalu = totalu*1000;
484
-	ltotalf = totalf*1000;
485
-	lreleasable = releasable*1000;
486
-	lpoolu = pools_used*1000;
487
-	lpoolt = pools_total*1000;
488 460
 	werase(mem_window);
489 461
 	box(mem_window, 0, 0);
490 462
 
491 463
 	snprintf(buf, sizeof(buf),"heap %4luM mmap %4luM releasable %3luM",
492
-			lheapu/1024, lmmapu/1024, lreleasable/1024);
464
+			stats->lheapu/1024, stats->lmmapu/1024, stats->lreleasable/1024);
493 465
 	mvwprintw(mem_window, 1, 1, "Memory: ");
494 466
 	print_colored(mem_window, buf);
495 467
 
496 468
 	mvwprintw(mem_window, 2, 1, "Malloc: ");
497 469
 	snprintf(buf, sizeof(buf),"used %4luM free %4luM total     %4luM",
498
-			ltotalu/1024, ltotalf/1024, (ltotalu+ltotalf)/1024);
470
+			stats->ltotalu/1024, stats->ltotalf/1024, (stats->ltotalu+stats->ltotalf)/1024);
499 471
 	print_colored(mem_window, buf);
500 472
 
501 473
 	mvwprintw(mem_window, 3, 1, "Mempool: ");
502 474
 	snprintf(buf, sizeof(buf), "count  %u  used %4luM total     %4luM",
503
-			pools_cnt, lpoolu/1024, lpoolt/1024);
475
+			stats->pools_cnt, stats->lpoolu/1024, stats->lpoolt/1024);
504 476
 	print_colored(mem_window, buf);
505 477
 
506
-	totalmem = lheapu + lmmapu + lpoolt;
478
+	totalmem = stats->lheapu + stats->lmmapu + stats->lpoolt;
507 479
 	if(totalmem > biggest_mem) {
508 480
 		biggest_mem = totalmem;
509 481
 		blink = 1;
510 482
 	}
511
-	show_bar(mem_window, 4, totalmem, lmmapu + lreleasable + lpoolt - lpoolu,
483
+	show_bar(mem_window, 4, totalmem, stats->lmmapu + stats->lreleasable + stats->lpoolt - stats->lpoolu,
512 484
 			biggest_mem, blink);
513 485
 	wrefresh(mem_window);
514 486
 }
515 487
 
516
-static struct timeval tv_conn;
488
+static void parse_memstats(const char *line, struct stats *stats)
489
+{
490
+	double heapu, mmapu, totalu, totalf, releasable, pools_used, pools_total;
491
+
492
+	if(sscanf(line, " heap %lfM mmap %lfM used %lfM free %lfM releasable %lfM pools %u pools_used %lfM pools_total %lfM",
493
+			&heapu, &mmapu, &totalu, &totalf, &releasable, &stats->pools_cnt, &pools_used, &pools_total) != 8)
494
+		return;
495
+	stats->lheapu = heapu*1000;
496
+	stats->lmmapu = mmapu*1000;
497
+	stats->ltotalu = totalu*1000;
498
+	stats->ltotalf = totalf*1000;
499
+	stats->lreleasable = releasable*1000;
500
+	stats->lpoolu = pools_used*1000;
501
+	stats->lpoolt = pools_total*1000;
502
+	stats->mem = heapu + mmapu + pools_total;
503
+}
517 504
 
518
-static void parse_stats(conn_t *conn)
505
+static int show_detail(int idx)
519 506
 {
507
+	if (global.num_clamd == 1) {
508
+		assert(idx == 0);
509
+		return 1;
510
+	}
511
+	return 0;
512
+}
513
+
514
+static int has_detail()
515
+{
516
+	return global.num_clamd == 1;
517
+}
518
+
519
+static int output_stats(struct stats *stats, unsigned idx)
520
+{
521
+	char buf[128];
522
+	char timbuf[15];
523
+	int blink = 0;
524
+	size_t i= 0;
525
+	char mem[6];
520 526
 	WINDOW *win = stats_head_window;
527
+
528
+	if (stats->mem == -1)
529
+		strcpy(mem, "N/A");
530
+	else {
531
+		char c;
532
+		double s;
533
+		if (stats->mem > 999.0)  {
534
+			c = 'G';
535
+			s = stats->mem / 1024.0;
536
+		} else {
537
+			c = 'M';
538
+			s = stats->mem;
539
+		}
540
+		snprintf(mem, sizeof(mem), "%7.3f", s);
541
+		i = 4;
542
+		if (mem[i-1] == '.') i--;
543
+		mem[i++] = c;
544
+		mem[i] = '\0';
545
+	}
546
+	i = idx+1;
547
+
548
+	if (!stats->db_time.tm_year)
549
+		strcpy(timbuf,"N/A");
550
+	else
551
+		snprintf(timbuf, sizeof(timbuf), "%04u-%02u-%02u %02uh",
552
+				1900 + stats->db_time.tm_year,
553
+				stats->db_time.tm_mon,
554
+				stats->db_time.tm_mday,
555
+				stats->db_time.tm_hour);
556
+
557
+	mvwprintw(win, i++, 0,"%2u %02u:%02u:%02u %3u %3u %5u %5u %5s %-14s %-6s %5s %s", idx+1,  stats->conn_hr, stats->conn_min, stats->conn_sec,
558
+			stats->live, stats->idle,
559
+			stats->current_q, stats->biggest_queue,
560
+			mem,
561
+			stats->remote, stats->engine_version, stats->db_version, timbuf);
562
+	win = stats_window;
563
+	i = 0;
564
+	if (show_detail(idx)) {
565
+		mvwprintw(win, i++, 0, "Primary threads: ");
566
+		snprintf(buf, sizeof(buf), "live %3u idle %3u max %3u", stats->prim_live, stats->prim_idle, stats->prim_max);
567
+		print_colored(win, buf);
568
+		show_bar(win, i++, stats->prim_live, stats->prim_idle, stats->prim_max, 0);
569
+
570
+		mvwprintw(win, i++, 0, "All     threads: ");
571
+		snprintf(buf, sizeof(buf), "live %3u idle %3u max %3u", stats->live, stats->idle, stats->max);
572
+		print_colored(win, buf);
573
+		show_bar(win, i++, stats->live, stats->idle, stats->max, 0);
574
+
575
+		mvwprintw(win, i++, 0, "Queue:");
576
+		snprintf(buf, sizeof(buf), "%6u items %6u max", stats->current_q, stats->biggest_queue);
577
+		print_colored(win, buf);
578
+		werase(mem_window);
579
+		output_memstats(stats);
580
+	}
581
+	blink = 0;
582
+	if(stats->current_q > stats->biggest_queue) {
583
+			stats->biggest_queue = stats->current_q;
584
+			blink = 1;
585
+	}
586
+	if (show_detail(idx)) {
587
+		show_bar(win, i++, stats->current_q, 0, biggest_queue, blink);
588
+	}
589
+	return i;
590
+}
591
+
592
+static void output_all(void)
593
+{
594
+	unsigned i, stats_line=0;
595
+	werase(stats_head_window);
596
+	werase(stats_window);
597
+	wattron(stats_head_window, COLOR_PAIR(queue_header_color));
598
+	mvwprintw(stats_head_window, 0, 0, "%s", clamd_header);
599
+	wattroff(stats_head_window, COLOR_PAIR(queue_header_color));
600
+	for (i=0;i<global.num_clamd;i++)
601
+		stats_line = output_stats(&global.all_stats[i], i);
602
+	output_queue(stats_line, maxystats - stats_line-1);
603
+	wrefresh(stats_head_window);
604
+	wrefresh(stats_window);
605
+	if (has_detail()) {
606
+		/* overlaps, must be done at the end */
607
+		wrefresh(mem_window);
608
+	}
609
+}
610
+
611
+static void parse_stats(conn_t *conn, struct stats *stats, unsigned idx)
612
+{
521 613
 	char buf[1024];
522
-	size_t i=0 ,j;
614
+	size_t j;
523 615
 	struct timeval tv;
524 616
 	unsigned conn_dt;
617
+	int primary = 0;
618
+	const char *pstart, *p;
619
+
620
+	if (conn->tcp)
621
+		stats->remote = conn->remote;
622
+	else
623
+		stats->remote = "local";
624
+
625
+	p = pstart = conn->version;
626
+	/* find digit in version */
627
+	while (*p && !isdigit(*p))
628
+		p++;
629
+	/* rewind to first space or dash */
630
+	while (p > pstart && *p && *p != ' ' && *p != '-')
631
+		p--;
632
+	if (*p) p++;
633
+	/* keep only base version, and cut -exp, and -gittags */
634
+	pstart = p;
635
+	while (*p && *p != '-' && *p != '/')
636
+		p++;
637
+
638
+	stats->engine_version = malloc(p - pstart+1);
639
+	if (!stats->engine_version) {
640
+		fprintf(stderr,"Out of memory!\n");
641
+		exit(1);
642
+	}
525 643
 
526
-	werase(stats_head_window);
527
-	werase(stats_window);
528
-	werase(mem_window);
644
+	memcpy(stats->engine_version, pstart, p-pstart);
645
+	stats->engine_version[p-pstart] = '\0';
646
+
647
+	pstart = strchr(p, '/');
648
+	if (!pstart)
649
+		stats->db_version = strdup("????");
650
+	else {
651
+		pstart++;
652
+		p = strchr(pstart, '/');
653
+		if (!p)
654
+			p = pstart + strlen(pstart);
655
+		stats->db_version = malloc(p - pstart + 1);
656
+		if (!stats->db_version) {
657
+			fprintf(stderr, "Out of memory!\n");
658
+			exit(1);
659
+		}
660
+		memcpy(stats->db_version, pstart, p-pstart);
661
+		stats->db_version[p-pstart] = '\0';
662
+		if(*p) p++;
663
+		if (!*p || !strptime(p,"%a %b  %d %H:%M:%S %Y", &stats->db_time)) {
664
+			memset(&stats->db_time, 0, sizeof(stats->db_time));
665
+		}
666
+	}
667
+	if (maxx > 61 && strlen(stats->db_version) > (maxx-61)) {
668
+		stats->db_version[maxx-61] = '\0';
669
+	}
670
+
671
+	stats->version = conn->version; /* for details view */
529 672
 	gettimeofday(&tv, NULL);
530
-	tv.tv_sec -= tv_conn.tv_sec;
531
-	tv.tv_usec -= tv_conn.tv_usec;
673
+	tv.tv_sec -= conn->tv_conn.tv_sec;
674
+	tv.tv_usec -= conn->tv_conn.tv_usec;
532 675
 	conn_dt = tv.tv_sec + tv.tv_usec/1e6;
533
-	mvwprintw(stats_head_window, i++, 0, "Connected since: %02u:%02u:%02u",
534
-			conn_dt/3600, (conn_dt/60)%60, conn_dt%60);
676
+
677
+	stats->live = stats->idle = stats->max = 0;
678
+	stats->conn_hr = conn_dt/3600;
679
+	stats->conn_min = (conn_dt/60)%60;
680
+	stats->conn_sec = conn_dt%60;
681
+
535 682
 	while(recv_line(conn, buf, sizeof(buf)) && strcmp("END\n",buf) != 0) {
536 683
 		char *val = strchr(buf, ':');
537
-		if(i >= 4 && win == stats_head_window) {
538
-			win = stats_window;
539
-			i = 0;
540
-		}
541 684
 
542 685
 		if(buf[0] == '\t') {
543
-			i = parse_queue(conn, i, buf, sizeof(buf));
686
+			parse_queue(conn, buf, sizeof(buf), idx);
544 687
 			continue;
545 688
 		} else if(val)
546 689
 			*val++ = '\0';
547 690
 		if(!strcmp("MEMSTATS", buf)) {
548
-			output_memstats(val);
691
+			parse_memstats(val, stats);
549 692
 			continue;
550 693
 		}
551 694
 		for(j=1;j<strlen(buf);j++)
552 695
 			buf[j] = tolower(buf[j]);
553
-		mvwprintw(win, i, 0, "%s", buf);
696
+	/*	mvwprintw(win, i, 0, "%s", buf);
554 697
 		if(!val) {
555 698
 			i++;
556 699
 			continue;
557 700
 		}
558 701
 		waddch(win, ':');
559 702
 		print_colored(win, val);
560
-		i++;
703
+		i++;*/
704
+		if(!strncmp("State",buf,5)) {
705
+			if(strstr(val, "PRIMARY")) {
706
+				/* primary thread pool */
707
+				primary = 1;
708
+			} else {
709
+				/* multiscan pool */
710
+				primary = 0;
711
+			}
712
+		}
561 713
 		if(!strcmp("Threads",buf)) {
562 714
 			unsigned live, idle, max;
563 715
 			if(sscanf(val, " live %u idle %u max %u", &live, &idle, &max) != 3)
564 716
 				continue;
565
-			show_bar(win, i++, live, idle, max, 0);
717
+			if (primary) {
718
+				stats->prim_live = live;
719
+				stats->prim_idle = idle;
720
+				assert(!stats->prim_max && "There can be only one primary pool!");
721
+				stats->prim_max = max;
722
+			}
723
+			stats->live += live;
724
+			stats->idle += idle;
725
+			stats->max += max;
566 726
 		} else if (!strcmp("Queue",buf)) {
567
-			int blink = 0;
568 727
 			unsigned len;
569 728
 			if(sscanf(val, "%u", &len) != 1)
570 729
 				continue;
571
-			if(len > biggest_queue) {
572
-				biggest_queue = len;
573
-				blink = 1;
574
-			}
575
-			show_bar(win, i++, len, 0, biggest_queue, blink);
730
+			stats->current_q = len;
576 731
 		}
577 732
 	}
578
-	wrefresh(stats_head_window);
579
-	wrefresh(stats_window);
580 733
 }
581 734
 
582 735
 static void read_version(conn_t *conn)
583 736
 {
584 737
 	char buf[1024];
585 738
 	if(recv_line(conn, buf, sizeof(buf))) {
586
-		clamd_version = strdup(buf);
739
+		conn->version = strdup(buf);
587 740
 	}
588 741
 }
589 742
 
590
-static conn_t conn;
591
-
592
-int main(int argc, char *argv[])
743
+/* -------------------------- Initialization ---------------- */
744
+static void setup_connections(int argc, char *argv[])
593 745
 {
594
-	int ch = 0, need_initwin=0;
595
-	fd_set rfds;
596
-	struct timeval tv_last, tv;
597
-
746
+	unsigned i;
598 747
 #ifdef _WIN32
599 748
 	WSADATA wsaData;
600 749
 	if (WSAStartup(MAKEWORD(2,2), &wsaData) != NO_ERROR) {
601 750
 		fprintf(stderr, "Error at WSAStartup(): %d\n", WSAGetLastError());
602
-		exit(1);
751
+		EXIT_PROGRAM(FAIL_INITIAL_CONN);
603 752
 	}
753
+#endif
604 754
 
605
-	if (make_connection(argc > 1 ? argv[1] : "localhost:3310", &conn) < 0)
606
-		exit(2);
755
+	memset(&global, 0, sizeof(global));
756
+	if (argc == 1) {
757
+		global.num_clamd = 1;
758
+#ifdef _WIN32
759
+		argv[1] = "localhost:3310";
607 760
 #else
608
-	/* TODO: parse clamd.conf */
609
-	if (make_connection(argc > 1 ? argv[1] : "/tmp/clamd.socket", &conn) < 0)
610
-		exit(2);
761
+		argv[1] = "/tmp/clamd.socket";
762
+#endif
763
+	} else
764
+		global.num_clamd = argc-1;
765
+	global.all_stats = calloc(global.num_clamd, sizeof(*global.all_stats));
766
+	OOM_CHECK(global.all_stats);
767
+	global.conn = calloc(global.num_clamd, sizeof(*global.conn));
768
+	OOM_CHECK(global.conn);
769
+	for (i=0;i<global.num_clamd;i++) {
770
+		if (make_connection(argv[i+1], &global.conn[i]) < 0) {
771
+			EXIT_PROGRAM(FAIL_INITIAL_CONN);
772
+		}
611 773
 
774
+	}
775
+#ifndef _WIN32
612 776
 	signal(SIGPIPE, SIG_IGN);
613 777
 #endif
778
+}
779
+
780
+static void free_global_stats(void)
781
+{
782
+	unsigned i;
783
+	for (i=0;i<global.n;i++) {
784
+		free(global.tasks[i].line);
785
+	}
786
+	for (i=0;i<global.num_clamd;i++) {
787
+		free(global.all_stats[i].engine_version);
788
+		free(global.all_stats[i].db_version);
789
+	}
790
+	free(global.tasks);
791
+	global.tasks = NULL;
792
+	global.n=0;
793
+}
794
+
795
+int main(int argc, char *argv[])
796
+{
797
+	int ch = 0;
798
+	struct timeval tv_last, tv;
799
+	unsigned i;
800
+
801
+	atexit(cleanup);
802
+	setup_connections(argc, argv);
614 803
 
615
-	gettimeofday(&tv_conn, NULL);
616
-	send_string(&conn, "SESSION\nVERSION\n");
617
-	read_version(&conn);
618
-	init_ncurses();
804
+	init_ncurses(global.num_clamd);
619 805
 
620
-	FD_ZERO(&rfds);
621
-	FD_SET(0, &rfds);
622 806
 	memset(&tv_last, 0, sizeof(tv_last));
623 807
 	do {
624 808
 		if(ch == KEY_RESIZE) {
625 809
 			resize();
626 810
 			endwin();
627 811
 			refresh();
628
-			need_initwin = 1;
812
+			init_windows(global.num_clamd);
629 813
 		}
630 814
 		if(ch == 'R') {
631 815
 			biggest_queue = 1;
632 816
 			biggest_mem = 0;
633 817
 		}
634 818
 		gettimeofday(&tv, NULL);
819
+		header();
635 820
 		if(tv.tv_sec - tv_last.tv_sec >= MIN_INTERVAL) {
636
-			if(need_initwin) {
637
-				init_windows();
638
-				need_initwin = 0;
821
+			free_global_stats();
822
+			for(i=0;i<global.num_clamd;i++) {
823
+				struct stats *stats = &global.all_stats[i];
824
+				send_string(&global.conn[i], "STATS\n");
825
+				memset(stats, 0, sizeof(*stats));
826
+				parse_stats(&global.conn[i], stats, i);
639 827
 			}
640
-			send_string(&conn, "STATS\n");
641
-			header();
642
-			parse_stats(&conn);
828
+			assert(global.tasks);
829
+			qsort(global.tasks, global.n, sizeof(*global.tasks), tasks_compare);
643 830
 			tv_last = tv;
644 831
 		}
832
+		/* always show, so that screen resizes take effect instantly*/
833
+		output_all();
645 834
 	} while(toupper(ch = getch()) != 'Q');
646
-	send_string(&conn, "END\n");
647
-	close(conn.sd);
648
-	free(clamd_version);
835
+	free_global_stats();
649 836
 	normal_exit = 1;
650 837
 	return 0;
651 838
 }