#include "kcollect.h"
#include <ndbm.h>
#include <fcntl.h>
#include <errno.h>
#define SLEEP_INTERVAL 60
#define DATA_BASE_INDEX 8
#define DISPLAY_TIME_ONLY "%H:%M:%S"
#define DISPLAY_FULL_DATE "%F %H:%M:%S"
#define HDR_BASE "HEADER"
#define HDR_STRLEN 6
#define HDR_FMT_INDEX 0
#define HDR_FMT_TITLE 1
#define HDR_FMT_HOST 2
#define HOST_NAME_MAX sysconf(_SC_HOST_NAME_MAX)
static void format_output(uintmax_t, char, uintmax_t, char *);
static void dump_text(kcollect_t *, size_t, size_t, const char *);
static const char *get_influx_series(const char *);
static void dump_influxdb(kcollect_t *, size_t, size_t, const char *);
static void (*dumpfn)(kcollect_t *, size_t, size_t, const char *);
static void dump_dbm(kcollect_t *, size_t, const char *);
static void load_dbm(const char *datafile, kcollect_t **, size_t *);
static int rec_comparator(const void *, const void *);
static void dump_fields(kcollect_t *);
static void adjust_fields(kcollect_t *, const char *);
static void restore_headers(kcollect_t *, const char *);
static int str2unix(const char *, const char*);
static kcollect_t *load_kernel(kcollect_t *, kcollect_t *, size_t *);
FILE *OutFP;
int UseGMT;
int OutputWidth = 1024;
int OutputHeight = 1024;
int SmoothOpt;
static int LoadedFromDB;
static int HostnameMismatch;
static int Fflag;
int
main(int ac, char **av)
{
kcollect_t *ary_base;
kcollect_t *ary;
size_t bytes = 0;
size_t count;
size_t total_count;
const char *datafile = NULL;
const char *fields = NULL;
int cmd = 't';
int ch;
int keepalive = 0;
int last_ticks;
int loops = 0;
int maxtime = 0;
const char *dbmFile = NULL;
int fromFile = 0;
OutFP = stdout;
dumpfn = dump_text;
sysctlbyname("kern.collect_data", NULL, &bytes, NULL, 0);
if (bytes == 0) {
fprintf(stderr, "kern.collect_data not available\n");
exit(1);
}
while ((ch = getopt(ac, av, "o:O:b:d:r:fFlsgt:xw:GW:H:")) != -1) {
char *suffix;
switch(ch) {
case 'o':
fields = optarg;
break;
case 'O':
if ((strncasecmp("influxdb", optarg, 16) == 0)) {
dumpfn = dump_influxdb;
} else if (strncasecmp("text", optarg, 16) == 0) {
dumpfn = dump_text;
} else {
fprintf(stderr, "Bad output text format %s\n", optarg);
exit(1);
}
break;
case 'b':
datafile = optarg;
cmd = 'b';
break;
case 'd':
dbmFile = optarg;
fromFile = 1;
break;
case 'r':
datafile = optarg;
cmd = 'r';
break;
case 'f':
keepalive = 1;
break;
case 'F':
Fflag = 1;
keepalive = 1;
break;
case 'l':
cmd = 'l';
break;
case 's':
SmoothOpt = 1;
break;
case 'w':
datafile = optarg;
cmd = 'w';
break;
case 'g':
cmd = 'g';
break;
case 'x':
cmd = 'x';
break;
case 't':
maxtime = strtol(optarg, &suffix, 0);
switch(*suffix) {
case 'd':
maxtime *= 24;
case 'h':
maxtime *= 60;
case 'm':
maxtime *= 60;
break;
case 0:
break;
default:
fprintf(stderr,
"Illegal suffix in -t option\n");
exit(1);
}
break;
case 'G':
UseGMT = 1;
break;
case 'W':
OutputWidth = strtol(optarg, NULL, 0);
break;
case 'H':
OutputHeight = strtol(optarg, NULL, 0);
break;
default:
fprintf(stderr, "Unknown option %c\n", ch);
exit(1);
}
}
if (cmd != 'x' && ac != optind) {
fprintf(stderr, "Unknown argument %s\n", av[optind]);
exit(1);
}
total_count = 0;
last_ticks = 0;
if (cmd == 'x' || cmd == 'w')
start_gnuplot(ac - optind, av + optind, datafile);
do {
if (HostnameMismatch) {
fprintf(stderr,
"Hostname mismatch, can't show live data\n");
exit(1);
}
bytes = 0;
sysctlbyname("kern.collect_data", NULL, &bytes, NULL, 0);
if (cmd == 'l')
bytes = sizeof(kcollect_t) * 2;
if (Fflag && loops == 0)
loops++;
if (loops) {
size_t loop_bytes;
loop_bytes = sizeof(kcollect_t) *
(4 + SLEEP_INTERVAL / KCOLLECT_INTERVAL);
if (bytes > loop_bytes)
bytes = loop_bytes;
}
if (fromFile) {
kcollect_t *dbmAry = NULL;
load_dbm(dbmFile, &dbmAry, &count);
ary = ary_base = dbmAry;
} else {
kcollect_t scaleid[2];
ary_base = malloc(bytes +
DATA_BASE_INDEX * sizeof(kcollect_t));
ary = ary_base + DATA_BASE_INDEX;
sysctlbyname("kern.collect_data", ary, &bytes, NULL, 0);
count = bytes / sizeof(kcollect_t);
if (count < 2) {
fprintf(stderr,
"[ERR] kern.collect_data failed\n");
exit(1);
}
scaleid[0] = ary[0];
scaleid[1] = ary[1];
count -= 2;
ary = load_kernel(scaleid, ary + 2, &count);
}
if (fields)
adjust_fields(&ary[1], fields);
if (loops) {
while (count > DATA_BASE_INDEX) {
if ((int)(ary[count-1].ticks - last_ticks) > 0)
break;
--count;
}
}
if (maxtime) {
maxtime *= ary[0].hz;
while (count > DATA_BASE_INDEX) {
if ((int)(ary[0].ticks - ary[count-1].ticks) <
maxtime) {
break;
}
--count;
}
}
switch(cmd) {
case 't':
if (count > DATA_BASE_INDEX) {
dumpfn(ary, count, total_count,
(fromFile ? DISPLAY_FULL_DATE :
DISPLAY_TIME_ONLY));
}
break;
case 'b':
if (HostnameMismatch) {
fprintf(stderr,
"Hostname mismatch, cannot save to DB\n");
exit(1);
} else {
if (count > DATA_BASE_INDEX)
dump_dbm(ary, count, datafile);
}
break;
case 'r':
if (count >= DATA_BASE_INDEX)
restore_headers(ary, datafile);
break;
case 'l':
dump_fields(ary);
exit(0);
break;
case 'g':
if (count > DATA_BASE_INDEX)
dump_gnuplot(ary, count);
break;
case 'w':
if (count >= DATA_BASE_INDEX)
dump_gnuplot(ary, count);
break;
case 'x':
if (count > DATA_BASE_INDEX)
dump_gnuplot(ary, count);
break;
}
if (keepalive && !fromFile) {
fflush(OutFP);
fflush(stdout);
switch(cmd) {
case 't':
sleep(1);
break;
case 'x':
case 'g':
case 'w':
sleep(60);
break;
default:
sleep(10);
break;
}
}
last_ticks = ary[DATA_BASE_INDEX].ticks;
if (count >= DATA_BASE_INDEX)
total_count += count - DATA_BASE_INDEX;
if (cmd != 'g' && cmd != 'x' && cmd != 'w')
++loops;
free(ary_base);
} while (keepalive);
if (cmd == 'x')
pclose(OutFP);
}
static
void
format_output(uintmax_t value,char fmt,uintmax_t scale, char* ret)
{
char buf[9];
switch(fmt) {
case '2':
sprintf(ret, "%5ju.%02ju",
value / 100, value % 100);
break;
case 'p':
sprintf(ret,"%4ju.%02ju%%",
value / 100, value % 100);
break;
case 'm':
humanize_number(buf, sizeof(buf), value, "",
2,
HN_FRACTIONAL |
HN_NOSPACE);
sprintf(ret,"%8.8s", buf);
break;
case 'c':
humanize_number(buf, sizeof(buf), value, "",
HN_AUTOSCALE,
HN_FRACTIONAL |
HN_NOSPACE |
HN_DIVISOR_1000);
sprintf(ret,"%8.8s", buf);
break;
case 'b':
if (scale > 100000000) {
humanize_number(buf, sizeof(buf),
value, "",
3,
HN_FRACTIONAL |
HN_NOSPACE);
} else {
humanize_number(buf, sizeof(buf),
value, "",
2,
HN_FRACTIONAL |
HN_NOSPACE);
}
sprintf(ret,"%8.8s", buf);
break;
default:
sprintf(ret,"%s"," ");
break;
}
}
static
const char *
get_influx_series(const char *val)
{
if ((strncmp("user", val, 8) == 0) ||
(strncmp("idle", val, 8) == 0 ) ||
(strncmp("syst", val, 8) == 0))
return "cpu_value";
if (strncmp("load", val, 8) == 0)
return "load_value";
if ((strncmp("swapuse", val, 8) == 0) ||
(strncmp("swapano", val, 8) == 0 ) ||
(strncmp("swapcac", val, 8) == 0))
return "swap_value";
if ((strncmp("fault", val, 8) == 0) ||
(strncmp("cow", val, 8) == 0 ) ||
(strncmp("zfill", val, 8) == 0))
return "vm_value";
if ((strncmp("cache", val, 8) == 0) ||
(strncmp("inact", val, 8) == 0 ) ||
(strncmp("act", val, 8) == 0) ||
(strncmp("wired", val, 8) == 0) ||
(strncmp("free", val, 8) == 0))
return "memory_value";
if ((strncmp("syscalls", val, 8) == 0) ||
(strncmp("nlookup", val, 8) == 0 ) ||
(strncmp("intr", val, 8) == 0) ||
(strncmp("ipi", val, 8) == 0) ||
(strncmp("timer", val, 8) == 0))
return "misc_value";
return "misc_value";
}
static
void
dump_influxdb(kcollect_t *ary, size_t count, size_t total_count __unused,
__unused const char* display_fmt)
{
int j;
int i;
uintmax_t value;
size_t ts_nsec;
char hostname[HOST_NAME_MAX];
char *colname;
if (LoadedFromDB) {
snprintf(hostname, HOST_NAME_MAX, "%s", (char *)ary[2].data);
} else {
if (gethostname(hostname, HOST_NAME_MAX) != 0) {
fprintf(stderr, "Failed to get hostname\n");
exit(1);
}
}
for (i = count - 1; i >= DATA_BASE_INDEX; --i) {
ts_nsec = (ary[i].realtime.tv_sec
* 1000
* 1000
* 1000
+ 123 );
ts_nsec += (ary[i].realtime.tv_usec * 1000);
for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
if (ary[1].data[j] == 0)
continue;
value = ary[i].data[j];
colname = (char *)&ary[1].data[j];
printf("%s,host=%s,type=%.8s value=%jd %jd\n",
get_influx_series(colname),
hostname, colname, value, ts_nsec);
}
printf("\n");
}
}
static
void
dump_text(kcollect_t *ary, size_t count, size_t total_count,
const char* display_fmt)
{
int j;
int i;
uintmax_t scale;
uintmax_t value;
char fmt;
char sbuf[20];
struct tm *tmv;
time_t t;
for (i = count - 1; i >= DATA_BASE_INDEX; --i) {
if ((total_count & 15) == 0) {
if (!strcmp(display_fmt, DISPLAY_FULL_DATE)) {
printf("%20s", "timestamp ");
} else {
printf("%8.8s", "time");
}
for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
if (ary[1].data[j]) {
printf(" %8.8s",
(char *)&ary[1].data[j]);
}
}
printf("\n");
}
t = ary[i].realtime.tv_sec;
if (UseGMT)
tmv = gmtime(&t);
else
tmv = localtime(&t);
strftime(sbuf, sizeof(sbuf), display_fmt, tmv);
printf("%8s", sbuf);
for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
if (ary[1].data[j] == 0)
continue;
value = ary[i].data[j];
scale = KCOLLECT_GETSCALE(ary[0].data[j]);
fmt = KCOLLECT_GETFMT(ary[0].data[j]);
printf(" ");
format_output(value, fmt, scale, sbuf);
printf("%s",sbuf);
}
printf("\n");
++total_count;
}
}
static
void
restore_headers(kcollect_t *ary, const char *datafile)
{
DBM *db;
char hdr[32];
datum key, value;
int i;
db = dbm_open(datafile, (O_RDWR),
(S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP));
if (db == NULL) {
switch (errno) {
case EACCES:
fprintf(stderr,
"[ERR] database file \"%s\" is read-only, "
"check permissions. (%i)\n",
datafile, errno);
break;
default:
fprintf(stderr,
"[ERR] opening our database file \"%s\" "
"produced an error. (%i)\n",
datafile, errno);
}
exit(EXIT_FAILURE);
} else {
for (i = 0; i < DATA_BASE_INDEX; ++i) {
snprintf(hdr, sizeof(hdr), "%s%d", HDR_BASE, i);
key.dptr = hdr;
key.dsize = strlen(key.dptr) + 1;
value.dptr = &ary[i].data;
value.dsize = sizeof(uint64_t) * KCOLLECT_ENTRIES;
if (dbm_store(db,key,value,DBM_REPLACE) == -1) {
fprintf(stderr,
"[ERR] error storing the value in "
"the database file \"%s\" (%i)\n",
datafile, errno);
dbm_close(db);
exit(EXIT_FAILURE);
}
}
}
dbm_close(db);
}
static
void
dump_dbm(kcollect_t *ary, size_t count, const char *datafile)
{
DBM * db;
db = dbm_open(datafile, (O_RDWR | O_CREAT),
(S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP));
if (db == NULL) {
switch (errno) {
case EACCES:
fprintf(stderr,
"[ERR] database file \"%s\" is read-only, "
"check permissions. (%i)\n",
datafile, errno);
break;
default:
fprintf(stderr,
"[ERR] opening our database file \"%s\" "
"produced an error. (%i)\n",
datafile, errno);
}
exit(EXIT_FAILURE);
} else {
struct tm *tmv;
char buf[20];
datum key;
datum value;
time_t t;
uint i;
int cmd;
char hdr[32];
for (i = 0; i < count; ++i) {
cmd = DBM_INSERT;
if (i < DATA_BASE_INDEX) {
snprintf(hdr, sizeof(hdr), "%s%d", HDR_BASE, i);
key.dptr = hdr;
key.dsize = strlen(hdr) + 1;
value = dbm_fetch(db, key);
if (value.dptr == NULL ||
bcmp(ary[i].data, value.dptr,
sizeof(uint64_t) * KCOLLECT_ENTRIES)) {
cmd = DBM_REPLACE;
if (value.dptr != NULL) {
fprintf(stderr,
"Header %d changed "
"in database, "
"updating\n",
i);
}
}
} else {
t = ary[i].realtime.tv_sec;
if (LoadedFromDB)
tmv = localtime(&t);
else
tmv = gmtime(&t);
strftime(buf, sizeof(buf),
DISPLAY_FULL_DATE, tmv);
key.dptr = buf;
key.dsize = sizeof(buf);
}
value.dptr = ary[i].data;
value.dsize = sizeof(uint64_t) * KCOLLECT_ENTRIES;
if (dbm_store(db, key, value, cmd) == -1) {
fprintf(stderr,
"[ERR] error storing the value in "
"the database file \"%s\" (%i)\n",
datafile, errno);
dbm_close(db);
exit(EXIT_FAILURE);
}
}
dbm_close(db);
}
}
static
int
str2unix(const char* str, const char* fmt){
struct tm tm;
time_t ts;
memset(&tm, 0, sizeof(struct tm));
strptime(str, fmt, &tm);
ts = timegm(&tm);
return (int)ts;
}
static
int
rec_comparator(const void *c1, const void *c2)
{
const kcollect_t *k1 = (const kcollect_t*)c1;
const kcollect_t *k2 = (const kcollect_t*)c2;
if (k1->realtime.tv_sec < k2->realtime.tv_sec)
return -1;
if (k1->realtime.tv_sec > k2->realtime.tv_sec)
return 1;
return 0;
}
static
kcollect_t *
load_kernel(kcollect_t *scaleid, kcollect_t *ary, size_t *counter)
{
ary -= DATA_BASE_INDEX;
bzero(ary, sizeof(*ary) * DATA_BASE_INDEX);
ary[0] = scaleid[0];
ary[1] = scaleid[1];
gethostname((char *)ary[2].data,
sizeof(uint64_t) * KCOLLECT_ENTRIES - 1);
*counter += DATA_BASE_INDEX;
return ary;
}
static
void
load_dbm(const char* datafile, kcollect_t **ret_ary,
size_t *counter)
{
char hostname[sizeof(uint64_t) * KCOLLECT_ENTRIES];
DBM *db = dbm_open(datafile,(O_RDONLY),(S_IRUSR|S_IRGRP));
datum key;
datum value;
size_t recCounter = DATA_BASE_INDEX;
int headersFound = 0;
uint c;
uint sc;
if (db == NULL) {
fprintf(stderr,
"[ERR] opening our database \"%s\" produced "
"an error! (%i)\n",
datafile, errno);
exit(EXIT_FAILURE);
}
for (key = dbm_firstkey(db); key.dptr; key = dbm_nextkey(db)) {
value = dbm_fetch(db, key);
if (value.dptr == NULL)
continue;
if (strncmp(key.dptr, HDR_BASE, HDR_STRLEN) == 0)
continue;
recCounter++;
}
if (*ret_ary)
free(*ret_ary);
*ret_ary = malloc(sizeof(kcollect_t) * recCounter);
if (*ret_ary == NULL) {
fprintf(stderr,
"[ERR] failed to allocate enough memory to "
"hold the database! Aborting.\n");
dbm_close(db);
exit(EXIT_FAILURE);
}
bzero(*ret_ary, sizeof(kcollect_t) * recCounter);
c = DATA_BASE_INDEX;
key = dbm_firstkey(db);
while (key.dptr && c < recCounter) {
value = dbm_fetch(db, key);
if (value.dptr == NULL) {
key = dbm_nextkey(db);
continue;
}
if (!strncmp(key.dptr, HDR_BASE, HDR_STRLEN)) {
sc = strtoul((char *)key.dptr +
HDR_STRLEN, NULL, 10);
if (sc >= DATA_BASE_INDEX) {
key = dbm_nextkey(db);
continue;
}
headersFound |= 1 << sc;
} else {
sc = c++;
(*ret_ary)[sc].realtime.tv_sec =
str2unix(key.dptr,
DISPLAY_FULL_DATE);
}
memcpy((*ret_ary)[sc].data,
value.dptr,
sizeof(uint64_t) * KCOLLECT_ENTRIES);
key = dbm_nextkey(db);
}
if ((headersFound & 4) && *(char *)(*ret_ary)[2].data == 0)
headersFound &= ~4;
bzero(hostname, sizeof(hostname));
gethostname(hostname, sizeof(hostname) - 1);
if (headersFound & 0x0004) {
if (*(char *)(*ret_ary)[2].data &&
strcmp(hostname, (char *)(*ret_ary)[2].data) != 0) {
HostnameMismatch = 1;
}
}
*counter = recCounter;
qsort(&(*ret_ary)[DATA_BASE_INDEX], recCounter - DATA_BASE_INDEX,
sizeof(kcollect_t), rec_comparator);
dbm_close(db);
if ((headersFound & 3) != 3) {
fprintf(stderr, "We could not retrieve all necessary headers, "
"might be the database file is corrupted? (%i)\n",
headersFound);
exit(EXIT_FAILURE);
}
LoadedFromDB = 1;
}
static void
dump_fields(kcollect_t *ary)
{
int j;
for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
if (ary[1].data[j] == 0)
continue;
printf("%8.8s %c\n",
(char *)&ary[1].data[j],
KCOLLECT_GETFMT(ary[0].data[j]));
}
}
static void
adjust_fields(kcollect_t *ent, const char *fields)
{
char *copy = strdup(fields);
char *word;
int selected[KCOLLECT_ENTRIES];
int j;
bzero(selected, sizeof(selected));
word = strtok(copy, ", \t");
while (word) {
for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
if (strncmp(word, (char *)&ent->data[j], 8) == 0) {
selected[j] = 1;
break;
}
}
word = strtok(NULL, ", \t");
}
free(copy);
for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
if (!selected[j])
ent->data[j] = 0;
}
}