Alien-libhisto
view release on metacpan or search on metacpan
bundled/tools/src/cmd_top.c view on Meta::CPAN
/*
* CLI subcommand histo top: real-time interactive terminal monitoring TUI.
*/
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE 1
#endif
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#endif
#include "cli_common.h"
#include "cli_palette.h"
#include "tui_term.h"
#include "tui_engine.h"
#include "histo/fit.h"
#include "histo/kde.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if !defined(_WIN32)
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#endif
#include <stdbool.h>
#include <ctype.h>
#include <math.h>
typedef enum {
VIEW_1D_BARS = 0,
VIEW_SPARKLINE,
VIEW_CDF,
VIEW_2D_HEATMAP
} tui_view_mode_t;
typedef enum {
SCALE_LINEAR = 0,
SCALE_LOG_Y,
SCALE_LOG_X,
SCALE_LOG_LOG
} tui_scale_mode_t;
typedef enum {
MODAL_NONE = 0,
MODAL_HELP,
MODAL_SCALE_PICKER
} tui_modal_type_t;
typedef struct {
tui_view_mode_t view_mode;
tui_scale_mode_t scale_mode;
tui_modal_type_t modal;
histo_palette_t palette;
bool show_kde;
bool show_fit;
bool show_errors;
bool show_legend; /* 2D: color range reference bar */
bool show_y_axis; /* 1D: count axis ruler tick line */
bool monochrome;
bool paused;
bool cmd_active;
bool log_z; /* 2D: logarithmic intensity scaling */
bool compact_header;
bool auto_bins;
int inspect_row;
char cmd_buf[256];
size_t cmd_len;
char status_msg[128];
double status_msg_time;
histo_t *frozen_snapshot;
histo2d_t *frozen_snapshot_2d;
} tui_state_t;
static const char *const BLOCKS_UTF8[9] = { " ", "â", "â", "â", "â", "â", "â", "â", "â" };
static void render_top_border(tui_frame_t *f, const char *title, const char *badge, int width) {
tui_frame_printf(f, "\033[1mââ %s ", title);
int title_cols = 3 + (int)strlen(title) + 1; // "ââ " (3) + title + " " (1)
int badge_cols = (badge && badge[0]) ? (tui_visual_width(badge) + 4) : 2; // " " + badge + " ââ"
int dashes = width - title_cols - badge_cols;
if (dashes < 0) dashes = 0;
for (int i = 0; i < dashes; ++i) tui_frame_puts(f, "â");
if (badge && badge[0]) {
tui_frame_printf(f, " %s ââ\033[0m\r\n", badge);
} else {
tui_frame_puts(f, "ââ\033[0m\r\n");
}
}
static void render_divider(tui_frame_t *f, int width) {
tui_frame_puts(f, "â");
for (int i = 0; i < width - 2; ++i) tui_frame_puts(f, "â");
tui_frame_puts(f, "â¤\r\n");
}
static void render_bottom_border(tui_frame_t *f, int width, bool newline) {
tui_frame_puts(f, "â");
for (int i = 0; i < width - 2; ++i) tui_frame_puts(f, "â");
tui_frame_puts(f, "â");
if (newline) tui_frame_puts(f, "\r\n");
}
static void render_2d_heatmap_viewport(tui_frame_t *f, const tui_state_t *st, const histo2d_t *h, int width, int max_rows) {
if (!h || max_rows <= 0) {
for (int r = 0; r < max_rows; ++r) tui_render_row(f, "", width, true);
return;
}
uint32_t nx = histo2d_nbins_x(h);
uint32_t ny = histo2d_nbins_y(h);
if (nx == 0 || ny == 0) {
for (int r = 0; r < max_rows; ++r) tui_render_row(f, "", width, true);
return;
}
histo2d_axis_t ax, ay;
histo2d_axis_x(h, &ax);
histo2d_axis_y(h, &ay);
double max_content = 0.0;
for (uint32_t ix = 0; ix < nx; ++ix) {
for (uint32_t iy = 0; iy < ny; ++iy) {
double c = 0.0;
histo2d_bin_content(h, ix, iy, &c);
if (c > max_content) max_content = c;
}
}
double max_scaled = (st->log_z && max_content > 0.0) ? log10(max_content + 1.0) : max_content;
if (max_scaled <= 0.0) max_scaled = 1.0;
/* Y-axis label width: " yval â " = ~10 chars */
int label_cols = 10;
/* Available width for heatmap cells: 2 chars per X bin */
int avail_cols = width - label_cols - 4;
uint32_t x_step = 1;
if ((int)(nx * 2) > avail_cols && avail_cols > 0) {
x_step = (nx * 2 + (uint32_t)avail_cols - 1) / (uint32_t)avail_cols;
}
/* Y rows: map ny bins to max_rows, stepping if needed */
/* Reserve 2 rows for X-axis line and labels, plus 1 if legend active */
int leg_rows = st->show_legend ? 1 : 0;
int heatmap_rows = max_rows - 2 - leg_rows;
if (heatmap_rows < 1) heatmap_rows = 1;
uint32_t y_step = (ny > (uint32_t)heatmap_rows) ? (ny + (uint32_t)heatmap_rows - 1) / (uint32_t)heatmap_rows : 1;
int rows_drawn = 0;
/* Render Y rows from top (ny-1) to bottom (0) */
for (int iy = (int)ny - 1; iy >= 0 && rows_drawn < heatmap_rows; iy -= (int)y_step) {
char row_buf[4096];
double ymin_b, ymax_b, dummy;
histo2d_bin_bounds(h, 0, (uint32_t)iy, &dummy, &dummy, &ymin_b, &ymax_b);
double y_center = 0.5 * (ymin_b + ymax_b);
int pos = snprintf(row_buf, sizeof(row_buf), "%7.1f â ", y_center);
for (uint32_t ix = 0; ix < nx && pos + 32 < (int)sizeof(row_buf); ix += x_step) {
double c = 0.0;
histo2d_bin_content(h, ix, (uint32_t)iy, &c);
double val_scaled = (st->log_z && c > 0.0) ? log10(c + 1.0) : c;
double frac = (val_scaled > 0.0) ? (val_scaled / max_scaled) : 0.0;
if (st->monochrome) {
static const char density[] = " .:-=+*#%@";
int idx = (int)(frac * 9.0);
if (idx < 0) idx = 0;
if (idx > 9) idx = 9;
pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "%c ", density[idx]);
} else {
char bg[32];
histo_palette_sample_ansi_bg(st->palette, frac, bg, sizeof(bg));
pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "%s \033[0m", bg);
}
}
tui_render_row(f, row_buf, width, true);
rows_drawn++;
}
/* X-axis border row */
if (rows_drawn < max_rows) {
char axis_buf[4096];
int pos = snprintf(axis_buf, sizeof(axis_buf), " â");
uint32_t x_cells = (nx + x_step - 1) / x_step;
for (uint32_t ix = 0; ix < x_cells && pos + 8 < (int)sizeof(axis_buf); ++ix) {
pos += snprintf(axis_buf + pos, sizeof(axis_buf) - pos, "ââ");
}
pos += snprintf(axis_buf + pos, sizeof(axis_buf) - pos, "ââ");
tui_render_row(f, axis_buf, width, true);
rows_drawn++;
}
/* X-axis labels row */
if (rows_drawn < max_rows) {
char label_buf[512];
char min_str[32], max_str[32];
snprintf(min_str, sizeof(min_str), "%.1f", ax.min);
snprintf(max_str, sizeof(max_str), "%.1f", ax.max);
uint32_t x_cells = (nx + x_step - 1) / x_step;
int bar_w = (int)(x_cells * 2 + 1);
if (bar_w < 10) bar_w = 10;
char l_bar[256];
if (bar_w > (int)sizeof(l_bar) - 1) bar_w = (int)sizeof(l_bar) - 1;
memset(l_bar, ' ', bar_w);
l_bar[bar_w] = '\0';
memcpy(l_bar, min_str, strlen(min_str));
int max_st = bar_w - (int)strlen(max_str);
if (max_st > (int)strlen(min_str)) {
memcpy(l_bar + max_st, max_str, strlen(max_str));
}
snprintf(label_buf, sizeof(label_buf), " %s", l_bar);
tui_render_row(f, label_buf, width, true);
rows_drawn++;
}
/* Optional Color Range Legend reference bar */
if (st->show_legend && rows_drawn < max_rows) {
char leg_buf[4096];
if (st->monochrome) {
snprintf(leg_buf, sizeof(leg_buf), " Scale: [ .:-=+*#%%@ ] (0.00 -> %.2e) [%s]", max_content, st->log_z ? "LOG-Z" : "LIN");
} else {
int pos = snprintf(leg_buf, sizeof(leg_buf), " Color Scale: 0.00 ");
for (int step = 0; step <= 16 && pos + 32 < (int)sizeof(leg_buf); ++step) {
double frac = (double)step / 16.0;
char bg[32];
histo_palette_sample_ansi_bg(st->palette, frac, bg, sizeof(bg));
pos += snprintf(leg_buf + pos, sizeof(leg_buf) - pos, "%s \033[0m", bg);
}
pos += snprintf(leg_buf + pos, sizeof(leg_buf) - pos, " %.2e [%s, %s]", max_content, histo_palette_name(st->palette), st->log_z ? "LOG-Z" : "LIN");
}
tui_render_row(f, leg_buf, width, true);
rows_drawn++;
}
while (rows_drawn < max_rows) {
tui_render_row(f, "", width, true);
rows_drawn++;
}
}
static void format_top_coord(char *buf, size_t sz, double val, bool use_sci) {
if (use_sci) {
snprintf(buf, sz, "%8.2e", val);
} else if (fabs(val) >= 10000.0) {
snprintf(buf, sz, "%8.1f", val);
} else if (fabs(val) >= 100.0) {
snprintf(buf, sz, "%7.2f", val);
} else {
snprintf(buf, sz, "%6.2f", val);
}
}
static void render_1d_bars_viewport(tui_frame_t *f, const tui_state_t *st, const histo_t *h, int width, int max_rows) {
if (!h || max_rows <= 0) {
for (int r = 0; r < max_rows; ++r) tui_render_row(f, "", width, true);
return;
}
uint32_t nbins = histo_nbins(h);
if (nbins == 0) {
for (int r = 0; r < max_rows; ++r) tui_render_row(f, "", width, true);
return;
}
double total_w = histo_total_weight(h);
uint64_t num_entries = histo_num_entries(h);
histo_kde_t *kde = NULL;
if (st->show_kde && num_entries >= 5) {
kde = histo_kde_create_from_histo(h, NULL);
}
histo_fit_result_t *fit_res = NULL;
if (st->show_fit && num_entries >= 5) {
histo_fit_model(h, HISTO_FIT_MODEL_GAUSSIAN, NULL, NULL, &fit_res);
}
bundled/tools/src/cmd_top.c view on Meta::CPAN
tui_engine_pan_2d(&eng, -0.10, 0.0, st.paused ? &st.frozen_snapshot_2d : NULL);
} else {
tui_engine_pan_1d(&eng, -0.10, st.paused ? &st.frozen_snapshot : NULL);
}
snprintf(st.status_msg, sizeof(st.status_msg), "Pan Left [-10%%]");
st.status_msg_time = cli_get_time_sec();
break;
case ']':
case '>':
if (eng.is_2d) {
tui_engine_pan_2d(&eng, 0.10, 0.0, st.paused ? &st.frozen_snapshot_2d : NULL);
} else {
tui_engine_pan_1d(&eng, 0.10, st.paused ? &st.frozen_snapshot : NULL);
}
snprintf(st.status_msg, sizeof(st.status_msg), "Pan Right [+10%%]");
st.status_msg_time = cli_get_time_sec();
break;
case '0':
tui_engine_set_autorange(&eng, true, eng.auto_range_threshold);
snprintf(st.status_msg, sizeof(st.status_msg), "Auto-range: ON (Reset View)");
st.status_msg_time = cli_get_time_sec();
break;
case ':':
st.cmd_active = true;
st.cmd_len = 0;
st.cmd_buf[0] = '\0';
break;
case '?':
st.modal = MODAL_HELP;
break;
case 'p':
case 'P':
st.palette = (histo_palette_t)((st.palette + 1) % HISTO_PALETTE_COUNT);
snprintf(st.status_msg, sizeof(st.status_msg), "Colormap: %s", histo_palette_name(st.palette));
st.status_msg_time = cli_get_time_sec();
break;
case 'l':
if (eng.is_2d) {
st.log_z = !st.log_z;
} else {
st.scale_mode = (st.scale_mode == SCALE_LINEAR) ? SCALE_LOG_Y :
(st.scale_mode == SCALE_LOG_Y) ? SCALE_LOG_X :
(st.scale_mode == SCALE_LOG_X) ? SCALE_LOG_LOG : SCALE_LINEAR;
if (st.scale_mode == SCALE_LOG_X || st.scale_mode == SCALE_LOG_LOG) {
tui_engine_rebuild_1d_log(&eng, 50, NULL);
} else {
tui_engine_rebuild_1d(&eng, 50, 0, 0, NULL);
}
if (st.paused && st.frozen_snapshot) {
histo_destroy(st.frozen_snapshot);
if (st.scale_mode == SCALE_LOG_X || st.scale_mode == SCALE_LOG_LOG) {
tui_engine_rebuild_1d_log(&eng, 50, &st.frozen_snapshot);
} else {
tui_engine_rebuild_1d(&eng, 50, 0, 0, &st.frozen_snapshot);
}
}
}
break;
case 'g':
case 'G':
st.show_legend = !st.show_legend;
break;
case 'y':
case 'Y':
st.show_y_axis = !st.show_y_axis;
break;
case 'k':
if (!eng.is_2d) st.show_kde = !st.show_kde;
break;
case 'f':
if (!eng.is_2d) st.show_fit = !st.show_fit;
break;
case 'a':
case 'A':
if (!eng.is_2d) {
tui_engine_set_autorange(&eng, !eng.auto_range, eng.auto_range_threshold);
snprintf(st.status_msg, sizeof(st.status_msg), "Auto-range: %s", eng.auto_range ? "ON" : "OFF");
st.status_msg_time = cli_get_time_sec();
}
break;
case 'e':
if (!eng.is_2d) st.show_errors = !st.show_errors;
break;
case 'C':
st.monochrome = !st.monochrome;
break;
case 'c':
tui_engine_clear(&eng);
if (st.frozen_snapshot) {
histo_destroy(st.frozen_snapshot);
st.frozen_snapshot = NULL;
}
if (st.frozen_snapshot_2d) {
histo2d_destroy(st.frozen_snapshot_2d);
st.frozen_snapshot_2d = NULL;
}
snprintf(st.status_msg, sizeof(st.status_msg), "Cleared data");
st.status_msg_time = cli_get_time_sec();
break;
case 'r':
if (!eng.is_2d) tui_engine_rebuild_1d(&eng, (nbins > 10) ? nbins / 2 : 5, 0, 0, NULL);
break;
case 'R':
if (!eng.is_2d) tui_engine_rebuild_1d(&eng, nbins * 2, 0, 0, NULL);
break;
default:
break;
}
}
}
}
int term_cols = 80, term_rows = 24;
tui_term_get_size(&term_cols, &term_rows);
if (term_cols < 40) term_cols = 40;
if (term_rows < 10) term_rows = 10;
int cols = term_cols - 1;
int rows = term_rows;
// Viewport rows budget
bundled/tools/src/cmd_top.c view on Meta::CPAN
histo_mean(snap, &mean);
histo_std_dev(snap, &sdev);
histo_median(snap, &med);
histo_quantile(snap, 0.95, &p95);
snprintf(stats_buf, sizeof(stats_buf), "[Col %d] Mean: %.2f ± %.2f â Med: %.2f â P95: %.2f â N=%lu â Pal:%s",
eng.val_col, mean, sdev, med, p95, (unsigned long)n_entries, histo_palette_name(st.palette));
} else {
snprintf(stats_buf, sizeof(stats_buf), "Waiting for stream...");
}
}
tui_render_row(&frame, stats_buf, cols, true);
render_divider(&frame, cols);
} else {
// Row 2: Stats summary
char stats_buf[256];
if (eng.is_2d) {
if (snap_2d && n_entries > 0) {
histo2d_axis_t ax, ay;
histo2d_axis_x(snap_2d, &ax);
histo2d_axis_y(snap_2d, &ay);
snprintf(stats_buf, sizeof(stats_buf),
"X: [%.2f, %.2f] %ux â Y: [%.2f, %.2f] %uy â Entries: %lu â Weight: %.2f",
ax.min, ax.max, histo2d_nbins_x(snap_2d),
ay.min, ay.max, histo2d_nbins_y(snap_2d),
(unsigned long)n_entries, tot_w);
} else {
snprintf(stats_buf, sizeof(stats_buf), "Waiting for streaming 'x y' samples...");
}
} else {
if (snap && n_entries > 0) {
double mean = 0, sdev = 0, med = 0, iqr = 0, p95 = 0, p99 = 0;
histo_mean(snap, &mean);
histo_std_dev(snap, &sdev);
histo_median(snap, &med);
histo_iqr(snap, &iqr);
histo_quantile(snap, 0.95, &p95);
histo_quantile(snap, 0.99, &p99);
snprintf(stats_buf, sizeof(stats_buf), "Mean: %.2f ± %.2f â Med: %.2f â IQR: %.2f â P95: %.2f â P99: %.2f",
mean, sdev, med, iqr, p95, p99);
} else {
snprintf(stats_buf, sizeof(stats_buf), "Waiting for streaming samples...");
}
}
tui_render_row(&frame, stats_buf, cols, true);
// Row 3: Divider
render_divider(&frame, cols);
// Row 4: Subheader
char subhdr[384];
if (eng.is_2d) {
if (snap_2d) {
histo2d_axis_t ax, ay;
histo2d_axis_x(snap_2d, &ax);
histo2d_axis_y(snap_2d, &ay);
snprintf(subhdr, sizeof(subhdr), "2D Heatmap â X: %u bins [%.1f, %.1f] â Y: %u bins [%.1f, %.1f] â Pal: %s â %s %s",
histo2d_nbins_x(snap_2d), ax.min, ax.max,
histo2d_nbins_y(snap_2d), ay.min, ay.max,
histo_palette_name(st.palette),
st.log_z ? "LOG-Z" : "LIN",
st.show_legend ? "â Legend: ON" : "");
} else {
snprintf(subhdr, sizeof(subhdr), "Initializing 2D...");
}
} else if (snap) {
double r_min = 0, r_max = 0;
histo_range(snap, &r_min, &r_max);
uint32_t nb = histo_nbins(snap);
const char *sc = (st.scale_mode == SCALE_LOG_Y) ? "LOG-Y" :
(st.scale_mode == SCALE_LOG_X) ? "LOG-X" :
(st.scale_mode == SCALE_LOG_LOG) ? "LOG-LOG" : "LIN";
char kde_tag[64] = "";
char fit_tag[64] = "";
if (st.show_kde && n_entries >= 5) {
histo_kde_t *sub_kde = histo_kde_create_from_histo(snap, NULL);
if (sub_kde) {
double bw = histo_kde_get_bandwidth(sub_kde);
snprintf(kde_tag, sizeof(kde_tag), "â KDE: Gauss(h=%.2g) ", bw);
histo_kde_destroy(sub_kde);
} else {
snprintf(kde_tag, sizeof(kde_tag), "â KDE ");
}
}
if (st.show_fit && n_entries >= 5) {
histo_fit_result_t *sub_fit = NULL;
histo_fit_model(snap, HISTO_FIT_MODEL_GAUSSIAN, NULL, NULL, &sub_fit);
if (sub_fit && sub_fit->converged) {
snprintf(fit_tag, sizeof(fit_tag), "â Fit: μ=%.2f Ï=%.2f ", sub_fit->params[1], sub_fit->params[2]);
histo_fit_result_destroy(sub_fit);
} else {
if (sub_fit) histo_fit_result_destroy(sub_fit);
snprintf(fit_tag, sizeof(fit_tag), "â Fit: Gauss ");
}
}
char err_tag[32] = "";
if (st.show_errors) snprintf(err_tag, sizeof(err_tag), "â Err: ON ");
char yaxis_tag[32] = "";
if (st.show_y_axis) snprintf(yaxis_tag, sizeof(yaxis_tag), "â Axis: ON ");
char col_tag[32] = "";
if (eng.val_col > 1) snprintf(col_tag, sizeof(col_tag), "Col:%d â ", eng.val_col);
char win_tag[32] = "";
if (eng.window_size > 0) snprintf(win_tag, sizeof(win_tag), "Win:%zu â ", eng.window_size);
snprintf(subhdr, sizeof(subhdr), "%s%sRange: [%.2f, %.2f] â Bins: %u â Scale: %s â Pal: %s â Auto: %s %s%s%s%s",
col_tag, win_tag, r_min, r_max, nb, sc,
histo_palette_name(st.palette),
eng.auto_range ? "ON" : "OFF",
kde_tag, fit_tag, err_tag, yaxis_tag);
} else {
snprintf(subhdr, sizeof(subhdr), "Initializing...");
}
tui_render_row(&frame, subhdr, cols, true);
}
if (st.modal == MODAL_HELP) {
render_help_viewport(&frame, &st, cols, viewport_rows);
} else if (eng.is_2d) {
render_2d_heatmap_viewport(&frame, &st, snap_2d, cols, viewport_rows);
} else {
render_1d_bars_viewport(&frame, &st, snap, cols, viewport_rows);
}
( run in 0.815 second using v1.01-cache-2.11-cpan-364913b4093 )