// ============================================================
// Lightweight, allocation-free-per-tick telemetry wrapper.
// Applications can expose these counters to a profiler or metrics backend.
// ============================================================
///|
/// Aggregate counters for one instrumented node.
pub struct NodeMetrics {
total_ticks : Ref[Int]
successes : Ref[Int]
failures : Ref[Int]
running : Ref[Int]
consecutive_running : Ref[Int]
last : Ref[Status?]
}
///|
/// Create empty metrics.
pub fn NodeMetrics::new() -> NodeMetrics {
{
total_ticks: Ref::new(0),
successes: Ref::new(0),
failures: Ref::new(0),
running: Ref::new(0),
consecutive_running: Ref::new(0),
last: Ref::new(None),
}
}
///|
/// Reset all counters.
pub fn NodeMetrics::reset(self : NodeMetrics) -> Unit {
self.total_ticks.set(0)
self.successes.set(0)
self.failures.set(0)
self.running.set(0)
self.consecutive_running.set(0)
self.last.set(None)
}
///|
/// Record one status observation.
pub fn NodeMetrics::record(self : NodeMetrics, status : Status) -> Unit {
self.total_ticks.set(self.total_ticks.get() + 1)
self.last.set(Some(status))
match status {
Status::BTSuccess => {
self.successes.set(self.successes.get() + 1)
self.consecutive_running.set(0)
}
Status::BTFailure => {
self.failures.set(self.failures.get() + 1)
self.consecutive_running.set(0)
}
Status::BTRunning => {
self.running.set(self.running.get() + 1)
self.consecutive_running.set(self.consecutive_running.get() + 1)
}
}
}
///|
/// Number of observed ticks.
pub fn NodeMetrics::total_ticks(self : NodeMetrics) -> Int {
self.total_ticks.get()
}
///|
/// Number of successful observations.
pub fn NodeMetrics::successes(self : NodeMetrics) -> Int {
self.successes.get()
}
///|
/// Number of failed observations.
pub fn NodeMetrics::failures(self : NodeMetrics) -> Int {
self.failures.get()
}
///|
/// Number of running observations.
pub fn NodeMetrics::running(self : NodeMetrics) -> Int {
self.running.get()
}
///|
/// Longest current running streak.
pub fn NodeMetrics::consecutive_running(self : NodeMetrics) -> Int {
self.consecutive_running.get()
}
///|
/// Most recently observed status.
pub fn NodeMetrics::last_status(self : NodeMetrics) -> Status? {
self.last.get()
}
///|
/// Wrap a node and record every tick without changing its behavior.
pub fn metrics_node(child : Node, metrics : NodeMetrics) -> Node {
let tick = fn(bb) {
let status = child.tick(bb)
metrics.record(status)
status
}
let reset = fn() { child.reset() }
Node::new(tick, reset)
}
///|
/// A named telemetry sample suitable for exporting to text or logs.
pub struct MetricsSnapshot {
name : String
total : Int
success : Int
failure : Int
running : Int
}
///|
/// Read a stable value snapshot from live metrics.
pub fn snapshot_metrics(
name : String,
metrics : NodeMetrics,
) -> MetricsSnapshot {
{
name,
total: metrics.total_ticks(),
success: metrics.successes(),
failure: metrics.failures(),
running: metrics.running(),
}
}
///|
/// Format a snapshot as one CSV row for simple observability pipelines.
pub fn MetricsSnapshot::to_csv(self : MetricsSnapshot) -> String {
"\{self.name},\{self.total},\{self.success},\{self.failure},\{self.running}"
}