use alloc::{string::String, sync::Arc, vec::Vec};
use crate::profiling::{SignalStats, StageMetrics};
use crate::StageKind;
#[derive(Debug)]
pub struct StageEntry {
pub metrics: Arc<StageMetrics>,
pub name: Option<String>,
}
impl StageEntry {
fn new() -> Self {
Self {
metrics: Arc::new(StageMetrics::new()),
name: None,
}
}
}
#[derive(Debug)]
pub struct SignalGauge {
pub name: String,
pub unit: String,
pub stats: Arc<SignalStats>,
}
#[derive(Debug, Default)]
pub struct RecordProfilingMetrics {
sources: Vec<StageEntry>,
taps: Vec<StageEntry>,
links: Vec<StageEntry>,
transforms: Vec<StageEntry>,
signals: Vec<SignalGauge>,
}
impl RecordProfilingMetrics {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.sources.is_empty()
&& self.taps.is_empty()
&& self.links.is_empty()
&& self.transforms.is_empty()
&& self.signals.is_empty()
}
pub fn push_source(&mut self) -> (usize, Arc<StageMetrics>) {
Self::push(&mut self.sources)
}
pub fn push_tap(&mut self) -> (usize, Arc<StageMetrics>) {
Self::push(&mut self.taps)
}
pub fn push_link(&mut self) -> (usize, Arc<StageMetrics>) {
Self::push(&mut self.links)
}
pub fn push_transform(&mut self) -> (usize, Arc<StageMetrics>) {
Self::push(&mut self.transforms)
}
fn push(vec: &mut Vec<StageEntry>) -> (usize, Arc<StageMetrics>) {
let idx = vec.len();
let entry = StageEntry::new();
let metrics = entry.metrics.clone();
vec.push(entry);
(idx, metrics)
}
pub fn source(&self, idx: usize) -> Option<&StageEntry> {
self.sources.get(idx)
}
pub fn tap(&self, idx: usize) -> Option<&StageEntry> {
self.taps.get(idx)
}
pub fn link(&self, idx: usize) -> Option<&StageEntry> {
self.links.get(idx)
}
pub fn transform(&self, idx: usize) -> Option<&StageEntry> {
self.transforms.get(idx)
}
pub fn tap_count(&self) -> usize {
self.taps.len()
}
pub fn sources(&self) -> &[StageEntry] {
&self.sources
}
pub fn taps(&self) -> &[StageEntry] {
&self.taps
}
pub fn links(&self) -> &[StageEntry] {
&self.links
}
pub fn transforms(&self) -> &[StageEntry] {
&self.transforms
}
pub fn push_signal_gauge(&mut self, name: &str, unit: &str) -> Arc<SignalStats> {
let stats = Arc::new(SignalStats::new());
self.signals.push(SignalGauge {
name: String::from(name),
unit: String::from(unit),
stats: stats.clone(),
});
stats
}
pub fn signals(&self) -> &[SignalGauge] {
&self.signals
}
pub fn set_stage_name(&mut self, kind: StageKind, idx: usize, name: &str) {
let vec = match kind {
StageKind::Source => &mut self.sources,
StageKind::Tap => &mut self.taps,
StageKind::Link => &mut self.links,
StageKind::Transform => &mut self.transforms,
};
if let Some(entry) = vec.get_mut(idx) {
entry.name = Some(String::from(name));
}
}
pub fn reset_all(&self) {
for e in self
.sources
.iter()
.chain(&self.taps)
.chain(&self.links)
.chain(&self.transforms)
{
e.metrics.reset();
}
for g in &self.signals {
g.stats.reset();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_assigns_sequential_indices() {
let mut m = RecordProfilingMetrics::new();
assert!(m.is_empty());
let (i0, _) = m.push_tap();
let (i1, _) = m.push_tap();
assert_eq!((i0, i1), (0, 1));
assert_eq!(m.tap_count(), 2);
assert!(!m.is_empty());
assert!(m.tap(0).is_some());
assert!(m.tap(2).is_none());
}
#[test]
fn set_stage_name() {
let mut m = RecordProfilingMetrics::new();
let _ = m.push_source();
m.set_stage_name(StageKind::Source, 0, "sensor_reader");
assert_eq!(m.source(0).unwrap().name.as_deref(), Some("sensor_reader"));
m.set_stage_name(StageKind::Source, 5, "ignored");
}
#[test]
fn reset_all_clears_metrics() {
let mut m = RecordProfilingMetrics::new();
let (_, src) = m.push_source();
let (_, tap) = m.push_tap();
src.record(10);
tap.record(20);
m.reset_all();
assert_eq!(src.call_count(), 0);
assert_eq!(tap.call_count(), 0);
}
#[test]
fn push_transform_records_metrics_and_reports_call_count() {
let mut m = RecordProfilingMetrics::new();
let (idx, metrics) = m.push_transform();
assert_eq!(idx, 0);
for v in [10u64, 30, 20] {
metrics.record(v);
}
let entry = m.transform(0).expect("transform stage registered");
assert_eq!(entry.metrics.call_count(), 3);
assert_eq!(entry.metrics.total_time_ns(), 60);
assert_eq!(entry.metrics.min_time_ns(), 10);
assert_eq!(entry.metrics.max_time_ns(), 30);
assert_eq!(m.transforms().len(), 1);
}
#[test]
fn set_stage_name_transform_lands_on_correct_index() {
let mut m = RecordProfilingMetrics::new();
let _ = m.push_source();
let _ = m.push_transform();
let _ = m.push_transform();
m.set_stage_name(StageKind::Transform, 1, "second_transform");
assert_eq!(m.transform(0).unwrap().name, None);
assert_eq!(
m.transform(1).unwrap().name.as_deref(),
Some("second_transform")
);
assert_eq!(m.source(0).unwrap().name, None);
}
#[test]
fn adjacent_record_profiling_metrics_do_not_cross_talk() {
let mut a = RecordProfilingMetrics::new();
let mut b = RecordProfilingMetrics::new();
let (_, metrics_a) = a.push_transform();
let (_, metrics_b) = b.push_transform();
metrics_a.record(100);
assert_eq!(a.transform(0).unwrap().metrics.call_count(), 1);
assert_eq!(
b.transform(0).unwrap().metrics.call_count(),
0,
"recording on one record's transform stage must not affect another record's"
);
assert_eq!(metrics_b.call_count(), 0);
}
}