use alloc::{string::String, vec::Vec};
use serde::{Deserialize, Serialize};
use crate::profiling::{RecordProfilingMetrics, SignalGauge, StageEntry};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StageProfilingInfo {
pub stage_type: String,
pub index: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
pub call_count: u64,
pub total_time_ns: u64,
pub avg_time_ns: u64,
pub min_time_ns: u64,
pub max_time_ns: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SignalStatsInfo {
pub signal: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub unit: String,
pub count: u64,
pub last: f64,
pub min: f64,
pub max: f64,
pub mean: f64,
}
impl SignalStatsInfo {
fn from_gauge(gauge: &SignalGauge) -> Self {
let s = &gauge.stats;
Self {
signal: gauge.name.clone(),
unit: gauge.unit.clone(),
count: s.count(),
last: s.last(),
min: s.min(),
max: s.max(),
mean: s.mean(),
}
}
}
impl StageProfilingInfo {
fn from_entry(stage_type: &str, index: usize, entry: &StageEntry) -> Self {
let m = &entry.metrics;
Self {
stage_type: String::from(stage_type),
index,
name: entry.name.clone(),
call_count: m.call_count(),
total_time_ns: m.total_time_ns(),
avg_time_ns: m.avg_time_ns(),
min_time_ns: m.min_time_ns(),
max_time_ns: m.max_time_ns(),
}
}
}
impl RecordProfilingMetrics {
pub fn snapshot(&self) -> Vec<StageProfilingInfo> {
let mut out = Vec::new();
for (kind, stages) in [
("source", self.sources()),
("tap", self.taps()),
("link", self.links()),
("transform", self.transforms()),
] {
for (i, entry) in stages.iter().enumerate() {
out.push(StageProfilingInfo::from_entry(kind, i, entry));
}
}
out
}
pub fn signal_snapshot(&self) -> Vec<SignalStatsInfo> {
self.signals()
.iter()
.map(SignalStatsInfo::from_gauge)
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn empty_unit_roundtrips() {
let info = SignalStatsInfo {
signal: "temperature".to_string(),
unit: String::new(),
count: 3,
last: 1.0,
min: 0.5,
max: 2.0,
mean: 1.2,
};
let json = serde_json::to_string(&info).unwrap();
assert!(!json.contains("unit"), "empty unit must be omitted: {json}");
let back: SignalStatsInfo = serde_json::from_str(&json).unwrap();
assert_eq!(back, info);
}
}