mirror of https://github.com/Cisco-Talos/clamav
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1116 lines
29 KiB
1116 lines
29 KiB
/*
|
|
* ClamdTOP version 0.1
|
|
*
|
|
* Copyright (C) 2008 Sourcefire, Inc.
|
|
*
|
|
* Authors: Török Edvin
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301, USA.
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#define __EXTENSIONS
|
|
#define GCC_PRINTF
|
|
#define GCC_SCANF
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <sys/types.h>
|
|
#include <curses.h>
|
|
#include <time.h>
|
|
#include <ctype.h>
|
|
#include <signal.h>
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <winsock2.h>
|
|
/* this is not correct, perhaps winsock errors are not mapped on errno */
|
|
#define herror perror
|
|
#else
|
|
#include <netdb.h>
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#endif
|
|
#include <sys/time.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
|
|
/* Types, prototypes and globals*/
|
|
typedef struct connection {
|
|
int sd;
|
|
const char *remote;
|
|
int tcp;
|
|
struct timeval tv_conn;
|
|
char *version;
|
|
int line;
|
|
} conn_t;
|
|
|
|
struct global_stats {
|
|
struct task *tasks;
|
|
ssize_t n;
|
|
struct stats *all_stats;
|
|
size_t num_clamd;
|
|
conn_t *conn;
|
|
};
|
|
|
|
struct stats {
|
|
const char *remote;
|
|
char *engine_version;
|
|
char *db_version;
|
|
struct tm db_time;
|
|
const char *version;
|
|
int stats_unsupp;
|
|
uint8_t conn_hr, conn_min, conn_sec;
|
|
/* threads - primary */
|
|
unsigned prim_live, prim_idle, prim_max;
|
|
/* threads - sum */
|
|
unsigned live, idle, max;
|
|
/* queue */
|
|
unsigned biggest_queue, current_q;
|
|
double mem;/* in megabytes */
|
|
unsigned long lheapu, lmmapu, ltotalu, ltotalf, lreleasable, lpoolu, lpoolt;
|
|
unsigned pools_cnt;
|
|
};
|
|
|
|
static void cleanup(void);
|
|
static int send_string_noreconn(conn_t *conn, const char *cmd);
|
|
static void send_string(conn_t *conn, const char *cmd);
|
|
static void read_version(conn_t *conn);
|
|
|
|
enum exit_reason {
|
|
FAIL_INITIAL_CONN=1,
|
|
OUT_OF_MEMORY,
|
|
RECONNECT_FAIL,
|
|
SIGINT_REASON
|
|
};
|
|
|
|
static void exit_program(enum exit_reason reason, const char *func, unsigned line);
|
|
#define EXIT_PROGRAM(r) exit_program(r, __PRETTY_FUNCTION__, __LINE__);
|
|
#define OOM_CHECK(p) do { if (!p) EXIT_PROGRAM(OUT_OF_MEMORY); } while (0)
|
|
|
|
|
|
static struct global_stats global;
|
|
static int curses_inited = 1;
|
|
static int maxystats=0;
|
|
/* ---------------------- NCurses routines -----------------*/
|
|
enum colors {
|
|
header_color=1,
|
|
version_color,
|
|
error_color,
|
|
value_color,
|
|
descr_color,
|
|
queue_header_color,
|
|
activ_color,
|
|
dim_color,
|
|
red_color,
|
|
};
|
|
|
|
#define UPDATE_INTERVAL 2
|
|
#define MIN_INTERVAL 1
|
|
|
|
/* the default color of the terminal in ncurses */
|
|
#define DEFAULT_COLOR -1
|
|
|
|
#define VALUE_ATTR A_BOLD | COLOR_PAIR(value_color)
|
|
#define DESCR_ATTR COLOR_PAIR(descr_color)
|
|
#define ERROR_ATTR A_BOLD | COLOR_PAIR(error_color)
|
|
|
|
static WINDOW *header_window = NULL;
|
|
static WINDOW *stats_head_window = NULL;
|
|
static WINDOW *stats_window = NULL;
|
|
static WINDOW *status_bar_window = NULL;
|
|
static WINDOW *mem_window = NULL;
|
|
|
|
static const char *status_bar_keys[10];
|
|
static unsigned maxy=0, maxx=0;
|
|
static char *queue_header = NULL;
|
|
static char *clamd_header = NULL;
|
|
|
|
#define CMDHEAD " COMMAND QUEUEDSINCE FILE"
|
|
#define CMDHEAD2 " # COMMAND QUEUEDSINCE FILE"
|
|
|
|
/*
|
|
* CLAMD - which local/remote clamd this is
|
|
* CONNTIM - since when we are connected (TODO: zeroed at reconnect)
|
|
* QUEUE - no of items in queue (total)
|
|
* MAXQUEUE - max no of items in queue observed
|
|
* LIVETHR - sum of live threads
|
|
* IDLETHR - sum of idle threads
|
|
*/
|
|
#define SUMHEAD "NO CONNTIME LIV IDL QUEUE MAXQ MEM HOST ENGINE DBVER DBTIME"
|
|
|
|
static void resize(void)
|
|
{
|
|
char *p;
|
|
unsigned new_maxy, new_maxx;
|
|
getmaxyx(stdscr, new_maxy, new_maxx);
|
|
if(new_maxy == maxy && new_maxx == maxx)
|
|
return;
|
|
maxx = new_maxx;
|
|
maxy = new_maxy;
|
|
free(queue_header);
|
|
free(clamd_header);
|
|
queue_header = malloc(maxx + 1);
|
|
OOM_CHECK(queue_header);
|
|
clamd_header = malloc(maxx + 1);
|
|
OOM_CHECK(clamd_header);
|
|
strncpy(queue_header, global.num_clamd>1 ? CMDHEAD2 : CMDHEAD, maxx);
|
|
strncpy(clamd_header, SUMHEAD, maxx);
|
|
queue_header[maxx] = '\0';
|
|
clamd_header[maxx] = '\0';
|
|
p = queue_header + strlen(queue_header);
|
|
while(p < queue_header+maxx)
|
|
*p++ = ' ';
|
|
p = clamd_header + strlen(clamd_header);
|
|
while(p < clamd_header+maxx)
|
|
*p++ = ' ';
|
|
}
|
|
|
|
static void rm_windows(void)
|
|
{
|
|
if(header_window) {
|
|
delwin(header_window);
|
|
header_window = NULL;
|
|
}
|
|
if(mem_window) {
|
|
delwin(mem_window);
|
|
mem_window = NULL;
|
|
}
|
|
if(stats_window) {
|
|
delwin(stats_window);
|
|
stats_window = NULL;
|
|
}
|
|
if(stats_head_window) {
|
|
delwin(stats_head_window);
|
|
stats_head_window = NULL;
|
|
}
|
|
if(status_bar_window) {
|
|
delwin(status_bar_window);
|
|
status_bar_window = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
static void init_windows(int num_clamd)
|
|
{
|
|
resize();
|
|
|
|
rm_windows();
|
|
/* non-overlapping windows */
|
|
header_window = subwin(stdscr, 1, maxx, 0, 0);
|
|
stats_head_window = subwin(stdscr, num_clamd+1, maxx, 1, 0);
|
|
maxystats = maxy-num_clamd-3;
|
|
stats_window = subwin(stdscr, maxystats, maxx, num_clamd+2, 0);
|
|
status_bar_window = subwin(stdscr, 1, maxx, maxy-1, 0);
|
|
/* memwindow overlaps, used only in details mode */
|
|
mem_window = derwin(stats_window, 6, 41, 0, maxx-41);
|
|
touchwin(stdscr);
|
|
werase(stdscr);
|
|
refresh();
|
|
memset(status_bar_keys, 0, sizeof(status_bar_keys));
|
|
status_bar_keys[0] = "F1 - help";
|
|
status_bar_keys[1] = "Q - quit";
|
|
status_bar_keys[2] = "R - reset bar maximums";
|
|
}
|
|
|
|
static void init_ncurses(int num_clamd)
|
|
{
|
|
initscr();
|
|
curses_inited = 1;
|
|
|
|
start_color();
|
|
keypad(stdscr, TRUE); /* enable keyboard mapping */
|
|
nonl(); /* tell curses not to do NL->CR/NL on output */
|
|
halfdelay(UPDATE_INTERVAL*10); /* timeout of 2s when waiting for input*/
|
|
noecho(); /* dont echo input */
|
|
curs_set(0); /* turn off cursor */
|
|
use_default_colors();
|
|
|
|
init_pair(header_color, COLOR_BLACK, COLOR_WHITE);
|
|
init_pair(version_color, DEFAULT_COLOR, DEFAULT_COLOR);
|
|
init_pair(error_color, COLOR_WHITE, COLOR_RED);
|
|
init_pair(value_color, COLOR_GREEN, DEFAULT_COLOR);
|
|
init_pair(descr_color, COLOR_CYAN, DEFAULT_COLOR);
|
|
init_pair(queue_header_color, COLOR_BLACK, COLOR_GREEN);
|
|
init_pair(activ_color, COLOR_MAGENTA, DEFAULT_COLOR);
|
|
init_pair(dim_color, COLOR_GREEN, DEFAULT_COLOR);
|
|
init_pair(red_color, COLOR_RED, DEFAULT_COLOR);
|
|
|
|
init_windows(num_clamd);
|
|
}
|
|
|
|
static void win_start(WINDOW *win, enum colors col)
|
|
{
|
|
wattrset(win, COLOR_PAIR(col));
|
|
wbkgd(win, COLOR_PAIR(col));
|
|
werase(win);
|
|
}
|
|
|
|
|
|
static void print_colored(WINDOW *win, const char *p)
|
|
{
|
|
while(*p) {
|
|
wattron(win, DESCR_ATTR);
|
|
while(*p && !isdigit(*p))
|
|
waddch(win, *p++);
|
|
wattroff(win, DESCR_ATTR);
|
|
wattron(win, VALUE_ATTR);
|
|
while(*p && isdigit(*p))
|
|
waddch(win, *p++);
|
|
wattroff(win, VALUE_ATTR);
|
|
}
|
|
}
|
|
|
|
static void header(void)
|
|
{
|
|
size_t i, x=0;
|
|
time_t t;
|
|
|
|
|
|
win_start(header_window, header_color);
|
|
mvwprintw(header_window, 0, 0, " ClamdTOP version 0.1 ");
|
|
time(&t);
|
|
wprintw(header_window, "%s", ctime(&t));
|
|
wrefresh(header_window);
|
|
|
|
/* win_start(version_window, version_color);
|
|
mvwprintw(version_window, 0, 0, "Connected to: ");
|
|
print_colored(version_window, clamd_version ? clamd_version : "Unknown");
|
|
wrefresh(version_window);*/
|
|
|
|
werase(status_bar_window);
|
|
for(i=0;i<sizeof(status_bar_keys)/sizeof(status_bar_keys[0]);i++) {
|
|
if(!status_bar_keys[i])
|
|
continue;
|
|
wattron(status_bar_window, A_REVERSE);
|
|
mvwprintw(status_bar_window, 0, x, "%s",status_bar_keys[i]);
|
|
wattroff(status_bar_window, A_REVERSE);
|
|
x += strlen(status_bar_keys[i]) + 1;
|
|
}
|
|
wrefresh(status_bar_window);
|
|
}
|
|
|
|
static void show_bar(WINDOW *win, size_t i, unsigned live, unsigned idle,
|
|
unsigned max, int blink)
|
|
{
|
|
int y,x;
|
|
unsigned len = 39;
|
|
unsigned start = 1;
|
|
unsigned activ = max ? ((live-idle)*(len - start - 2) + (max/2)) / max : 0;
|
|
unsigned dim = max ? idle*(len - start - 2) / max : 0;
|
|
unsigned rem = len - activ - dim - start-2;
|
|
|
|
assert(activ + 2 < len && activ+dim + 2 < len && activ+dim+rem + 2 < len && "Invalid values");
|
|
mvwaddch(win, i, start, '[' | A_BOLD);
|
|
wattron(win, A_BOLD | COLOR_PAIR(activ_color));
|
|
for(i=0;i<activ;i++)
|
|
waddch(win, '|');
|
|
wattroff(win, A_BOLD | COLOR_PAIR(activ_color));
|
|
wattron(win, A_DIM | COLOR_PAIR(dim_color));
|
|
for(i=0;i<dim;i++)
|
|
waddch(win, '|');
|
|
wattroff(win, A_DIM | COLOR_PAIR(dim_color));
|
|
for(i=0;i<rem;i++)
|
|
waddch(win, ' ');
|
|
waddch(win, ']' | A_BOLD);
|
|
if(blink) {
|
|
getyx(win, y, x);
|
|
mvwaddch(win, y, x-2, '>' | A_BLINK | COLOR_PAIR(red_color));
|
|
move(y, x);
|
|
}
|
|
}
|
|
|
|
/* --------------------- Error handling ---------------------*/
|
|
static int normal_exit = 0;
|
|
static const char *exit_reason = NULL;
|
|
static const char *exit_func = NULL;
|
|
static unsigned exit_line = 0;
|
|
|
|
static void cleanup(void)
|
|
{
|
|
unsigned i;
|
|
if (curses_inited) {
|
|
if (status_bar_window) {
|
|
werase(status_bar_window);
|
|
wrefresh(status_bar_window);
|
|
}
|
|
rm_windows();
|
|
endwin();
|
|
}
|
|
curses_inited = 0;
|
|
for (i=0;i<global.num_clamd;i++) {
|
|
if (global.conn[i].sd && global.conn[i].sd != -1)
|
|
send_string_noreconn(&global.conn[i], "END\n");
|
|
close(global.conn[i].sd);
|
|
free(global.conn[i].version);
|
|
}
|
|
free(global.all_stats);
|
|
free(global.conn);
|
|
free(queue_header);
|
|
free(clamd_header);
|
|
if(!normal_exit) {
|
|
fprintf(stderr, "Abnormal program termination");
|
|
if (exit_reason) fprintf(stderr, ": %s",exit_reason);
|
|
if (exit_func) fprintf(stderr, " in %s", exit_func);
|
|
if (exit_line) fprintf(stderr, " at line %u", exit_line);
|
|
fputc('\n',stderr);
|
|
}
|
|
}
|
|
|
|
|
|
static void exit_program(enum exit_reason reason, const char *func, unsigned line)
|
|
{
|
|
switch(reason) {
|
|
case FAIL_INITIAL_CONN:
|
|
exit_reason = "Unable to connect to all clamds";
|
|
break;
|
|
case OUT_OF_MEMORY:
|
|
exit_reason = "Out of memory";
|
|
break;
|
|
case RECONNECT_FAIL:
|
|
exit_reason = "Failed to reconnect to clamd after connection was lost";
|
|
break;
|
|
case SIGINT_REASON:
|
|
exit_reason = "User interrupt";
|
|
break;
|
|
default:
|
|
exit_reason = "Unknown";
|
|
break;
|
|
}
|
|
exit_func = func;
|
|
exit_line = line;
|
|
exit(reason);
|
|
}
|
|
|
|
struct task {
|
|
char *line;
|
|
double tim;
|
|
int clamd_no;
|
|
};
|
|
|
|
static int tasks_compare(const void *a, const void *b)
|
|
{
|
|
const struct task *ta = a;
|
|
const struct task *tb = b;
|
|
if(ta->tim < tb->tim)
|
|
return 1;
|
|
if(ta->tim > tb->tim)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* ----------- Socket routines ----------------------- */
|
|
static void print_con_info(conn_t *conn, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
if (stats_head_window) {
|
|
char *buf = malloc(maxx);
|
|
memset(buf, ' ', maxx);
|
|
OOM_CHECK(buf);
|
|
vsnprintf(buf, maxx-1, fmt, ap);
|
|
buf[strlen(buf)] = ' ';
|
|
buf[maxx-1] = '\0';
|
|
wattron(stats_head_window, ERROR_ATTR);
|
|
mvwprintw(stats_head_window, conn->line, 0, "%s", buf);
|
|
wattroff(stats_head_window, ERROR_ATTR);
|
|
wrefresh(stats_head_window);
|
|
free(buf);
|
|
} else
|
|
vfprintf(stdout, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
|
|
static int make_connection(const char *soname, conn_t *conn)
|
|
{
|
|
int s;
|
|
struct timeval tv;
|
|
conn->tcp = 0;
|
|
#ifdef _WIN32
|
|
{
|
|
#else
|
|
if(access(soname, F_OK) == 0) {
|
|
struct sockaddr_un addr;
|
|
s = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if(s < 0) {
|
|
perror("socket");
|
|
return -1;
|
|
}
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sun_family = AF_UNIX;
|
|
strncpy(addr.sun_path, soname, sizeof(addr.sun_path));
|
|
print_con_info(conn, "Connecting to: %s\n", soname);
|
|
if (connect(s, (struct sockaddr *)&addr, sizeof(addr))) {
|
|
perror("connect");
|
|
return -1;
|
|
}
|
|
} else {
|
|
#endif
|
|
struct sockaddr_in server;
|
|
struct hostent *hp;
|
|
unsigned port = 0;
|
|
char *name, *pt = strdup(soname);
|
|
const char *host = pt;
|
|
conn->tcp=1;
|
|
name = strchr(pt, ':');
|
|
if(name) {
|
|
*name++ = '\0';
|
|
port = atoi(name);
|
|
}
|
|
if(!port)
|
|
port = 3310;
|
|
print_con_info(conn, "Looking up: %s\n", host);
|
|
if((hp = gethostbyname(host)) == NULL) {
|
|
herror("Cannot find host");
|
|
return -1;
|
|
}
|
|
free(pt);
|
|
s = socket(AF_INET, SOCK_STREAM, 0);
|
|
if(s < 0) {
|
|
perror("socket");
|
|
return -1;
|
|
}
|
|
server.sin_family = AF_INET;
|
|
server.sin_port = htons(port);
|
|
server.sin_addr.s_addr = ((struct in_addr*)(hp->h_addr))->s_addr;
|
|
print_con_info(conn, "Connecting to: %s:%u\n", inet_ntoa(server.sin_addr), port);
|
|
if (connect(s, (struct sockaddr *)&server, sizeof(server))) {
|
|
perror("connect");
|
|
return -1;
|
|
}
|
|
}
|
|
conn->remote = soname;
|
|
conn->sd = s;
|
|
gettimeofday(&conn->tv_conn, NULL);
|
|
tv.tv_sec = 4;
|
|
tv.tv_usec = 0;
|
|
setsockopt(conn->sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
send_string(conn, "SESSION\nVERSION\n");
|
|
free(conn->version);
|
|
conn->version = NULL;
|
|
read_version(conn);
|
|
return 0;
|
|
}
|
|
|
|
static void reconnect(conn_t *conn);
|
|
|
|
static int send_string_noreconn(conn_t *conn, const char *cmd)
|
|
{
|
|
assert(cmd);
|
|
assert(conn && conn->sd > 0);
|
|
return send(conn->sd, cmd, strlen(cmd), 0);
|
|
}
|
|
|
|
static void send_string(conn_t *conn, const char *cmd)
|
|
{
|
|
while(send_string_noreconn(conn, cmd) == -1) {
|
|
reconnect(conn);
|
|
}
|
|
}
|
|
|
|
static int tries = 0;
|
|
static void reconnect(conn_t *conn)
|
|
{
|
|
if(++tries > 3) {
|
|
EXIT_PROGRAM(RECONNECT_FAIL);
|
|
}
|
|
if (make_connection(conn->remote, conn) < 0) {
|
|
print_con_info(conn, "Unable to reconnect to %s: %s", conn->remote, strerror(errno));
|
|
exit(3);
|
|
}
|
|
tries = 0;
|
|
}
|
|
|
|
static int recv_line(conn_t *conn, char *buf, size_t len)
|
|
{
|
|
assert(len > 0);
|
|
assert(conn);
|
|
assert(buf);
|
|
|
|
len--;
|
|
if (!len || conn->sd == -1)
|
|
return 0;
|
|
assert(conn->sd > 0);
|
|
while (len > 0) {
|
|
ssize_t nread = recv(conn->sd, buf, len, MSG_PEEK);
|
|
if (nread <= 0) {
|
|
print_con_info(conn, "%s: %s", conn->remote, strerror(errno));
|
|
/* it could be a timeout, be nice and send an END */
|
|
send_string_noreconn(conn, "END\n");
|
|
close(conn->sd);
|
|
conn->sd = -1;
|
|
return 0;
|
|
} else {
|
|
char *p = memchr(buf, '\n', nread);
|
|
if (p) {
|
|
len = p - buf + 1;
|
|
} else
|
|
len = nread;
|
|
assert(len > 0);
|
|
assert(len <= (size_t)nread);
|
|
nread = recv(conn->sd, buf, len, 0);
|
|
if (nread == -1)
|
|
reconnect(conn);
|
|
else {
|
|
assert(nread >0 && (size_t)nread == len);
|
|
buf += nread;
|
|
}
|
|
if (p)
|
|
break;
|
|
}
|
|
}
|
|
*buf = '\0';
|
|
return 1;
|
|
}
|
|
|
|
static void output_queue(size_t line, ssize_t max)
|
|
{
|
|
ssize_t i;
|
|
struct task *tasks = global.tasks;
|
|
wattron(stats_window, COLOR_PAIR(queue_header_color));
|
|
mvwprintw(stats_window, line++, 0, "%s", queue_header);
|
|
wattroff(stats_window, COLOR_PAIR(queue_header_color));
|
|
if (max >= global.n)
|
|
max = global.n;
|
|
else
|
|
--max;
|
|
if (max < 0) max = 0;
|
|
for(i=0;i<max;i++) {
|
|
assert(tasks);
|
|
char *cmde = strchr(tasks[i].line, ' ');
|
|
if(cmde) {
|
|
char cmd[16];
|
|
const char *filstart = strchr(cmde + 1, ' ');
|
|
strncpy(cmd, tasks[i].line, sizeof(cmd)-1);
|
|
cmd[15]='\0';
|
|
if (tasks[i].line+15 > cmde)
|
|
cmd[cmde - tasks[i].line] = '\0';
|
|
if(filstart) {
|
|
++filstart;
|
|
if (global.num_clamd>1)
|
|
mvwprintw(stats_window, line + i, 0, "%2u %s", tasks[i].clamd_no, cmd + 1);
|
|
else
|
|
mvwprintw(stats_window, line + i, 0, " %s", cmd + 1);
|
|
mvwprintw(stats_window, line + i, 15, "%10.03fs", tasks[i].tim);
|
|
mvwprintw(stats_window, line + i, 30, "%s",filstart);
|
|
}
|
|
}
|
|
}
|
|
if (max < global.n) {
|
|
/* in summary mode we can only show a max amount of tasks */
|
|
wattron(stats_window, A_DIM | COLOR_PAIR(header_color));
|
|
mvwprintw(stats_window, line+i, 0, "*** %u more task(s) not shown ***", (unsigned)(global.n - max));
|
|
wattroff(stats_window, A_DIM | COLOR_PAIR(header_color));
|
|
}
|
|
}
|
|
|
|
/* ---------------------- stats parsing routines ------------------- */
|
|
|
|
|
|
static void parse_queue(conn_t *conn, char* buf, size_t len, unsigned idx)
|
|
{
|
|
do {
|
|
double tim;
|
|
const char *t = strchr(buf, ' ');
|
|
if(!t)
|
|
continue;
|
|
if(sscanf(t,"%lf", &tim) != 1)
|
|
continue;
|
|
++global.n;
|
|
global.tasks = realloc(global.tasks, sizeof(*global.tasks)*global.n);
|
|
OOM_CHECK(global.tasks);
|
|
global.tasks[global.n-1].line = strdup(buf);
|
|
OOM_CHECK(global.tasks[global.n-1].line);
|
|
global.tasks[global.n-1].tim = tim;
|
|
global.tasks[global.n-1].clamd_no = idx + 1;
|
|
} while (recv_line(conn, buf, len) && buf[0] == '\t' && strcmp("END\n", buf) != 0);
|
|
}
|
|
|
|
static unsigned biggest_queue = 1, biggest_mem = 0;
|
|
|
|
static void output_memstats(struct stats *stats)
|
|
{
|
|
char buf[128];
|
|
unsigned long totalmem;
|
|
int blink = 0;
|
|
|
|
werase(mem_window);
|
|
if (stats->mem != -1) {
|
|
box(mem_window, 0, 0);
|
|
|
|
snprintf(buf, sizeof(buf),"heap %4luM mmap %4luM unused %3luM",
|
|
stats->lheapu/1024, stats->lmmapu/1024, stats->lreleasable/1024);
|
|
mvwprintw(mem_window, 1, 1, "Mem: ");
|
|
print_colored(mem_window, buf);
|
|
|
|
mvwprintw(mem_window, 2, 1, "Libc: ");
|
|
snprintf(buf, sizeof(buf),"used %4luM free %4luM total %4luM",
|
|
stats->ltotalu/1024, stats->ltotalf/1024, (stats->ltotalu+stats->ltotalf)/1024);
|
|
print_colored(mem_window, buf);
|
|
|
|
mvwprintw(mem_window, 3, 1, "Pool: ");
|
|
snprintf(buf, sizeof(buf), "count %u used %4luM total %4luM",
|
|
stats->pools_cnt, stats->lpoolu/1024, stats->lpoolt/1024);
|
|
print_colored(mem_window, buf);
|
|
|
|
totalmem = stats->lheapu + stats->lmmapu + stats->lpoolt;
|
|
if(totalmem > biggest_mem) {
|
|
biggest_mem = totalmem;
|
|
blink = 1;
|
|
}
|
|
show_bar(mem_window, 4, totalmem, stats->lmmapu + stats->lreleasable + stats->lpoolt - stats->lpoolu,
|
|
biggest_mem, blink);
|
|
}
|
|
wrefresh(mem_window);
|
|
}
|
|
|
|
static void parse_memstats(const char *line, struct stats *stats)
|
|
{
|
|
double heapu, mmapu, totalu, totalf, releasable, pools_used, pools_total;
|
|
|
|
if(sscanf(line, " heap %lfM mmap %lfM used %lfM free %lfM releasable %lfM pools %u pools_used %lfM pools_total %lfM",
|
|
&heapu, &mmapu, &totalu, &totalf, &releasable, &stats->pools_cnt, &pools_used, &pools_total) != 8)
|
|
return;
|
|
stats->lheapu = heapu*1000;
|
|
stats->lmmapu = mmapu*1000;
|
|
stats->ltotalu = totalu*1000;
|
|
stats->ltotalf = totalf*1000;
|
|
stats->lreleasable = releasable*1000;
|
|
stats->lpoolu = pools_used*1000;
|
|
stats->lpoolt = pools_total*1000;
|
|
stats->mem = heapu + mmapu + pools_total;
|
|
if (stats->mem == 0) stats->mem = -1;
|
|
}
|
|
|
|
static int show_detail(int idx)
|
|
{
|
|
if (global.num_clamd == 1) {
|
|
assert(idx == 0);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int has_detail()
|
|
{
|
|
return global.num_clamd == 1;
|
|
}
|
|
|
|
static int output_stats(struct stats *stats, unsigned idx)
|
|
{
|
|
char buf[128];
|
|
char timbuf[15];
|
|
int blink = 0;
|
|
size_t i= 0;
|
|
char mem[6];
|
|
WINDOW *win = stats_head_window;
|
|
|
|
if (stats->mem == -1 || stats->stats_unsupp)
|
|
strncpy(mem, "N/A", sizeof(mem));
|
|
else {
|
|
char c;
|
|
double s;
|
|
if (stats->mem > 999.0) {
|
|
c = 'G';
|
|
s = stats->mem / 1024.0;
|
|
} else {
|
|
c = 'M';
|
|
s = stats->mem;
|
|
}
|
|
snprintf(mem, sizeof(mem), "%7.3f", s);
|
|
i = 4;
|
|
if (mem[i-1] == '.') i--;
|
|
mem[i++] = c;
|
|
mem[i] = '\0';
|
|
}
|
|
i = idx+1;
|
|
|
|
if (!stats->db_time.tm_year)
|
|
strncpy(timbuf,"N/A",sizeof(timbuf));
|
|
else
|
|
snprintf(timbuf, sizeof(timbuf), "%04u-%02u-%02u %02uh",
|
|
1900 + stats->db_time.tm_year,
|
|
stats->db_time.tm_mon,
|
|
stats->db_time.tm_mday,
|
|
stats->db_time.tm_hour);
|
|
|
|
if (!stats->stats_unsupp) {
|
|
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,
|
|
stats->live, stats->idle,
|
|
stats->current_q, stats->biggest_queue,
|
|
mem,
|
|
stats->remote, stats->engine_version, stats->db_version, timbuf);
|
|
} else {
|
|
mvwprintw(win, i++, 0,"%2u %02u:%02u:%02u N/A N/A N/A N/A N/A %-14s %-6s %5s %s", idx+1, stats->conn_hr, stats->conn_min, stats->conn_sec,
|
|
stats->remote, stats->engine_version, stats->db_version, timbuf);
|
|
}
|
|
win = stats_window;
|
|
i = 0;
|
|
if (show_detail(idx) && !stats->stats_unsupp) {
|
|
mvwprintw(win, i++, 0, "Primary threads: ");
|
|
snprintf(buf, sizeof(buf), "live %3u idle %3u max %3u", stats->prim_live, stats->prim_idle, stats->prim_max);
|
|
print_colored(win, buf);
|
|
show_bar(win, i++, stats->prim_live, stats->prim_idle, stats->prim_max, 0);
|
|
|
|
mvwprintw(win, i++, 0, "All threads: ");
|
|
snprintf(buf, sizeof(buf), "live %3u idle %3u max %3u", stats->live, stats->idle, stats->max);
|
|
print_colored(win, buf);
|
|
show_bar(win, i++, stats->live, stats->idle, stats->max, 0);
|
|
|
|
mvwprintw(win, i++, 0, "Queue:");
|
|
snprintf(buf, sizeof(buf), "%6u items %6u max", stats->current_q, stats->biggest_queue);
|
|
print_colored(win, buf);
|
|
werase(mem_window);
|
|
output_memstats(stats);
|
|
}
|
|
blink = 0;
|
|
if(stats->current_q > stats->biggest_queue) {
|
|
stats->biggest_queue = stats->current_q;
|
|
blink = 1;
|
|
}
|
|
if (show_detail(idx) && !stats->stats_unsupp) {
|
|
show_bar(win, i++, stats->current_q, 0, biggest_queue, blink);
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static void output_all(void)
|
|
{
|
|
unsigned i, stats_line=0;
|
|
werase(stats_head_window);
|
|
werase(stats_window);
|
|
wattron(stats_head_window, COLOR_PAIR(queue_header_color));
|
|
mvwprintw(stats_head_window, 0, 0, "%s", clamd_header);
|
|
wattroff(stats_head_window, COLOR_PAIR(queue_header_color));
|
|
for (i=0;i<global.num_clamd;i++)
|
|
stats_line = output_stats(&global.all_stats[i], i);
|
|
output_queue(stats_line, maxystats - stats_line-1);
|
|
wrefresh(stats_head_window);
|
|
wrefresh(stats_window);
|
|
if (has_detail()) {
|
|
/* overlaps, must be done at the end */
|
|
wrefresh(mem_window);
|
|
}
|
|
}
|
|
|
|
static void parse_stats(conn_t *conn, struct stats *stats, unsigned idx)
|
|
{
|
|
char buf[1024];
|
|
size_t j;
|
|
struct timeval tv;
|
|
unsigned conn_dt;
|
|
int primary = 0;
|
|
const char *pstart, *p;
|
|
|
|
if (conn->tcp)
|
|
stats->remote = conn->remote;
|
|
else
|
|
stats->remote = "local";
|
|
|
|
if (!conn->version) {
|
|
stats->engine_version = "???";
|
|
return;
|
|
}
|
|
p = pstart = conn->version;
|
|
/* find digit in version */
|
|
while (*p && !isdigit(*p))
|
|
p++;
|
|
/* rewind to first space or dash */
|
|
while (p > pstart && *p && *p != ' ' && *p != '-')
|
|
p--;
|
|
if (*p) p++;
|
|
/* keep only base version, and cut -exp, and -gittags */
|
|
pstart = p;
|
|
while (*p && *p != '-' && *p != '/')
|
|
p++;
|
|
|
|
stats->engine_version = malloc(p - pstart+1);
|
|
OOM_CHECK(stats->engine_version);
|
|
|
|
memcpy(stats->engine_version, pstart, p-pstart);
|
|
stats->engine_version[p-pstart] = '\0';
|
|
|
|
pstart = strchr(p, '/');
|
|
if (!pstart)
|
|
stats->db_version = strdup("????");
|
|
else {
|
|
pstart++;
|
|
p = strchr(pstart, '/');
|
|
if (!p)
|
|
p = pstart + strlen(pstart);
|
|
stats->db_version = malloc(p - pstart + 1);
|
|
OOM_CHECK(stats->db_version);
|
|
memcpy(stats->db_version, pstart, p-pstart);
|
|
stats->db_version[p-pstart] = '\0';
|
|
if(*p) p++;
|
|
if (!*p || !strptime(p,"%a %b %d %H:%M:%S %Y", &stats->db_time)) {
|
|
memset(&stats->db_time, 0, sizeof(stats->db_time));
|
|
}
|
|
}
|
|
if (maxx > 61 && strlen(stats->db_version) > (maxx-61)) {
|
|
stats->db_version[maxx-61] = '\0';
|
|
}
|
|
|
|
stats->version = conn->version; /* for details view */
|
|
gettimeofday(&tv, NULL);
|
|
tv.tv_sec -= conn->tv_conn.tv_sec;
|
|
tv.tv_usec -= conn->tv_conn.tv_usec;
|
|
conn_dt = tv.tv_sec + tv.tv_usec/1e6;
|
|
|
|
stats->live = stats->idle = stats->max = 0;
|
|
stats->conn_hr = conn_dt/3600;
|
|
stats->conn_min = (conn_dt/60)%60;
|
|
stats->conn_sec = conn_dt%60;
|
|
|
|
while(recv_line(conn, buf, sizeof(buf)) && strcmp("END\n",buf) != 0) {
|
|
char *val = strchr(buf, ':');
|
|
|
|
if(buf[0] == '\t') {
|
|
parse_queue(conn, buf, sizeof(buf), idx);
|
|
continue;
|
|
} else if(val)
|
|
*val++ = '\0';
|
|
if(!strcmp("MEMSTATS", buf)) {
|
|
parse_memstats(val, stats);
|
|
continue;
|
|
}
|
|
if(!strncmp("UNKNOWN COMMAND", buf, 15)) {
|
|
stats->stats_unsupp = 1;
|
|
break;
|
|
}
|
|
for(j=1;j<strlen(buf);j++)
|
|
buf[j] = tolower(buf[j]);
|
|
/* mvwprintw(win, i, 0, "%s", buf);
|
|
if(!val) {
|
|
i++;
|
|
continue;
|
|
}
|
|
waddch(win, ':');
|
|
print_colored(win, val);
|
|
i++;*/
|
|
if(!strncmp("State",buf,5)) {
|
|
if(strstr(val, "PRIMARY")) {
|
|
/* primary thread pool */
|
|
primary = 1;
|
|
} else {
|
|
/* multiscan pool */
|
|
primary = 0;
|
|
}
|
|
}
|
|
if(!strcmp("Threads",buf)) {
|
|
unsigned live, idle, max;
|
|
if(sscanf(val, " live %u idle %u max %u", &live, &idle, &max) != 3)
|
|
continue;
|
|
if (primary) {
|
|
stats->prim_live = live;
|
|
stats->prim_idle = idle;
|
|
assert(!stats->prim_max && "There can be only one primary pool!");
|
|
stats->prim_max = max;
|
|
}
|
|
stats->live += live;
|
|
stats->idle += idle;
|
|
stats->max += max;
|
|
} else if (!strcmp("Queue",buf)) {
|
|
unsigned len;
|
|
if(sscanf(val, "%u", &len) != 1)
|
|
continue;
|
|
stats->current_q = len;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void read_version(conn_t *conn)
|
|
{
|
|
char buf[1024];
|
|
if(recv_line(conn, buf, sizeof(buf))) {
|
|
conn->version = strdup(buf);
|
|
}
|
|
}
|
|
|
|
static void sigint(int a)
|
|
{
|
|
EXIT_PROGRAM(SIGINT_REASON);
|
|
}
|
|
|
|
/* -------------------------- Initialization ---------------- */
|
|
static void setup_connections(int argc, char *argv[])
|
|
{
|
|
unsigned i;
|
|
#ifdef _WIN32
|
|
WSADATA wsaData;
|
|
if (WSAStartup(MAKEWORD(2,2), &wsaData) != NO_ERROR) {
|
|
fprintf(stderr, "Error at WSAStartup(): %d\n", WSAGetLastError());
|
|
EXIT_PROGRAM(FAIL_INITIAL_CONN);
|
|
}
|
|
#endif
|
|
|
|
/* clamdtop v0.1 */
|
|
puts( " __ ____");
|
|
puts(" _____/ /___ _____ ___ ____/ / /_____ ____ ___ ___");
|
|
puts(" / ___/ / __ `/ __ `__ \\/ __ / __/ __ \\/ __ \\ _ __/ _ \\ < /");
|
|
puts("/ /__/ / /_/ / / / / / / /_/ / /_/ /_/ / /_/ / | |/ / // / / /");
|
|
puts("\\___/_/\\__,_/_/ /_/ /_/\\__,_/\\__/\\____/ .___/ |___/\\___(_)_/");
|
|
puts(" /_/ ");
|
|
memset(&global, 0, sizeof(global));
|
|
if (argc == 1) {
|
|
global.num_clamd = 1;
|
|
#ifdef _WIN32
|
|
argv[1] = "localhost:3310";
|
|
#else
|
|
argv[1] = "/tmp/clamd.socket";
|
|
#endif
|
|
} else
|
|
global.num_clamd = argc-1;
|
|
global.all_stats = calloc(global.num_clamd, sizeof(*global.all_stats));
|
|
OOM_CHECK(global.all_stats);
|
|
global.conn = calloc(global.num_clamd, sizeof(*global.conn));
|
|
OOM_CHECK(global.conn);
|
|
for (i=0;i<global.num_clamd;i++) {
|
|
global.conn[i].line = i+1;
|
|
if (make_connection(argv[i+1], &global.conn[i]) < 0) {
|
|
EXIT_PROGRAM(FAIL_INITIAL_CONN);
|
|
}
|
|
|
|
}
|
|
#ifndef _WIN32
|
|
signal(SIGPIPE, SIG_IGN);
|
|
signal(SIGINT, sigint);
|
|
#endif
|
|
}
|
|
|
|
static void free_global_stats(void)
|
|
{
|
|
unsigned i;
|
|
for (i=0;i<(unsigned)global.n;i++) {
|
|
free(global.tasks[i].line);
|
|
}
|
|
for (i=0;i<global.num_clamd;i++) {
|
|
free(global.all_stats[i].engine_version);
|
|
free(global.all_stats[i].db_version);
|
|
}
|
|
free(global.tasks);
|
|
global.tasks = NULL;
|
|
global.n=0;
|
|
}
|
|
|
|
static int help_line;
|
|
static void explain(const char *abbrev, const char *msg)
|
|
{
|
|
wattron(stdscr, A_BOLD);
|
|
mvwprintw(stdscr, help_line++, 0, "%-15s", abbrev);
|
|
wattroff(stdscr, A_BOLD);
|
|
wprintw(stdscr," %s", msg);
|
|
}
|
|
|
|
static int show_help(void)
|
|
{
|
|
int ch;
|
|
werase(stdscr);
|
|
help_line = 0;
|
|
|
|
explain("NO","Unique clamd number");
|
|
explain("CONNTIME", "How long it is connected");
|
|
explain("LIV", "Total number of live threads");
|
|
explain("IDL", "Total number of idle threads");
|
|
explain("QUEUE", "Number of items in queue");
|
|
explain("MAXQ","Maximum number of items observed in queue");
|
|
explain("MEM", "Total memory usage (if available)");
|
|
explain("HOST", "Which clamd, local means unix socket");
|
|
explain("ENGINE", "Engine version");
|
|
explain("DBVER", "Database version");
|
|
explain("DBTIME", "Database publish time");
|
|
explain("Primary threads", "Threadpool used to receive commands");
|
|
explain("All threads","All threadpools, including multiscan");
|
|
explain("live","Executing commands, or scanning");
|
|
explain("idle","Waiting for commands, will exit after idle_timeout");
|
|
explain("max", "Maximum number of threads configured for this pool");
|
|
explain("Queue","Tasks queued for processing, but not yet picked up by a thread");
|
|
explain("COMMAND","Command this thread is executing");
|
|
explain("QUEUEDSINCE","How long this task is executing");
|
|
explain("FILE","Which file it is processing (if applicable)");
|
|
explain("Mem","Memory usage reported by libc");
|
|
explain("Libc","Used/free memory reported by libc");
|
|
explain("Pool","Memory usage reported by libclamav's pool");
|
|
|
|
wrefresh(stdscr);
|
|
werase(status_bar_window);
|
|
wattron(status_bar_window, A_REVERSE);
|
|
mvwprintw(status_bar_window, 0, 0, "Press any key to exit help");
|
|
wattroff(status_bar_window, A_REVERSE);
|
|
wrefresh(status_bar_window);
|
|
/* getch() times out after a few seconds */
|
|
do {
|
|
ch = getch();
|
|
/* we do need to exit on resize, because the text scroll out of
|
|
* view */
|
|
} while (ch == -1 /*|| ch == KEY_RESIZE*/);
|
|
return ch == KEY_RESIZE ? KEY_RESIZE : -1;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int ch = 0;
|
|
struct timeval tv_last, tv;
|
|
unsigned i;
|
|
|
|
atexit(cleanup);
|
|
setup_connections(argc, argv);
|
|
|
|
init_ncurses(global.num_clamd);
|
|
|
|
memset(&tv_last, 0, sizeof(tv_last));
|
|
do {
|
|
if (ch == KEY_F(1)) {
|
|
ch = show_help();
|
|
}
|
|
if(ch == KEY_RESIZE) {
|
|
resize();
|
|
endwin();
|
|
refresh();
|
|
init_windows(global.num_clamd);
|
|
}
|
|
if(ch == 'R') {
|
|
biggest_queue = 1;
|
|
biggest_mem = 0;
|
|
}
|
|
gettimeofday(&tv, NULL);
|
|
header();
|
|
if(tv.tv_sec - tv_last.tv_sec >= MIN_INTERVAL) {
|
|
free_global_stats();
|
|
for(i=0;i<global.num_clamd;i++) {
|
|
struct stats *stats = &global.all_stats[i];
|
|
if (global.conn[i].sd != -1)
|
|
send_string(&global.conn[i], "STATS\n");
|
|
memset(stats, 0, sizeof(*stats));
|
|
parse_stats(&global.conn[i], stats, i);
|
|
}
|
|
if (global.tasks)
|
|
qsort(global.tasks, global.n, sizeof(*global.tasks), tasks_compare);
|
|
tv_last = tv;
|
|
}
|
|
/* always show, so that screen resizes take effect instantly*/
|
|
output_all();
|
|
for(i=0;i<global.num_clamd;i++) {
|
|
if (global.conn[i].sd == -1)
|
|
reconnect(&global.conn[i]);
|
|
}
|
|
} while(toupper(ch = getch()) != 'Q');
|
|
free_global_stats();
|
|
normal_exit = 1;
|
|
return 0;
|
|
}
|
|
|