1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00

log: added new module to print fancy tables and use it for histograms as well as periodc stats

This commit is contained in:
Steffen Vogel 2017-07-12 00:56:08 +02:00
parent 2af83114bf
commit cc6f6a6132
13 changed files with 471 additions and 157 deletions

View file

@ -41,7 +41,8 @@
#define DEFAULT_NR_HUGEPAGES 100
/** Width of log output in characters */
#define LOG_WIDTH 132
#define LOG_WIDTH 80
#define LOG_HEIGHT 25
/** Socket priority */
#define SOCKET_PRIO 7

View file

@ -29,14 +29,17 @@ extern "C" {
#include <stdarg.h>
#include <time.h>
#include <sys/ioctl.h>
#include "common.h"
#include "log_config.h"
#ifdef __GNUC__
#define INDENT int __attribute__ ((__cleanup__(log_outdent), unused)) _old_indent = log_indent(1);
#define NOINDENT int __attribute__ ((__cleanup__(log_outdent), unused)) _old_indent = log_noindent();
#else
#define INDENT ;
#define NOINDENT ;
#endif
/* The log level which is passed as first argument to print() */
@ -86,20 +89,16 @@ struct log {
enum state state;
struct timespec epoch; /**< A global clock used to prefix the log messages. */
struct winsize window; /**< Size of the terminal window. */
/** Debug level used by the debug() macro.
* It defaults to V (defined by the Makefile) and can be
* overwritten by the 'debug' setting in the configuration file. */
int level;
/** Debug facilities used by the debug() macro. */
long facilities;
/** Path of the log file */
const char *path;
/** Send all log output to this file / stdout / stderr */
FILE *file;
long facilities; /**< Debug facilities used by the debug() macro. */
const char *path; /**< Path of the log file. */
FILE *file; /**< Send all log output to this file / stdout / stderr. */
};
/** The global log instance. */
@ -122,6 +121,9 @@ int log_destroy(struct log *l);
*/
int log_indent(int levels);
/** Disable log indention of current thread. */
int log_noindent();
/** A helper function the restore the previous log indention level.
*
* This function is usually called by a __cleanup__ handler (GCC C Extension).
@ -163,9 +165,6 @@ void log_vprint(struct log *l, const char *lvl, const char *fmt, va_list va);
void debug(long lvl, const char *fmt, ...)
__attribute__ ((format(printf, 2, 3)));
/** Print a horizontal line. */
void line();
/** Printf alike info message. */
void info(const char *fmt, ...)
__attribute__ ((format(printf, 1, 2)));
@ -186,6 +185,41 @@ void error(const char *fmt, ...)
void serror(const char *fmt, ...)
__attribute__ ((format(printf, 1, 2)));
/** @addtogroup table Print fancy tables
* @{
*/
struct table_column {
int width; /**< Width of the column. */
char *title; /**< The title as shown in the table header. */
char *format; /**< The format which is used to print the table rows. */
char *unit; /**< An optional unit which will be shown in the table header. */
enum {
TABLE_ALIGN_LEFT,
TABLE_ALIGN_RIGHT
} align;
int _width; /**< The real width of this column. Calculated by table_header() */
};
struct table {
int ncols;
int width;
struct table_column *cols;
};
/** Print a table header consisting of \p n columns. */
void table_header(struct table *t);
/** Print table rows. */
void table_row(struct table *t, ...);
/** Print the table footer. */
void table_footer(struct table *t);
/** @} */
#ifdef __cplusplus
}
#endif

View file

@ -82,6 +82,7 @@ json_t * stats_json(struct stats *s);
void stats_reset(struct stats *s);
void stats_print_header();
void stats_print_footer();
void stats_print_periodic(struct stats *s, FILE *f, enum stats_format fmt, int verbose, struct path *p);

View file

@ -41,12 +41,6 @@
#endif
/* Some color escape codes for pretty log messages */
/* Alternate character set */
#define ACS(chr) "\e(0" chr "\e(B"
#define ACS_HORIZONTAL ACS("\x71")
#define ACS_VERTICAL ACS("\x78")
#define ACS_VERTRIGHT ACS("\x74")
#define CLR(clr, str) "\e[" XSTR(clr) "m" str "\e[0m"
#define CLR_GRY(str) CLR(30, str) /**< Print str in gray */
#define CLR_RED(str) CLR(31, str) /**< Print str in red */
@ -58,6 +52,29 @@
#define CLR_WHT(str) CLR(37, str) /**< Print str in white */
#define CLR_BLD(str) CLR( 1, str) /**< Print str in bold */
/* Alternate character set
*
* The suffixed of the BOX_ macro a constructed by
* combining the following letters in the written order:
* - U for a line facing upwards
* - D for a line facing downwards
* - L for a line facing leftwards
* - R for a line facing rightwards
*
* E.g. a cross can be constructed by combining all line fragments:
* BOX_UDLR
*/
#define BOX(chr) "\e(0" chr "\e(B"
#define BOX_LR BOX("\x71") /**< Boxdrawing: ─ */
#define BOX_UD BOX("\x78") /**< Boxdrawing: │ */
#define BOX_UDR BOX("\x74") /**< Boxdrawing: ├ */
#define BOX_UDLR BOX("\x6E") /**< Boxdrawing: ┼ */
#define BOX_UDL BOX("\x75") /**< Boxdrawing: ┤ */
#define BOX_ULR BOX("\x76") /**< Boxdrawing: ┴ */
#define BOX_UL BOX("\x6A") /**< Boxdrawing: ┘ */
#define BOX_DLR BOX("\x77") /**< Boxdrawing: ┘ */
#define BOX_DL BOX("\x6B") /**< Boxdrawing: ┘ */
/* CPP stringification */
#define XSTR(x) STR(x)
#define STR(x) #x

View file

@ -26,7 +26,7 @@ LIBEXT = $(BUILDDIR)/$(LIBEXT_NAME).so.$(LIBEXT_ABI_VERSION)
LIBEXT_SRCS += $(addprefix lib/, sample.c queue.c queue_signalled.c \
memory.c log.c shmem.c utils.c kernel/kernel.c list.c \
timing.c pool.c \
timing.c pool.c log_helper.c \
)
LIBEXT_LDFLAGS = -shared

View file

@ -31,7 +31,7 @@ LIB_SRCS += $(addprefix lib/nodes/, file.c cbuilder.c shmem.c signal.c) \
log.c log_config.c utils.c super_node.c hist.c timing.c pool.c \
list.c queue.c queue_signalled.c memory.c advio.c web.c api.c \
plugin.c node_type.c stats.c mapping.c sample_io.c shmem.c \
json.c crypt.c compat.c \
json.c crypt.c compat.c log_table.c log_helper.c \
)
LIB_LDFLAGS = -shared

View file

@ -166,27 +166,36 @@ void hist_plot(struct hist *h)
if (h->data[i] > max)
max = h->data[i];
}
struct table_column cols[] = {
{ -9, "Value", "%+9.3g", NULL, TABLE_ALIGN_RIGHT },
{ -6, "Count", "%6ju", NULL, TABLE_ALIGN_RIGHT },
{ 0, "Plot", "%s", "occurences", TABLE_ALIGN_LEFT }
};
struct table table = {
.ncols = ARRAY_LEN(cols),
.cols = cols
};
/* Print plot */
stats("%9s | %5s | %s", "Value", "Count", "Plot");
line();
table_header(&table);
for (int i = 0; i < h->length; i++) {
double value = VAL(h, i);
hist_cnt_t cnt = h->data[i];
int bar = HIST_HEIGHT * ((double) cnt / max);
char *buf = 0;
buf = strcatf(&buf, "%+9.3g | %5ju | ", value, cnt);
int bar = cols[2]._width * ((double) cnt / max);
char *buf = strf("%s", "");
for (int i = 0; i < bar; i++)
buf = strcatf(&buf, "\u2588");
stats("%s", buf);
table_row(&table, value, cnt, buf);
free(buf);
}
line();
table_footer(&table);
}
char * hist_dump(struct hist *h)

View file

@ -110,8 +110,7 @@ static int stats_collect_periodic(struct hook *h)
{
struct stats_collect *p = h->_vd;
if (h->path->state == STATE_STARTED)
stats_print_periodic(&p->stats, p->output, p->format, p->verbose, h->path);
stats_print_periodic(&p->stats, p->output, p->format, p->verbose, h->path);
return 0;
}

174
lib/log.c
View file

@ -22,9 +22,10 @@
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>
#include "config.h"
#include "log.h"
@ -45,7 +46,11 @@ struct log default_log = {
.facilities = LOG_ALL,
.file = NULL,
.path = NULL,
.epoch = { -1 , -1 }
.epoch = { -1 , -1 },
.window = {
.ws_row = LOG_HEIGHT,
.ws_col = LOG_WIDTH
}
};
/** List of debug facilities as strings */
@ -80,8 +85,41 @@ static const char *facilities_strs[] = {
/** The current log indention level (per thread!). */
static __thread int indent = 0;
int log_indent(int levels)
{
int old = indent;
indent += levels;
return old;
}
int log_noindent()
{
int old = indent;
indent = 0;
return old;
}
void log_outdent(int *old)
{
indent = *old;
}
#endif
static void log_resize(int signal, siginfo_t *sinfo, void *ctx)
{
int ret;
ret = ioctl(STDOUT_FILENO, TIOCGWINSZ, &global_log->window);
if (ret)
return;
debug(LOG_LOG | 15, "New terminal size: %dx%x", global_log->window.ws_row, global_log->window.ws_col);
}
int log_init(struct log *l, int level, long facilitites)
{
int ret;
/* Register this log instance globally */
global_log = l;
@ -89,6 +127,26 @@ int log_init(struct log *l, int level, long facilitites)
l->facilities = facilitites;
l->file = stderr;
l->path = NULL;
l->window.ws_col = LOG_WIDTH;
l->window.ws_row = LOG_HEIGHT;
/* Register signal handler which is called whenever the
* terminal size changes. */
if (l->file == stderr) {
struct sigaction sa_resize = {
.sa_flags = SA_SIGINFO,
.sa_sigaction = log_resize
};
sigemptyset(&sa_resize.sa_mask);
ret = sigaction(SIGWINCH, &sa_resize, NULL);
if (ret)
return ret;
/* Try to get initial window size */
ioctl(STDERR_FILENO, TIOCGWINSZ, &global_log->window);
}
l->state = STATE_INITIALIZED;
@ -135,19 +193,6 @@ int log_destroy(struct log *l)
return 0;
}
int log_indent(int levels)
{
int old = indent;
indent += levels;
return old;
}
void log_outdent(int *old)
{
indent = *old;
}
#endif
int log_set_facility_expression(struct log *l, const char *expression)
{
bool negate;
@ -212,18 +257,15 @@ void log_vprint(struct log *l, const char *lvl, const char *fmt, va_list ap)
struct timespec ts = time_now();
char *buf = alloc(512);
/* Timestamp */
strcatf(&buf, "%10.3f ", time_delta(&l->epoch, &ts));
/* Severity */
strcatf(&buf, "%5s ", lvl);
/* Timestamp & Severity */
strcatf(&buf, "%10.3f %5s ", time_delta(&l->epoch, &ts), lvl);
/* Indention */
#ifdef __GNUC__
for (int i = 0; i < indent; i++)
strcatf(&buf, ACS_VERTICAL " ");
strcatf(&buf, "%s ", BOX_UD);
strcatf(&buf, ACS_VERTRIGHT " ");
strcatf(&buf, "%s ", BOX_UDR);
#endif
/* Format String */
@ -234,92 +276,6 @@ void log_vprint(struct log *l, const char *lvl, const char *fmt, va_list ap)
OpalPrint("VILLASnode: %s\n", buf);
#endif
fprintf(l->file ? l->file : stderr, "\33[2K\r%s\n", buf);
free(buf);
}
void line()
{
char buf[LOG_WIDTH];
memset(buf, 0x71, sizeof(buf));
log_print(global_log, "", "\b" ACS("%.*s"), LOG_WIDTH, buf);
}
void debug(long class, const char *fmt, ...)
{
va_list ap;
struct log *l = global_log ? global_log : &default_log;
int lvl = class & 0xFF;
int fac = class & ~0xFF;
if (((fac == 0) || (fac & l->facilities)) && (lvl <= l->level)) {
va_start(ap, fmt);
log_vprint(l, LOG_LVL_DEBUG, fmt, ap);
va_end(ap);
}
}
void info(const char *fmt, ...)
{
va_list ap;
struct log *l = global_log ? global_log : &default_log;
va_start(ap, fmt);
log_vprint(l, LOG_LVL_INFO, fmt, ap);
va_end(ap);
}
void warn(const char *fmt, ...)
{
va_list ap;
struct log *l = global_log ? global_log : &default_log;
va_start(ap, fmt);
log_vprint(l, LOG_LVL_WARN, fmt, ap);
va_end(ap);
}
void stats(const char *fmt, ...)
{
va_list ap;
struct log *l = global_log ? global_log : &default_log;
va_start(ap, fmt);
log_vprint(l, LOG_LVL_STATS, fmt, ap);
va_end(ap);
}
void error(const char *fmt, ...)
{
va_list ap;
struct log *l = global_log ? global_log : &default_log;
va_start(ap, fmt);
log_vprint(l, LOG_LVL_ERROR, fmt, ap);
va_end(ap);
die();
}
void serror(const char *fmt, ...)
{
va_list ap;
char *buf = NULL;
struct log *l = global_log ? global_log : &default_log;
va_start(ap, fmt);
vstrcatf(&buf, fmt, ap);
va_end(ap);
log_print(l, LOG_LVL_ERROR, "%s: %m (%u)", buf, errno);
free(buf);
die();
}
}

105
lib/log_helper.c Normal file
View file

@ -0,0 +1,105 @@
/** Logging and debugging routines
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* 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 3 of the License, or
* any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <errno.h>
#include "utils.h"
#include "log.h"
void debug(long class, const char *fmt, ...)
{
va_list ap;
struct log *l = global_log ? global_log : &default_log;
int lvl = class & 0xFF;
int fac = class & ~0xFF;
if (((fac == 0) || (fac & l->facilities)) && (lvl <= l->level)) {
va_start(ap, fmt);
log_vprint(l, LOG_LVL_DEBUG, fmt, ap);
va_end(ap);
}
}
void info(const char *fmt, ...)
{
va_list ap;
struct log *l = global_log ? global_log : &default_log;
va_start(ap, fmt);
log_vprint(l, LOG_LVL_INFO, fmt, ap);
va_end(ap);
}
void warn(const char *fmt, ...)
{
va_list ap;
struct log *l = global_log ? global_log : &default_log;
va_start(ap, fmt);
log_vprint(l, LOG_LVL_WARN, fmt, ap);
va_end(ap);
}
void stats(const char *fmt, ...)
{
va_list ap;
struct log *l = global_log ? global_log : &default_log;
va_start(ap, fmt);
log_vprint(l, LOG_LVL_STATS, fmt, ap);
va_end(ap);
}
void error(const char *fmt, ...)
{
va_list ap;
struct log *l = global_log ? global_log : &default_log;
va_start(ap, fmt);
log_vprint(l, LOG_LVL_ERROR, fmt, ap);
va_end(ap);
killme(SIGALRM);
}
void serror(const char *fmt, ...)
{
va_list ap;
char *buf = NULL;
struct log *l = global_log ? global_log : &default_log;
va_start(ap, fmt);
vstrcatf(&buf, fmt, ap);
va_end(ap);
log_print(l, LOG_LVL_ERROR, "%s: %m (%u)", buf, errno);
free(buf);
killme(SIGALRM);
}

170
lib/log_table.c Normal file
View file

@ -0,0 +1,170 @@
/** Print fancy tables.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* 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 3 of the License, or
* any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <stdlib.h>
#include <string.h>
#include "utils.h"
#include "log.h"
static int table_resize(struct table *t, int width)
{
int norm, flex, fixed, total;
t->width = width;
norm = 0;
flex = 0;
fixed = 0;
total = t->width - t->ncols * 2;
/* Normalize width */
for (int i = 0; i < t->ncols; i++) {
if (t->cols[i].width > 0)
norm += t->cols[i].width;
if (t->cols[i].width == 0)
flex++;
if (t->cols[i].width < 0)
fixed += -1 * t->cols[i].width;
}
for (int i = 0; i < t->ncols; i++) {
if (t->cols[i].width > 0)
t->cols[i]._width = t->cols[i].width * (float) (total - fixed) / norm;
if (t->cols[i].width == 0)
t->cols[i]._width = (float) (total - fixed) / flex;
if (t->cols[i].width < 0)
t->cols[i]._width = -1 * t->cols[i].width;
}
return 0;
}
void table_header(struct table *t)
{ NOINDENT
struct log *l = global_log ? global_log : &default_log;
if (t->width != l->window.ws_col - 24)
table_resize(t, l->window.ws_col - 24);
char *line1 = strf("\b\b" BOX_UD);
char *line0 = strf("\b");
char *line2 = strf("\b");
for (int i = 0; i < t->ncols; i++) {
char *col = strf(CLR_BLD("%s"), t->cols[i].title);
if (t->cols[i].unit)
strcatf(&col, " (" CLR_YEL("%s") ")", t->cols[i].unit);
int l = strlenp(col);
int r = strlen(col);
int w = t->cols[i]._width + r - l;
if (t->cols[i].align == TABLE_ALIGN_LEFT)
strcatf(&line1, " %-*.*s " BOX_UD, w, w, col);
else
strcatf(&line1, " %*.*s " BOX_UD, w, w, col);
for (int j = 0; j < t->cols[i]._width + 2; j++) {
strcatf(&line0, "%s", BOX_LR);
strcatf(&line2, "%s", BOX_LR);
}
if (i == t->ncols - 1) {
strcatf(&line0, "%s", BOX_DL);
strcatf(&line2, "%s", BOX_UDL);
}
else {
strcatf(&line0, "%s", BOX_DLR);
strcatf(&line2, "%s", BOX_UDLR);
}
free(col);
}
stats(line0);
stats(line1);
stats(line2);
free(line0);
free(line1);
free(line2);
}
void table_row(struct table *t, ...)
{ NOINDENT
struct log *l = global_log ? global_log : &default_log;
if (t->width != l->window.ws_col - 24) {
table_resize(t, l->window.ws_col - 24);
table_header(t);
}
va_list args;
va_start(args, t);
char *line = strf("\b\b" BOX_UD);
for (int i = 0; i < t->ncols; ++i) {
char *col = vstrf(t->cols[i].format, args);
int l = strlenp(col);
int r = strlen(col);
int w = t->cols[i]._width + r - l;
if (t->cols[i].align == TABLE_ALIGN_LEFT)
strcatf(&line, " %-*.*s " BOX_UD, w, w, col);
else
strcatf(&line, " %*.*s " BOX_UD, w, w, col);
free(col);
}
va_end(args);
stats(line);
free(line);
}
void table_footer(struct table *t)
{ NOINDENT
struct log *l = global_log ? global_log : &default_log;
if (t->width != l->window.ws_col - 24)
table_resize(t, l->window.ws_col - 24);
char *line = strf("\b");
for (int i = 0; i < t->ncols; i++) {
for (int j = 0; j < t->cols[i]._width + 2; j++)
strcatf(&line, BOX_LR);
if (i == t->ncols - 1)
strcatf(&line, "%s", BOX_UL);
else
strcatf(&line, "%s", BOX_ULR);
}
stats(line);
free(line);
}

View file

@ -34,7 +34,7 @@ static struct stats_desc {
const char *unit;
const char *desc;
int hist_buckets;
} stats_table[] = {
} stats_metrics[] = {
{ "skipped", "samples", "skipped samples by hooks", 25 },
{ "reorderd", "samples", "reordered samples", 25 },
{ "gap_sample", "seconds", "inter message timestamps (as sent by remote)", 25 },
@ -116,7 +116,7 @@ json_t * stats_json(struct stats *s)
json_t *obj = json_object();
for (int i = 0; i < STATS_COUNT; i++) {
struct stats_desc *desc = &stats_table[i];
struct stats_desc *desc = &stats_metrics[i];
json_t *stats = hist_json(&s->histograms[i]);
@ -145,19 +145,36 @@ void stats_reset(struct stats *s)
}
}
static struct table_column stats_cols[] = {
{ 35, "Path", "%s", NULL, TABLE_ALIGN_LEFT },
{ 10, "Cnt", "%ju", "p", TABLE_ALIGN_RIGHT },
{ 10, "OWD", "%f", "S", TABLE_ALIGN_RIGHT },
{ 10, "Rate", "%f", "p/S", TABLE_ALIGN_RIGHT },
{ 10, "Drop", "%ju", "p", TABLE_ALIGN_RIGHT },
{ 10, "Skip", "%ju", "p", TABLE_ALIGN_RIGHT }
};
static struct table stats_table = {
.ncols = ARRAY_LEN(stats_cols),
.cols = stats_cols
};
void stats_print_header(enum stats_format fmt)
{
#define UNIT(u) "(" YEL(u) ")"
switch (fmt) {
case STATS_FORMAT_HUMAN:
stats("%-40s|%19s|%19s|%19s|%19s|", "Source " MAG("=>") " Destination",
"OWD" UNIT("S") " ",
"Rate" UNIT("p/S") " ",
"Drop" UNIT("p") " ",
"Skip" UNIT("p") " "
);
line();
table_header(&stats_table);
break;
default: { }
}
}
void stats_print_footer(enum stats_format fmt)
{
switch (fmt) {
case STATS_FORMAT_HUMAN:
table_footer(&stats_table);
break;
default: { }
@ -168,7 +185,9 @@ void stats_print_periodic(struct stats *s, FILE *f, enum stats_format fmt, int v
{
switch (fmt) {
case STATS_FORMAT_HUMAN:
stats("%-40.40s|%10f|%10f|%10ju|%10ju|", path_name(p),
table_row(&stats_table,
path_name(p),
s->histograms[STATS_OWD].total,
s->histograms[STATS_OWD].last,
1.0 / s->histograms[STATS_GAP_SAMPLE].last,
s->histograms[STATS_REORDERED].total,
@ -191,7 +210,7 @@ void stats_print(struct stats *s, FILE *f, enum stats_format fmt, int verbose)
switch (fmt) {
case STATS_FORMAT_HUMAN:
for (int i = 0; i < STATS_COUNT; i++) {
struct stats_desc *desc = &stats_table[i];
struct stats_desc *desc = &stats_metrics[i];
stats("%s: %s", desc->name, desc->desc);
hist_print(&s->histograms[i], verbose);
@ -231,7 +250,7 @@ void stats_send(struct stats *s, struct node *n)
enum stats_id stats_lookup_id(const char *name)
{
for (int i = 0; i < STATS_COUNT; i++) {
struct stats_desc *desc = &stats_table[i];
struct stats_desc *desc = &stats_metrics[i];
if (!strcmp(desc->name, name))
return i;

View file

@ -47,6 +47,9 @@ struct super_node sn;
static void quit(int signal, siginfo_t *sinfo, void *ctx)
{
if (sn.stats > 0)
stats_print_footer(STATS_FORMAT_HUMAN);
super_node_stop(&sn);
super_node_destroy(&sn);
@ -119,7 +122,7 @@ int main(int argc, char *argv[])
super_node_start(&sn);
if (sn.stats > 0)
stats_print_header();
stats_print_header(STATS_FORMAT_HUMAN);
struct timespec now, last = time_now();