use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum DiagnosticSeverity {
Info,
Warning,
Degraded,
Error,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum KernelStage {
Collect,
Classify,
Intersect,
Refine,
Fragment,
Select,
Sew,
Validate,
Export,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DiagnosticEvent {
pub severity: DiagnosticSeverity,
pub stage: KernelStage,
pub code: String,
pub message: String,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct KernelDiagnostics {
pub events: Vec<DiagnosticEvent>,
pub counters: BTreeMap<String, u64>,
pub measurements: BTreeMap<String, f64>,
}
impl KernelDiagnostics {
pub fn event(
&mut self,
severity: DiagnosticSeverity,
stage: KernelStage,
code: impl Into<String>,
message: impl Into<String>,
) {
self.events.push(DiagnosticEvent {
severity,
stage,
code: code.into(),
message: message.into(),
});
}
pub fn count(&mut self, code: impl Into<String>) {
*self.counters.entry(code.into()).or_default() += 1;
}
pub fn count_n(&mut self, code: impl Into<String>, amount: u64) {
*self.counters.entry(code.into()).or_default() += amount;
}
pub fn measure_max(&mut self, code: impl Into<String>, value: f64) {
let entry = self.measurements.entry(code.into()).or_insert(value);
*entry = entry.max(value);
}
pub fn worst(&self) -> Option<DiagnosticSeverity> {
self.events.iter().map(|event| event.severity).max()
}
pub fn shippable(&self) -> bool {
self.events
.iter()
.all(|event| event.severity < DiagnosticSeverity::Error)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct KernelOutcome<T> {
pub value: T,
pub diagnostics: KernelDiagnostics,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn diagnostics_are_stable_and_queryable() {
let mut diagnostics = KernelDiagnostics::default();
diagnostics.count("intersect.pairs");
diagnostics.count_n("intersect.pairs", 2);
diagnostics.measure_max("validate.edge_error", 0.1);
diagnostics.measure_max("validate.edge_error", 0.05);
diagnostics.event(
DiagnosticSeverity::Warning,
KernelStage::Classify,
"pair.near_tangent",
"dedicated path selected",
);
assert_eq!(diagnostics.counters["intersect.pairs"], 3);
assert_eq!(diagnostics.measurements["validate.edge_error"], 0.1);
assert!(diagnostics.shippable());
}
}