ringkernel_procint/analytics/
dfg_metrics.rs1use crate::models::{DFGGraph, GpuDFGEdge, GpuDFGNode};
6
7#[derive(Debug, Default)]
9pub struct DFGMetricsCalculator {
10 pub metrics: DFGMetrics,
12}
13
14impl DFGMetricsCalculator {
15 pub fn new() -> Self {
17 Self::default()
18 }
19
20 pub fn calculate(&mut self, dfg: &DFGGraph) -> &DFGMetrics {
22 let nodes = dfg.nodes();
23 let edges = dfg.edges();
24
25 self.metrics = DFGMetrics {
26 node_count: nodes.len(),
27 edge_count: edges.len(),
28 total_events: nodes.iter().map(|n| n.event_count as u64).sum(),
29 avg_events_per_node: self.avg_events_per_node(nodes),
30 density: self.graph_density(nodes.len(), edges.len()),
31 avg_degree: self.avg_degree(nodes, edges),
32 max_in_degree: self.max_in_degree(nodes),
33 max_out_degree: self.max_out_degree(nodes),
34 avg_duration_ms: self.avg_duration(nodes),
35 total_cost: 0.0, bottleneck_score: self.bottleneck_score(nodes),
37 parallelism_factor: self.parallelism_factor(edges),
38 };
39
40 &self.metrics
41 }
42
43 fn avg_events_per_node(&self, nodes: &[GpuDFGNode]) -> f32 {
44 if nodes.is_empty() {
45 return 0.0;
46 }
47 let total: u64 = nodes.iter().map(|n| n.event_count as u64).sum();
48 total as f32 / nodes.len() as f32
49 }
50
51 fn graph_density(&self, node_count: usize, edge_count: usize) -> f32 {
52 if node_count <= 1 {
53 return 0.0;
54 }
55 let max_edges = node_count * (node_count - 1);
56 edge_count as f32 / max_edges as f32
57 }
58
59 fn avg_degree(&self, nodes: &[GpuDFGNode], edges: &[GpuDFGEdge]) -> f32 {
60 if nodes.is_empty() {
61 return 0.0;
62 }
63 2.0 * edges.len() as f32 / nodes.len() as f32
65 }
66
67 fn max_in_degree(&self, nodes: &[GpuDFGNode]) -> u32 {
68 nodes
69 .iter()
70 .map(|n| n.incoming_count as u32)
71 .max()
72 .unwrap_or(0)
73 }
74
75 fn max_out_degree(&self, nodes: &[GpuDFGNode]) -> u32 {
76 nodes
77 .iter()
78 .map(|n| n.outgoing_count as u32)
79 .max()
80 .unwrap_or(0)
81 }
82
83 fn avg_duration(&self, nodes: &[GpuDFGNode]) -> f32 {
84 if nodes.is_empty() {
85 return 0.0;
86 }
87 let total: f32 = nodes.iter().map(|n| n.avg_duration_ms).sum();
88 total / nodes.len() as f32
89 }
90
91 fn bottleneck_score(&self, nodes: &[GpuDFGNode]) -> f32 {
92 if nodes.is_empty() {
94 return 0.0;
95 }
96
97 let rates: Vec<f32> = nodes
98 .iter()
99 .filter(|n| n.event_count > 0 && n.incoming_count > 0)
100 .map(|n| n.outgoing_count as f32 / n.incoming_count.max(1) as f32)
101 .collect();
102
103 if rates.is_empty() {
104 return 0.0;
105 }
106
107 let mean: f32 = rates.iter().sum::<f32>() / rates.len() as f32;
108 let variance: f32 =
109 rates.iter().map(|r| (r - mean).powi(2)).sum::<f32>() / rates.len() as f32;
110
111 (variance / (variance + 1.0)).min(1.0)
113 }
114
115 fn parallelism_factor(&self, edges: &[GpuDFGEdge]) -> f32 {
116 if edges.is_empty() {
118 return 1.0;
119 }
120
121 let mut out_counts: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
123 for edge in edges {
124 *out_counts.entry(edge.source_activity).or_insert(0) += 1;
125 }
126
127 let split_count = out_counts.values().filter(|&&c| c > 1).count();
128 1.0 + split_count as f32 * 0.5
129 }
130
131 pub fn metrics(&self) -> &DFGMetrics {
133 &self.metrics
134 }
135}
136
137#[derive(Debug, Clone, Default)]
139pub struct DFGMetrics {
140 pub node_count: usize,
142 pub edge_count: usize,
144 pub total_events: u64,
146 pub avg_events_per_node: f32,
148 pub density: f32,
150 pub avg_degree: f32,
152 pub max_in_degree: u32,
154 pub max_out_degree: u32,
156 pub avg_duration_ms: f32,
158 pub total_cost: f64,
160 pub bottleneck_score: f32,
162 pub parallelism_factor: f32,
164}
165
166impl DFGMetrics {
167 pub fn complexity_score(&self) -> f32 {
169 self.density * 0.4 + (self.avg_degree / 10.0).min(1.0) * 0.3 + self.bottleneck_score * 0.3
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_metrics_calculation() {
179 let mut dfg = DFGGraph::default();
180
181 dfg.update_node(1, 100, 5000.0, 100.0);
183 dfg.update_node(2, 80, 3000.0, 80.0);
184 dfg.update_edge(1, 2, 50, 2000.0);
185
186 let mut calc = DFGMetricsCalculator::new();
187 let metrics = calc.calculate(&dfg);
188
189 assert!(metrics.node_count > 0);
190 assert!(metrics.total_events > 0);
191 }
192}