#![allow(dead_code)]
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct NodeProfile {
pub node_id: String,
pub avg_duration_us: u64,
pub max_duration_us: u64,
pub call_count: u64,
pub total_us: u64,
}
impl NodeProfile {
#[must_use]
pub fn new(node_id: impl Into<String>) -> Self {
Self {
node_id: node_id.into(),
avg_duration_us: 0,
max_duration_us: 0,
call_count: 0,
total_us: 0,
}
}
#[must_use]
pub fn std_dev_us(&self) -> u64 {
self.max_duration_us.saturating_sub(self.avg_duration_us) / 2
}
fn record(&mut self, duration_us: u64) {
self.total_us += duration_us;
self.call_count += 1;
self.avg_duration_us = self.total_us / self.call_count;
if duration_us > self.max_duration_us {
self.max_duration_us = duration_us;
}
}
}
#[derive(Debug, Default)]
pub struct GraphProfiler {
profiles: HashMap<String, NodeProfile>,
}
impl GraphProfiler {
#[must_use]
pub fn new() -> Self {
Self {
profiles: HashMap::new(),
}
}
pub fn record(&mut self, node_id: &str, duration_us: u64) {
let profile = self
.profiles
.entry(node_id.to_string())
.or_insert_with(|| NodeProfile::new(node_id));
profile.record(duration_us);
}
#[must_use]
pub fn profile(&self, node_id: &str) -> Option<&NodeProfile> {
self.profiles.get(node_id)
}
#[must_use]
pub fn hottest_nodes(&self, n: usize) -> Vec<&NodeProfile> {
let mut profiles: Vec<&NodeProfile> = self.profiles.values().collect();
profiles.sort_by(|a, b| b.total_us.cmp(&a.total_us));
profiles.truncate(n);
profiles
}
#[must_use]
pub fn all_profiles(&self) -> Vec<&NodeProfile> {
self.profiles.values().collect()
}
}
#[derive(Debug, Clone)]
pub struct PortThroughput {
pub port_id: String,
pub bytes_transferred: u64,
pub frames_transferred: u64,
pub avg_frame_size: u64,
}
impl PortThroughput {
#[must_use]
pub fn new(
port_id: impl Into<String>,
bytes_transferred: u64,
frames_transferred: u64,
) -> Self {
let avg_frame_size = bytes_transferred
.checked_div(frames_transferred)
.unwrap_or(0);
Self {
port_id: port_id.into(),
bytes_transferred,
frames_transferred,
avg_frame_size,
}
}
}
#[derive(Debug, Clone)]
pub struct GraphProfilingReport {
pub node_profiles: Vec<NodeProfile>,
pub port_throughputs: Vec<PortThroughput>,
pub total_duration_us: u64,
pub cpu_efficiency_pct: f32,
}
impl GraphProfilingReport {
#[must_use]
#[allow(clippy::manual_checked_ops)]
pub fn generate(profiler: &GraphProfiler) -> Self {
let mut node_profiles: Vec<NodeProfile> = profiler.profiles.values().cloned().collect();
node_profiles.sort_by(|a, b| b.total_us.cmp(&a.total_us));
let total_node_us: u64 = node_profiles.iter().map(|p| p.total_us).sum();
let total_duration_us = total_node_us;
let cpu_efficiency_pct = if total_duration_us > 0 {
(total_node_us as f64 / total_duration_us as f64 * 100.0) as f32
} else {
100.0
};
Self {
node_profiles,
port_throughputs: vec![],
total_duration_us,
cpu_efficiency_pct,
}
}
#[must_use]
#[allow(clippy::manual_checked_ops)]
pub fn generate_full(
profiler: &GraphProfiler,
port_throughputs: Vec<PortThroughput>,
total_duration_us: u64,
) -> Self {
let mut report = Self::generate(profiler);
report.port_throughputs = port_throughputs;
let total_node_us: u64 = report.node_profiles.iter().map(|p| p.total_us).sum();
report.total_duration_us = total_duration_us;
report.cpu_efficiency_pct = if total_duration_us > 0 {
(total_node_us as f64 / total_duration_us as f64 * 100.0).min(100.0) as f32
} else {
100.0
};
report
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_node_profile_initial_state() {
let p = NodeProfile::new("node_a");
assert_eq!(p.node_id, "node_a");
assert_eq!(p.call_count, 0);
assert_eq!(p.total_us, 0);
assert_eq!(p.avg_duration_us, 0);
assert_eq!(p.max_duration_us, 0);
}
#[test]
fn test_node_profile_record_updates_stats() {
let mut p = NodeProfile::new("n");
p.record(100);
p.record(200);
assert_eq!(p.call_count, 2);
assert_eq!(p.total_us, 300);
assert_eq!(p.avg_duration_us, 150);
assert_eq!(p.max_duration_us, 200);
}
#[test]
fn test_node_profile_std_dev() {
let mut p = NodeProfile::new("n");
p.record(100);
p.record(300); assert_eq!(p.std_dev_us(), 50);
}
#[test]
fn test_node_profile_std_dev_zero_when_equal() {
let mut p = NodeProfile::new("n");
p.record(100);
assert_eq!(p.std_dev_us(), 0); }
#[test]
fn test_profiler_record_creates_profile() {
let mut profiler = GraphProfiler::new();
profiler.record("node_x", 500);
assert!(profiler.profile("node_x").is_some());
assert_eq!(
profiler
.profile("node_x")
.expect("profile should succeed")
.call_count,
1
);
}
#[test]
fn test_profiler_missing_node_returns_none() {
let profiler = GraphProfiler::new();
assert!(profiler.profile("nonexistent").is_none());
}
#[test]
fn test_profiler_multiple_records() {
let mut profiler = GraphProfiler::new();
for us in [100, 200, 300] {
profiler.record("n", us);
}
let p = profiler.profile("n").expect("profile should succeed");
assert_eq!(p.call_count, 3);
assert_eq!(p.total_us, 600);
}
#[test]
fn test_profiler_hottest_nodes_sorted() {
let mut profiler = GraphProfiler::new();
profiler.record("slow", 1000);
profiler.record("fast", 10);
profiler.record("medium", 500);
let hot = profiler.hottest_nodes(2);
assert_eq!(hot[0].node_id, "slow");
assert_eq!(hot[1].node_id, "medium");
}
#[test]
fn test_profiler_hottest_n_clamped() {
let mut profiler = GraphProfiler::new();
profiler.record("a", 100);
let hot = profiler.hottest_nodes(10);
assert_eq!(hot.len(), 1);
}
#[test]
fn test_port_throughput_avg_frame_size() {
let pt = PortThroughput::new("port_0", 1024, 4);
assert_eq!(pt.avg_frame_size, 256);
}
#[test]
fn test_port_throughput_zero_frames() {
let pt = PortThroughput::new("port_0", 0, 0);
assert_eq!(pt.avg_frame_size, 0);
}
#[test]
fn test_report_generate_empty_profiler() {
let profiler = GraphProfiler::new();
let report = GraphProfilingReport::generate(&profiler);
assert!(report.node_profiles.is_empty());
assert_eq!(report.total_duration_us, 0);
assert!((report.cpu_efficiency_pct - 100.0).abs() < 0.01);
}
#[test]
fn test_report_generate_sorted_profiles() {
let mut profiler = GraphProfiler::new();
profiler.record("cheap", 50);
profiler.record("expensive", 5000);
let report = GraphProfilingReport::generate(&profiler);
assert_eq!(report.node_profiles[0].node_id, "expensive");
}
#[test]
fn test_report_generate_full() {
let mut profiler = GraphProfiler::new();
profiler.record("n", 1000);
let pt = PortThroughput::new("p0", 4096, 8);
let report = GraphProfilingReport::generate_full(&profiler, vec![pt], 2000);
assert_eq!(report.port_throughputs.len(), 1);
assert_eq!(report.total_duration_us, 2000);
assert!((report.cpu_efficiency_pct - 50.0).abs() < 1.0);
}
}