use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
pub mod andon;
pub mod dashboard;
pub mod drift;
pub mod export;
pub mod gpu;
pub mod inference;
pub mod lineage;
pub mod llm;
pub mod params;
pub mod prometheus;
pub mod report;
pub mod storage;
pub mod tui;
pub mod wasm;
pub use andon::{Alert, AlertLevel, AndonConfig, AndonSystem};
pub use dashboard::{Dashboard, DashboardConfig};
pub use drift::{Anomaly, AnomalySeverity, DriftDetector, DriftStatus, SlidingWindowBaseline};
pub use export::{ExportFormat, MetricsExporter};
pub use lineage::{ChangeType, Derivation, ModelLineage, ModelMetadata};
pub use llm::{
EvalResult, InMemoryLLMEvaluator, LLMError, LLMEvaluator, LLMMetrics, PromptId, PromptVersion,
};
pub use params::{ParamDiff, ParamLogger, ParamValue};
pub use report::{
HanseiAnalyzer, IssueSeverity, MetricSummary, PostTrainingReport, TrainingIssue, Trend,
};
pub use storage::{InMemoryStore, JsonFileStore, MetricsStore, StorageError, StorageResult};
pub use tui::{
BrailleChart, GpuTelemetry, SamplePeek, TrainingSnapshot, TrainingState, TrainingStateWriter,
TrainingStatus, TuiMonitor, TuiMonitorConfig,
};
pub use wasm::{WasmDashboard, WasmDashboardOptions, WasmMetricsCollector};
#[cfg(test)]
#[path = "tests/mod.rs"]
mod tests;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Metric {
Loss,
Accuracy,
LearningRate,
GradientNorm,
Epoch,
Batch,
Custom(String),
}
impl Metric {
pub fn as_str(&self) -> &str {
match self {
Metric::Loss => "loss",
Metric::Accuracy => "accuracy",
Metric::LearningRate => "learning_rate",
Metric::GradientNorm => "gradient_norm",
Metric::Epoch => "epoch",
Metric::Batch => "batch",
Metric::Custom(name) => name,
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
match s {
"loss" => Some(Metric::Loss),
"accuracy" => Some(Metric::Accuracy),
"learning_rate" => Some(Metric::LearningRate),
"gradient_norm" => Some(Metric::GradientNorm),
"epoch" => Some(Metric::Epoch),
"batch" => Some(Metric::Batch),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricRecord {
pub timestamp: u64,
pub metric: Metric,
pub value: f64,
pub tags: HashMap<String, String>,
}
impl MetricRecord {
pub fn new(metric: Metric, value: f64) -> Self {
let timestamp =
SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_millis() as u64).unwrap_or(0);
Self { timestamp, metric, value, tags: HashMap::new() }
}
pub fn with_tag(mut self, key: &str, value: &str) -> Self {
self.tags.insert(key.to_string(), value.to_string());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricStats {
pub count: usize,
pub mean: f64,
pub std: f64,
pub min: f64,
pub max: f64,
pub sum: f64,
pub has_nan: bool,
pub has_inf: bool,
}
impl Default for MetricStats {
fn default() -> Self {
Self {
count: 0,
mean: 0.0,
std: 0.0,
min: f64::INFINITY,
max: f64::NEG_INFINITY,
sum: 0.0,
has_nan: false,
has_inf: false,
}
}
}
#[derive(Debug)]
pub struct MetricsCollector {
records: Vec<MetricRecord>,
running_stats: HashMap<Metric, RunningStats>,
}
#[derive(Debug, Clone)]
struct RunningStats {
count: usize,
mean: f64,
m2: f64, min: f64,
max: f64,
sum: f64,
has_nan: bool,
has_inf: bool,
}
impl Default for RunningStats {
fn default() -> Self {
Self {
count: 0,
mean: 0.0,
m2: 0.0,
min: f64::INFINITY,
max: f64::NEG_INFINITY,
sum: 0.0,
has_nan: false,
has_inf: false,
}
}
}
impl RunningStats {
#[allow(dead_code)]
fn new() -> Self {
Self {
count: 0,
mean: 0.0,
m2: 0.0,
min: f64::INFINITY,
max: f64::NEG_INFINITY,
sum: 0.0,
has_nan: false,
has_inf: false,
}
}
fn update(&mut self, value: f64) {
if value.is_nan() {
self.has_nan = true;
return;
}
if value.is_infinite() {
self.has_inf = true;
self.min = self.min.min(value);
self.max = self.max.max(value);
return;
}
self.count += 1;
self.sum += value;
self.min = self.min.min(value);
self.max = self.max.max(value);
let delta = value - self.mean;
self.mean += delta / self.count as f64;
let delta2 = value - self.mean;
self.m2 += delta * delta2;
}
fn std(&self) -> f64 {
if self.count < 2 {
return 0.0;
}
(self.m2 / (self.count - 1) as f64).sqrt()
}
fn to_stats(&self) -> MetricStats {
MetricStats {
count: self.count,
mean: self.mean,
std: self.std(),
min: self.min,
max: self.max,
sum: self.sum,
has_nan: self.has_nan,
has_inf: self.has_inf,
}
}
}
impl MetricsCollector {
pub fn new() -> Self {
Self { records: Vec::new(), running_stats: HashMap::new() }
}
pub fn record(&mut self, metric: Metric, value: f64) {
self.records.push(MetricRecord::new(metric.clone(), value));
self.running_stats.entry(metric).or_default().update(value);
}
pub fn record_batch(&mut self, metrics: &[(Metric, f64)]) {
for (metric, value) in metrics {
self.record(metric.clone(), *value);
}
}
pub fn count(&self) -> usize {
self.records.len()
}
pub fn is_empty(&self) -> bool {
self.records.is_empty()
}
pub fn clear(&mut self) {
self.records.clear();
self.running_stats.clear();
}
pub fn summary(&self) -> HashMap<Metric, MetricStats> {
self.running_stats
.iter()
.map(|(metric, stats)| (metric.clone(), stats.to_stats()))
.collect()
}
pub fn to_records(&self) -> Vec<MetricRecord> {
self.records.clone()
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(&self.records)
}
pub fn summary_to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(&self.summary())
}
}
impl Default for MetricsCollector {
fn default() -> Self {
Self::new()
}
}
pub type MetricsSummary = HashMap<Metric, MetricStats>;