344 lines
8.4 KiB
C
344 lines
8.4 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
#include "progress.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
|
|
#define FP_DEFAULT_WIDTH 40
|
|
#define FP_POSTFIX_MAX 256
|
|
|
|
/* Unicode block characters for smooth progress bar */
|
|
static const char* BLOCKS[] = {" ", "\xe2\x96\x8f", "\xe2\x96\x8e", "\xe2\x96\x8d",
|
|
"\xe2\x96\x8c", "\xe2\x96\x8b", "\xe2\x96\x8a",
|
|
"\xe2\x96\x89", "\xe2\x96\x88"};
|
|
|
|
struct fp_progress {
|
|
int64_t total;
|
|
int64_t current;
|
|
char* desc;
|
|
char postfix[FP_POSTFIX_MAX];
|
|
char* unit;
|
|
|
|
double start_time;
|
|
double last_update_time;
|
|
int64_t last_update_n;
|
|
|
|
int width;
|
|
bool disabled;
|
|
bool closed;
|
|
|
|
fp_progress* parent;
|
|
};
|
|
|
|
/* Get current time in seconds */
|
|
static double get_time(void) {
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
return tv.tv_sec + tv.tv_usec / 1000000.0;
|
|
}
|
|
|
|
/* Get terminal width */
|
|
static int get_terminal_width(void) {
|
|
struct winsize w;
|
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0 && w.ws_col > 0) {
|
|
return w.ws_col;
|
|
}
|
|
return 80;
|
|
}
|
|
|
|
/* Format time as HH:MM:SS or MM:SS */
|
|
static void format_time(double seconds, char* buf, size_t size) {
|
|
if (seconds < 0 || seconds > 86400 * 365) {
|
|
snprintf(buf, size, "?");
|
|
return;
|
|
}
|
|
|
|
int s = (int)seconds;
|
|
int h = s / 3600;
|
|
int m = (s % 3600) / 60;
|
|
s = s % 60;
|
|
|
|
if (h > 0) {
|
|
snprintf(buf, size, "%d:%02d:%02d", h, m, s);
|
|
} else {
|
|
snprintf(buf, size, "%02d:%02d", m, s);
|
|
}
|
|
}
|
|
|
|
/* Format rate with appropriate unit */
|
|
static void format_rate(double rate, const char* unit, char* buf, size_t size) {
|
|
if (rate < 0.01) {
|
|
snprintf(buf, size, "%.2e %s/s", rate, unit ? unit : "it");
|
|
} else if (rate < 1.0) {
|
|
snprintf(buf, size, "%.2f %s/s", rate, unit ? unit : "it");
|
|
} else if (rate < 1000.0) {
|
|
snprintf(buf, size, "%.1f %s/s", rate, unit ? unit : "it");
|
|
} else {
|
|
snprintf(buf, size, "%.2fk %s/s", rate / 1000.0, unit ? unit : "it");
|
|
}
|
|
}
|
|
|
|
fp_progress* fp_new(int64_t total, const char* desc) {
|
|
fp_progress* p = calloc(1, sizeof(fp_progress));
|
|
if (!p) return NULL;
|
|
|
|
p->total = total;
|
|
p->current = 0;
|
|
p->width = FP_DEFAULT_WIDTH;
|
|
p->start_time = get_time();
|
|
p->last_update_time = p->start_time;
|
|
p->last_update_n = 0;
|
|
p->disabled = false;
|
|
p->closed = false;
|
|
p->parent = NULL;
|
|
p->postfix[0] = '\0';
|
|
|
|
if (desc) {
|
|
p->desc = strdup(desc);
|
|
}
|
|
|
|
p->unit = strdup("it");
|
|
|
|
return p;
|
|
}
|
|
|
|
fp_progress* fp_new_nested(fp_progress* parent, int64_t total, const char* desc) {
|
|
fp_progress* p = fp_new(total, desc);
|
|
if (p) {
|
|
p->parent = parent;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void fp_free(fp_progress* p) {
|
|
if (!p) return;
|
|
if (p->desc) free(p->desc);
|
|
if (p->unit) free(p->unit);
|
|
free(p);
|
|
}
|
|
|
|
void fp_update(fp_progress* p, int64_t n) {
|
|
if (!p || p->closed) return;
|
|
|
|
p->current += n;
|
|
if (p->current > p->total && p->total > 0) {
|
|
p->current = p->total;
|
|
}
|
|
|
|
double now = get_time();
|
|
|
|
/* Throttle updates to avoid excessive terminal writes */
|
|
if (now - p->last_update_time >= 0.1 || p->current >= p->total) {
|
|
p->last_update_time = now;
|
|
p->last_update_n = p->current;
|
|
|
|
if (!p->disabled) {
|
|
fp_refresh(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
void fp_set(fp_progress* p, int64_t n) {
|
|
if (!p || p->closed) return;
|
|
|
|
int64_t delta = n - p->current;
|
|
if (delta > 0) {
|
|
fp_update(p, delta);
|
|
} else {
|
|
p->current = n;
|
|
if (!p->disabled) {
|
|
fp_refresh(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
void fp_set_postfix(fp_progress* p, const char* fmt, ...) {
|
|
if (!p) return;
|
|
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vsnprintf(p->postfix, FP_POSTFIX_MAX, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void fp_close(fp_progress* p) {
|
|
if (!p || p->closed) return;
|
|
|
|
p->closed = true;
|
|
|
|
if (!p->disabled) {
|
|
fp_refresh(p);
|
|
printf("\n");
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
int64_t fp_total(fp_progress* p) {
|
|
return p ? p->total : 0;
|
|
}
|
|
|
|
int64_t fp_current(fp_progress* p) {
|
|
return p ? p->current : 0;
|
|
}
|
|
|
|
double fp_elapsed(fp_progress* p) {
|
|
if (!p) return 0.0;
|
|
return get_time() - p->start_time;
|
|
}
|
|
|
|
double fp_rate(fp_progress* p) {
|
|
if (!p) return 0.0;
|
|
double elapsed = fp_elapsed(p);
|
|
if (elapsed < 0.001) return 0.0;
|
|
return p->current / elapsed;
|
|
}
|
|
|
|
double fp_eta(fp_progress* p) {
|
|
if (!p || p->total <= 0) return 0.0;
|
|
double rate = fp_rate(p);
|
|
if (rate < 0.001) return 0.0;
|
|
int64_t remaining = p->total - p->current;
|
|
return remaining / rate;
|
|
}
|
|
|
|
float fp_percent(fp_progress* p) {
|
|
if (!p || p->total <= 0) return 0.0f;
|
|
return (float)p->current / (float)p->total * 100.0f;
|
|
}
|
|
|
|
void fp_set_width(fp_progress* p, int width) {
|
|
if (p && width > 0) {
|
|
p->width = width;
|
|
}
|
|
}
|
|
|
|
void fp_set_unit(fp_progress* p, const char* unit) {
|
|
if (!p) return;
|
|
if (p->unit) free(p->unit);
|
|
p->unit = unit ? strdup(unit) : strdup("it");
|
|
}
|
|
|
|
void fp_disable(fp_progress* p) {
|
|
if (p) p->disabled = true;
|
|
}
|
|
|
|
void fp_refresh(fp_progress* p) {
|
|
if (!p || p->disabled) return;
|
|
|
|
char line[1024];
|
|
char* ptr = line;
|
|
int remaining = sizeof(line);
|
|
int written;
|
|
|
|
/* Clear line and return to start */
|
|
printf("\r\033[K");
|
|
|
|
/* Description */
|
|
if (p->desc && p->desc[0]) {
|
|
written = snprintf(ptr, remaining, "%s: ", p->desc);
|
|
ptr += written;
|
|
remaining -= written;
|
|
}
|
|
|
|
/* Percentage */
|
|
float pct = fp_percent(p);
|
|
written = snprintf(ptr, remaining, "%3.0f%%|", pct);
|
|
ptr += written;
|
|
remaining -= written;
|
|
|
|
/* Progress bar */
|
|
int bar_width = p->width;
|
|
int term_width = get_terminal_width();
|
|
|
|
/* Adjust bar width based on available space */
|
|
int overhead = (int)(ptr - line) + 50; /* space for stats */
|
|
if (p->postfix[0]) overhead += (int)strlen(p->postfix) + 2;
|
|
|
|
if (bar_width > term_width - overhead) {
|
|
bar_width = term_width - overhead;
|
|
}
|
|
if (bar_width < 10) bar_width = 10;
|
|
|
|
/* Calculate filled portion */
|
|
float progress = (p->total > 0) ? (float)p->current / (float)p->total : 0.0f;
|
|
if (progress > 1.0f) progress = 1.0f;
|
|
|
|
float filled = progress * bar_width;
|
|
int full_blocks = (int)filled;
|
|
int partial = (int)((filled - full_blocks) * 8);
|
|
|
|
for (int i = 0; i < bar_width && remaining > 4; i++) {
|
|
const char* block;
|
|
if (i < full_blocks) {
|
|
block = BLOCKS[8]; /* Full block */
|
|
} else if (i == full_blocks && partial > 0) {
|
|
block = BLOCKS[partial];
|
|
} else {
|
|
block = BLOCKS[0]; /* Space */
|
|
}
|
|
written = snprintf(ptr, remaining, "%s", block);
|
|
ptr += written;
|
|
remaining -= written;
|
|
}
|
|
|
|
written = snprintf(ptr, remaining, "| ");
|
|
ptr += written;
|
|
remaining -= written;
|
|
|
|
/* Current/Total */
|
|
written = snprintf(ptr, remaining, "%lld/%lld ",
|
|
(long long)p->current, (long long)p->total);
|
|
ptr += written;
|
|
remaining -= written;
|
|
|
|
/* Time elapsed/remaining */
|
|
char elapsed_str[16], eta_str[16];
|
|
format_time(fp_elapsed(p), elapsed_str, sizeof(elapsed_str));
|
|
format_time(fp_eta(p), eta_str, sizeof(eta_str));
|
|
|
|
written = snprintf(ptr, remaining, "[%s<%s, ", elapsed_str, eta_str);
|
|
ptr += written;
|
|
remaining -= written;
|
|
|
|
/* Rate */
|
|
char rate_str[32];
|
|
format_rate(fp_rate(p), p->unit, rate_str, sizeof(rate_str));
|
|
written = snprintf(ptr, remaining, "%s", rate_str);
|
|
ptr += written;
|
|
remaining -= written;
|
|
|
|
/* Postfix */
|
|
if (p->postfix[0]) {
|
|
written = snprintf(ptr, remaining, ", %s", p->postfix);
|
|
ptr += written;
|
|
remaining -= written;
|
|
}
|
|
|
|
written = snprintf(ptr, remaining, "]");
|
|
ptr += written;
|
|
|
|
printf("%s", line);
|
|
fflush(stdout);
|
|
}
|
|
|
|
void fp_log_step(fp_train_state* state) {
|
|
if (!state) return;
|
|
printf("step %d | loss: %.4f | lr: %.2e | %.1f samples/s\n",
|
|
state->step, state->loss, state->lr, state->throughput);
|
|
fflush(stdout);
|
|
}
|
|
|
|
void fp_log_epoch(fp_train_state* state) {
|
|
if (!state) return;
|
|
printf("epoch %d | loss: %.4f | lr: %.2e | %.1f samples/s\n",
|
|
state->epoch, state->loss, state->lr, state->throughput);
|
|
fflush(stdout);
|
|
}
|