Skip to main content

confium_coordinator/coordinator/
metrics_aggregator.rs

1//! Multi-coordinator metrics aggregator.
2
3use serde::{Deserialize, Serialize};
4
5/// Metrics from a single coordinator instance.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct InstanceMetrics {
8    pub instance_id: String,
9    pub active_sessions: u64,
10    pub total_created: u64,
11    pub total_completed: u64,
12    pub total_expired: u64,
13    pub registered_signers: u64,
14    pub aggregations_attempted: u64,
15    pub aggregations_failed: u64,
16}
17
18/// Aggregated metrics across multiple coordinator instances.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct AggregatedMetrics {
21    pub instance_count: usize,
22    pub total_active_sessions: u64,
23    pub total_created: u64,
24    pub total_completed: u64,
25    pub total_expired: u64,
26    pub total_signers: u64,
27    pub total_aggregations: u64,
28    pub total_failures: u64,
29    pub overall_success_rate: f64,
30    pub avg_sessions_per_instance: f64,
31    pub max_active_sessions: u64,
32}
33
34/// Aggregate metrics from multiple coordinator instances.
35pub fn aggregate(instances: &[InstanceMetrics]) -> AggregatedMetrics {
36    let count = instances.len() as u64;
37    if count == 0 {
38        return AggregatedMetrics {
39            instance_count: 0,
40            total_active_sessions: 0,
41            total_created: 0,
42            total_completed: 0,
43            total_expired: 0,
44            total_signers: 0,
45            total_aggregations: 0,
46            total_failures: 0,
47            overall_success_rate: 1.0,
48            avg_sessions_per_instance: 0.0,
49            max_active_sessions: 0,
50        };
51    }
52
53    let total_active: u64 = instances.iter().map(|i| i.active_sessions).sum();
54    let total_created: u64 = instances.iter().map(|i| i.total_created).sum();
55    let total_completed: u64 = instances.iter().map(|i| i.total_completed).sum();
56    let total_expired: u64 = instances.iter().map(|i| i.total_expired).sum();
57    let total_signers: u64 = instances.iter().map(|i| i.registered_signers).sum();
58    let total_agg: u64 = instances.iter().map(|i| i.aggregations_attempted).sum();
59    let total_fail: u64 = instances.iter().map(|i| i.aggregations_failed).sum();
60    let max_active: u64 = instances
61        .iter()
62        .map(|i| i.active_sessions)
63        .max()
64        .unwrap_or(0);
65    let success_rate = if total_agg > 0 {
66        1.0 - (total_fail as f64 / total_agg as f64)
67    } else {
68        1.0
69    };
70
71    AggregatedMetrics {
72        instance_count: instances.len(),
73        total_active_sessions: total_active,
74        total_created,
75        total_completed,
76        total_expired,
77        total_signers,
78        total_aggregations: total_agg,
79        total_failures: total_fail,
80        overall_success_rate: success_rate,
81        avg_sessions_per_instance: total_active as f64 / count as f64,
82        max_active_sessions: max_active,
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    fn make_instance(id: &str, active: u64, created: u64) -> InstanceMetrics {
91        InstanceMetrics {
92            instance_id: id.into(),
93            active_sessions: active,
94            total_created: created,
95            total_completed: created / 2,
96            total_expired: 0,
97            registered_signers: 3,
98            aggregations_attempted: 100,
99            aggregations_failed: 5,
100        }
101    }
102
103    #[test]
104    fn empty_instances() {
105        let agg = aggregate(&[]);
106        assert_eq!(agg.instance_count, 0);
107        assert_eq!(agg.total_active_sessions, 0);
108    }
109
110    #[test]
111    fn single_instance() {
112        let agg = aggregate(&[make_instance("a", 5, 10)]);
113        assert_eq!(agg.instance_count, 1);
114        assert_eq!(agg.total_active_sessions, 5);
115        assert_eq!(agg.total_created, 10);
116    }
117
118    #[test]
119    fn sums_across_instances() {
120        let agg = aggregate(&[make_instance("a", 5, 10), make_instance("b", 3, 20)]);
121        assert_eq!(agg.total_active_sessions, 8);
122        assert_eq!(agg.total_created, 30);
123        assert_eq!(agg.total_signers, 6);
124    }
125
126    #[test]
127    fn success_rate_computed() {
128        let agg = aggregate(&[make_instance("a", 0, 0)]);
129        assert!((agg.overall_success_rate - 0.95).abs() < 0.001);
130    }
131
132    #[test]
133    fn avg_sessions() {
134        let agg = aggregate(&[make_instance("a", 4, 0), make_instance("b", 6, 0)]);
135        assert!((agg.avg_sessions_per_instance - 5.0).abs() < 0.001);
136    }
137
138    #[test]
139    fn max_active_sessions() {
140        let agg = aggregate(&[
141            make_instance("a", 3, 0),
142            make_instance("b", 7, 0),
143            make_instance("c", 5, 0),
144        ]);
145        assert_eq!(agg.max_active_sessions, 7);
146    }
147
148    #[test]
149    fn serializes() {
150        let agg = aggregate(&[make_instance("a", 1, 1)]);
151        let json = serde_json::to_string(&agg).unwrap();
152        assert!(json.contains("instance_count"));
153    }
154}