#define _GNU_SOURCE
#include <stdio.h>
#include <libintl.h>
#include <math.h>
#include <poll.h>
#include <stdarg.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <ncurses.h>
#include "error.h"
#include "colors.h"
#include "gen.h"
#include "main.h"
#include "kalman.h"
#ifdef FW
#include "fft.h"
#endif
#include "utils.h"
char win_resize = 0;
WINDOW *w_stats = NULL, *w_line1 = NULL, *w_slow = NULL, *w_line2 = NULL, *w_fast = NULL;
int stats_h = 10;
int logs_n = 0, slow_n = 0, fast_n = 0;
char **slow_history = NULL, **fast_history = NULL;
int window_history_n = 0;
char use_colors = 0;
double graph_limit = MY_DOUBLE_INF;
double hz = 1.0;
double *history = NULL, *history_temp = NULL, *history_fft_magn = NULL, *history_fft_phase = NULL;
char *history_set = NULL;
int history_n = 0;
char draw_phase = 0;
char pause_graphs = 0;
typedef enum { C_WHITE = 0, C_GREEN, C_YELLOW, C_BLUE, C_MAGENTA, C_CYAN, C_RED } color_t;
void update_terminal(void)
{
wnoutrefresh(w_stats);
wnoutrefresh(w_slow);
wnoutrefresh(w_fast);
doupdate();
}
void create_windows(void)
{
char *r_a = gettext("realloc issue");
int nr = 0;
if (w_stats)
{
delwin(w_stats);
delwin(w_line1);
delwin(w_slow);
delwin(w_line2);
delwin(w_fast);
}
#ifdef FW
fft_free();
fft_init(max_x);
#endif
if (max_x > history_n)
{
history = (double *)realloc(history, sizeof(double) * max_x);
if (!history)
error_exit(r_a);
history_temp = (double *)realloc(history_temp, sizeof(double) * max_x);
if (!history_temp)
error_exit(r_a);
/* halve of it is enough really */
history_fft_magn = (double *)realloc(history_fft_magn, sizeof(double) * max_x);
if (!history_fft_magn)
error_exit(r_a);
history_fft_phase = (double *)realloc(history_fft_phase, sizeof(double) * max_x);
if (!history_fft_phase)
error_exit(r_a);
history_set = (char *)realloc(history_set, sizeof(char) * max_x);
if (!history_set)
error_exit(r_a);
memset(&history[history_n], 0x00, (max_x - history_n) * sizeof(double));
memset(&history_set[history_n], 0x00, (max_x - history_n) * sizeof(char));
history_n = max_x;
}
if ((int)max_y > window_history_n)
{
slow_history = (char **)realloc(slow_history, sizeof(char *) * max_y);
if (!slow_history)
error_exit(r_a);
fast_history = (char **)realloc(fast_history, sizeof(char *) * max_y);
if (!fast_history)
error_exit(r_a);
memset(&slow_history[window_history_n], 0x00, (max_y - window_history_n) * sizeof(char *));
memset(&fast_history[window_history_n], 0x00, (max_y - window_history_n) * sizeof(char *));
window_history_n = max_y;
}
w_stats = newwin(stats_h, max_x, 0, 0);
scrollok(w_stats, false);
w_line1 = newwin(1, max_x, stats_h, 0);
scrollok(w_line1, false);
wnoutrefresh(w_line1);
logs_n = max_y - (stats_h + 1 + 1);
fast_n = logs_n * 11 / 20;
slow_n = logs_n - fast_n;
w_slow = newwin(slow_n, max_x, (stats_h + 1), 0);
scrollok(w_slow, true);
w_line2 = newwin(1, max_x, (stats_h + 1) + slow_n, 0);
scrollok(w_line2, false);
wnoutrefresh(w_line2);
w_fast = newwin(fast_n, max_x, (stats_h + 1) + slow_n + 1, 0);
scrollok(w_fast, true);
wattron(w_line1, A_REVERSE);
wattron(w_line2, A_REVERSE);
for(nr=0; nr<max_x; nr++)
{
wprintw(w_line1, " ");
wprintw(w_line2, " ");
}
wattroff(w_line2, A_REVERSE);
wattroff(w_line1, A_REVERSE);
wnoutrefresh(w_line1);
wnoutrefresh(w_line2);
doupdate();
signal(SIGWINCH, handler);
}
void myprintloc(WINDOW *w, int y, int x, const char *fmt, ...)
{
char *line = NULL;
int line_len = 0;
va_list ap;
va_start(ap, fmt);
line_len = vasprintf(&line, fmt, ap);
va_end(ap);
wmove(w, y, x);
if (use_colors)
{
int index = 0;
for(index=0; index<line_len; index++)
{
static int clr = C_WHITE, att = A_NORMAL;
if (line[index] == COLOR_ESCAPE[0])
{
switch(line[++index])
{
case '1':
clr = C_RED;
break;
case '2':
clr = C_BLUE;
break;
case '3':
clr = C_GREEN;
break;
case '4':
clr = C_YELLOW;
break;
case '5':
clr = C_MAGENTA;
break;
case '6':
clr = C_CYAN;
break;
case '7':
clr = C_WHITE;
break;
case '8':
att = A_BOLD;
break;
case '9':
att = A_NORMAL;
break;
}
wattr_set(w, att, clr, NULL);
}
else
{
waddch(w, line[index]);
}
}
}
else
{
mvwprintw(w, y, x, "%s", line);
}
free(line);
}
void myprint(WINDOW *w, const char *what)
{
if (use_colors)
{
int y = -1, x = -1;
getyx(w, y, x);
myprintloc(w, y, x, what);
}
else
{
wprintw(w, what);
}
}
void recreate_terminal(void)
{
int index = 0;
determine_terminal_size(&max_y, &max_x);
resizeterm(max_y, max_x);
endwin();
refresh();
create_windows();
for(index = window_history_n - 1; index >= 0; index--)
{
if (slow_history[index])
myprint(w_slow, slow_history[index]);
if (fast_history[index])
myprint(w_fast, fast_history[index]);
}
doupdate();
win_resize = 0;
}
void init_ncurses_ui(double graph_limit_in, double hz_in, char use_colors_in)
{
graph_limit = graph_limit_in;
hz = hz_in;
use_colors = use_colors_in;
initscr();
start_color();
keypad(stdscr, TRUE);
intrflush(stdscr, FALSE);
noecho();
refresh();
nodelay(stdscr, FALSE);
meta(stdscr, TRUE); /* enable 8-bit input */
idlok(stdscr, TRUE); /* may give a little clunky screenredraw */
idcok(stdscr, TRUE); /* may give a little clunky screenredraw */
leaveok(stdscr, FALSE);
init_pair(C_WHITE, COLOR_WHITE, COLOR_BLACK);
init_pair(C_CYAN, COLOR_CYAN, COLOR_BLACK);
init_pair(C_MAGENTA, COLOR_MAGENTA, COLOR_BLACK);
init_pair(C_BLUE, COLOR_BLUE, COLOR_BLACK);
init_pair(C_YELLOW, COLOR_YELLOW, COLOR_BLACK);
init_pair(C_GREEN, COLOR_GREEN, COLOR_BLACK);
init_pair(C_RED, COLOR_RED, COLOR_BLACK);
kalman_init(0.0);
recreate_terminal();
}
void end_ncurses(void)
{
int index = 0;
for(index=0; index<window_history_n; index++)
{
free(slow_history[index]);
free(fast_history[index]);
}
free(slow_history);
free(fast_history);
if (w_stats)
{
delwin(w_stats);
delwin(w_line1);
delwin(w_slow);
delwin(w_line2);
delwin(w_fast);
}
endwin();
free(history);
free(history_set);
#ifdef FW
fft_free();
fft_stop();
#endif
free(history_temp);
free(history_fft_phase);
free(history_fft_magn);
}
void fast_log(const char *fmt, ...)
{
char buffer[4096] = { 0 };
va_list ap;
va_start(ap, fmt);
vsnprintf(buffer, sizeof buffer, fmt, ap);
va_end(ap);
free(fast_history[window_history_n - 1]);
memmove(&fast_history[1], &fast_history[0], (window_history_n - 1) * sizeof(char *));
fast_history[0] = strdup(buffer);
myprint(w_fast, buffer);
if (win_resize)
recreate_terminal();
}
void slow_log(const char *fmt, ...)
{
char buffer[4096] = { 0 };
va_list ap;
va_start(ap, fmt);
vsnprintf(buffer, sizeof buffer, fmt, ap);
va_end(ap);
free(slow_history[window_history_n - 1]);
memmove(&slow_history[1], &slow_history[0], (window_history_n - 1) * sizeof(char *));
slow_history[0] = strdup(buffer);
myprint(w_slow, buffer);
if (win_resize)
recreate_terminal();
}
void my_beep(void)
{
beep();
}
void status_line(char *fmt, ...)
{
char *line = NULL;
va_list ap;
wattron(w_line2, A_REVERSE);
wmove(w_line2, 0, 0);
va_start(ap, fmt);
vasprintf(&line, fmt, ap);
myprint(w_line2, line);
free(line);
va_end(ap);
wattroff(w_line2, A_REVERSE);
wnoutrefresh(w_line2);
if (win_resize)
recreate_terminal();
}
void draw_column(WINDOW *win, int x, int height, char overflow, char limitter)
{
void *dummy = NULL;
int y = 0, end_y = 0, win_h = 0, win_w = 0;
getmaxyx(win, win_h, win_w);
(void)win_w; /* silence warnings */
end_y = max(0, win_h - height);
for(y=win_h - 1; y >= end_y; y--)
mvwchgat(win, y, x, 1, A_REVERSE, C_YELLOW, dummy);
if (limitter)
mvwchgat(win, 0, x, 1, A_REVERSE, C_BLUE, dummy);
else if (overflow)
mvwchgat(win, 0, x, 1, A_REVERSE, C_RED, dummy);
else if (height == 0)
mvwchgat(win, win_h - 1, x, 1, A_REVERSE, C_GREEN, dummy);
}
void draw_rad_column(WINDOW *win, int x, double val)
{
void *dummy = NULL;
int y = 0, end_y = 0, win_h = 0, win_w = 0;
int center_y = 0;
getmaxyx(win, win_h, win_w);
(void)win_w; /* silence warnings */
center_y = win_h / 2;
end_y = (int)((double)(win_h / 2) * ((val / PI) + 1.0));
if (end_y > center_y)
{
for(y=center_y; y<end_y; y++)
mvwchgat(win, y, x, 1, A_REVERSE, C_YELLOW, dummy);
}
else
{
for(y=end_y; y<center_y; y++)
mvwchgat(win, y, x, 1, A_REVERSE, C_YELLOW, dummy);
}
}
double get_cur_scc()
{
double scc_val = 0.0;
double prev_val = 0.0, u0 = 0.0;
double t[3] = { 0 };
int loop = 0, n = 0;
char first = 1;
t[0] = t[1] = t[2] = 0.0;
for(loop=0; loop<history_n; loop++)
{
double cur_val = history[loop];
if (!history_set[loop])
continue;
if (first)
{
prev_val = 0;
u0 = cur_val;
first = 0;
}
else
{
t[0] += prev_val * cur_val;
}
t[1] = t[1] + cur_val;
t[2] = t[2] + (cur_val * cur_val);
prev_val = cur_val;
n++;
}
t[0] = t[0] + prev_val * u0;
t[1] = t[1] * t[1];
scc_val = (double)n * t[2] - t[1];
if (scc_val == 0.0)
return -1.0;
return ((double)n * t[0] - t[1]) / scc_val;
}
#ifdef FW
void draw_fft(void)
{
double mx_mag = 0.0;
int index = 0, highest = 0;
int cx = 0, cy = 0;
/* double max_freq = hz / 2.0; */
double highest_freq = 0, avg_freq_index = 0.0, total_val = 0.0, avg_freq = 0.0;
int dummy = 0;
getyx(w_slow, cy, cx);
for(index=0; index<max_x; index++)
{
double val = 0.0;
if (history_set[index])
val = history[index];
else
val = index > 0 ? history[index - 1] : 0;
if (val > graph_limit)
val = graph_limit;
history_temp[index] = val;
}
fft_do(history_temp, history_fft_magn, history_fft_phase);
for(index=1; index<max_x/2; index++)
{
avg_freq_index += (double)index * history_fft_magn[index];
total_val += history_fft_magn[index];
if (history_fft_magn[index] > mx_mag)
{
mx_mag = history_fft_magn[index];
highest = index;
}
}
highest_freq = (hz / (double)max_x) * (double)highest;
avg_freq_index /= total_val;
avg_freq = (hz / (double)max_x) * avg_freq_index;
wattron(w_line1, A_REVERSE);
myprintloc(w_line1, 0, 38, gettext("highest: %6.2fHz, avg: %6.2fHz"), highest_freq, avg_freq);
wattroff(w_line1, A_REVERSE);
wnoutrefresh(w_line1);
dummy = max_x / 2 + 1;
if (draw_phase)
{
int y = 0;
for(y=0; y<slow_n; y++)
mvwchgat(w_slow, y, dummy, 1, A_REVERSE, C_WHITE, NULL);
for(index=0; index<slow_n; index++)
mvwchgat(w_slow, index, 0, max_x, A_NORMAL, C_WHITE, NULL);
for(index=1; index<dummy - 1; index++)
draw_rad_column(w_slow, index - 1, history_fft_phase[index]);
}
else
{
for(index=0; index<slow_n; index++)
mvwchgat(w_slow, index, max_x / 2, max_x / 2, A_NORMAL, C_WHITE, NULL);
}
for(index=1; index<dummy; index++)
{
int height_magn = (int)((double)slow_n * (history_fft_magn[index] / mx_mag));
draw_column(w_slow, max_x / 2 + index - 1, height_magn, 0, 0);
}
wmove(w_slow, cy, cx);
wnoutrefresh(w_slow);
}
#endif
double calc_trend()
{
int half = history_n / 2, index = 0;
double v1 = 0.0, v2 = 0.0;
int n_v1 = 0, n_v2 = 0;
for(index=0; index<half; index++)
{
if (!history_set[index])
continue;
v1 += history[index];
n_v1++;
}
for(index=half; index<history_n; index++)
{
if (!history_set[index])
continue;
v2 += history[index];
n_v2++;
}
if (n_v2 == 0 || n_v1 == 0)
return 0;
v1 /= (double)n_v1;
v2 /= (double)n_v2;
return (v1 - v2) / (v2 / 100.0);
}
void draw_graph(double val)
{
int index = 0, loop_n = min(max_x, history_n), n = 0, n2 = 0;
double avg = 0, sd = 0;
double avg2 = 0, sd2 = 0;
double mi = MY_DOUBLE_INF, ma = -MY_DOUBLE_INF, diff = 0.0;
for(index=0; index<loop_n; index++)
{
double val = history[index];
if (!history_set[index])
continue;
mi = min(val, mi);
ma = max(val, ma);
avg += val;
sd += val * val;
n++;
}
if (!n)
return;
avg /= (double)n;
sd = sqrt((sd / (double)n) - pow(avg, 2.0));
mi = max(mi, avg - sd);
ma = min(ma, avg + sd);
for(index=0; index<loop_n; index++)
{
double val = history[index];
if (!history_set[index])
continue;
if (val < mi || val > ma)
continue;
avg2 += val;
sd2 += val * val;
n2++;
}
if (n2)
{
avg2 /= (double)n2;
sd2 = sqrt((sd2 / (double)n2) - pow(avg2, 2.0));
mi = max(mi, avg2 - sd2);
ma = min(ma, avg2 + sd2);
diff = ma - mi;
if (diff == 0.0)
diff = 1.0;
wattron(w_line1, A_REVERSE);
myprintloc(w_line1, 0, 0, gettext("graph range: %7.2fms - %7.2fms "), mi, ma);
wattroff(w_line1, A_REVERSE);
wnoutrefresh(w_line1);
/* fprintf(stderr, "%d| %f %f %f %f\n", h_stats.n, mi, avg, ma, sd); */
for(index=0; index<loop_n; index++)
{
char overflow = 0, limitter = 0;
double val = 0, height = 0;
int i_h = 0, x = max_x - (1 + index);
if (!history_set[index])
{
mvwchgat(w_stats, stats_h - 1, x, 1, A_REVERSE, C_CYAN, NULL);
continue;
}
if (history[index] < graph_limit)
val = history[index];
else
{
val = graph_limit;
limitter = 1;
}
height = (val - mi) / diff;
if (height > 1.0)
{
height = 1.0;
overflow = 1;
}
i_h = (int)(height * stats_h);
/* fprintf(stderr, "%d %f %f %d %d\n", index, history[index], height, i_h, overflow); */
draw_column(w_stats, x, i_h, overflow, limitter);
}
}
}
void show_stats_t(int y, int x, char *header, stats_t *data, char abbreviate)
{
if (data -> valid)
{
char *cur_str = format_value(data -> cur, 6, 2, abbreviate);
char *min_str = format_value(data -> min, 6, 2, abbreviate);
char *avg_str = format_value(data -> avg / (double)data -> n, 6, 2, abbreviate);
char *max_str = format_value(data -> max, 6, 2, abbreviate);
char *sd_str = format_value(calc_sd(data), 6, 2, abbreviate);
myprintloc(w_stats, y, x, "%s: %s %s %s %s %s", header,
data -> cur_valid ? cur_str : gettext(" n/a"),
min_str, avg_str, max_str, sd_str);
free(sd_str);
free(max_str);
free(avg_str);
free(min_str);
free(cur_str);
}
else
{
myprintloc(w_stats, y, x, gettext("%s: n/a"), header);
}
}
void update_stats(stats_t *resolve, stats_t *connect, stats_t *request, stats_t *total, stats_t *ssl_setup, int n_ok, int n_fail, const char *last_connect_str, const char *fp, char use_tfo, char dg, stats_t *st_to, stats_t *tcp_rtt_stats, int re_tx, int pmtu, int tos, stats_t *close_st, stats_t *t_write, int n_cookies, char abbreviate, stats_t *stats_header_size)
{
double k = 0.0;
char force_redraw = 0;
struct pollfd p = { 0, POLLIN, 0 };
werase(w_stats);
if (n_ok)
{
char buffer[4096] = { 0 }, *scc_str = NULL, *kalman_str = NULL;
int buflen = 0;
myprintloc(w_stats, 0, 0, " %6s %6s %6s %6s %6s", gettext("latest"), gettext("min"), gettext("avg"), gettext("max"), gettext("sd"));
show_stats_t(1, 0, gettext("resolve"), resolve, abbreviate);
show_stats_t(2, 0, gettext("connect"), connect, abbreviate);
show_stats_t(3, 0, gettext("ssl "), ssl_setup, abbreviate);
show_stats_t(4, 0, gettext("send "), t_write, abbreviate);
show_stats_t(5, 0, gettext("request"), request, abbreviate);
show_stats_t(6, 0, gettext("close "), close_st, abbreviate);
show_stats_t(7, 0, gettext("total "), total, abbreviate);
scc_str = format_value(get_cur_scc(), 5, 3, abbreviate);
kalman_str = format_value(kalman_do(total -> cur), 5, 3, abbreviate);
myprintloc(w_stats, 8, 0, gettext("ok: %3d, fail: %3d%s, scc: %s, kalman: %s"), n_ok, n_fail, use_tfo ? gettext(", with TFO") : "", scc_str, kalman_str);
free(kalman_str);
free(scc_str);
if (max_x >= 44 * 2 + 1)
{
double trend = calc_trend();
char trend_dir = ' ';
myprintloc(w_stats, 0, 45, " %6s %6s %6s %6s %6s", gettext("cur"), gettext("min"), gettext("avg"), gettext("max"), gettext("sd"));
show_stats_t(1, 45, gettext("t offst"), st_to, abbreviate);
#if defined(linux) || defined(__FreeBSD__)
show_stats_t(2, 45, gettext("tcp rtt"), tcp_rtt_stats, abbreviate);
#endif
show_stats_t(3, 45, gettext("headers"), stats_header_size, abbreviate);
if (trend < 0)
trend_dir = '-';
else if (trend > 0)
trend_dir = '+';
myprintloc(w_stats, 8, 48, gettext("# cookies: %d"), n_cookies);
#ifdef linux
myprintloc(w_stats, 9, 48, gettext("trend: %c%6.2f%%, re-tx: %2d, pmtu: %5d, TOS: %02x"), trend_dir, fabs(trend), re_tx, pmtu, tos);
#else
myprintloc(w_stats, 9, 48, gettext("trend: %c%6.2f%%, TOS: %02x"), trend_dir, fabs(trend), tos);
#endif
}
buflen = snprintf(buffer, sizeof buffer, gettext("HTTP rc: %s, SSL fp: %s"), last_connect_str, fp ? fp : gettext("n/a"));
if (buflen <= max_x)
myprintloc(w_stats, 9, 0, "%s", buffer);
else
{
static char prev_sf[48] = { 0 };
myprintloc(w_stats, 9, 0, gettext("http result code: %s"), last_connect_str);
if (fp && strcmp(prev_sf, fp))
{
slow_log(gettext("\nSSL fingerprint: %s"), fp);
memcpy(prev_sf, fp, 47);
}
}
}
memmove(&history[1], &history[0], (history_n - 1) * sizeof(double));
memmove(&history_set[1], &history_set[0], (history_n - 1) * sizeof(char));
history[0]= total -> cur;
history_set[0] = 1;
if (poll(&p, 1, 0) == 1 && p.revents == POLLIN)
{
int c = getch();
if (c == 12) /* ^L */
force_redraw = 1;
if (c == 'H')
pause_graphs = !pause_graphs;
if (c == 'q')
stop = 1;
}
if (dg && !pause_graphs)
{
draw_graph(k);
#ifdef FW
draw_fft();
#endif
}
wnoutrefresh(w_stats);
if (win_resize || force_redraw)
recreate_terminal();
}