Skip to main content

swarm_engine_core/online_stats/
dumpable.rs

1//! Dumpable Stats - TickDumper 連携
2//!
3//! SwarmStats を Dumpable として公開し、デバッグ出力に含める。
4
5use std::sync::{Arc, RwLock};
6
7use crate::debug::Dumpable;
8
9use super::swarm::SwarmStats;
10
11/// SwarmStats を Dumpable として公開
12///
13/// TickDumper に登録することで、Tick 単位のデバッグ出力に統計情報を含められる。
14pub struct DumpableStats {
15    stats: Arc<RwLock<SwarmStats>>,
16}
17
18impl DumpableStats {
19    pub fn new(stats: Arc<RwLock<SwarmStats>>) -> Self {
20        Self { stats }
21    }
22}
23
24impl Dumpable for DumpableStats {
25    fn name(&self) -> &'static str {
26        "swarm_stats"
27    }
28
29    fn snapshot(&self, _tick: u64) -> Option<serde_json::Value> {
30        let stats = self.stats.read().ok()?;
31        let global = stats.global();
32
33        let action_stats: serde_json::Map<String, serde_json::Value> = stats
34            .all_action_stats()
35            .map(|(action, s)| {
36                (
37                    action.clone(),
38                    serde_json::json!({
39                        "visits": s.visits,
40                        "successes": s.successes,
41                        "failures": s.failures,
42                        "success_rate": s.success_rate(),
43                        "avg_duration_ms": s.avg_duration().as_millis(),
44                    }),
45                )
46            })
47            .collect();
48
49        Some(serde_json::json!({
50            "global": {
51                "total_visits": global.total_visits,
52                "total_successes": global.total_successes,
53                "total_failures": global.total_failures,
54                "success_rate": global.success_rate(),
55            },
56            "actions": action_stats,
57        }))
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use std::time::Duration;
64
65    use super::*;
66    use crate::events::{ActionEventBuilder, ActionEventResult};
67    use crate::types::WorkerId;
68
69    fn make_event(tick: u64, action: &str, success: bool) -> crate::events::ActionEvent {
70        let result = if success {
71            ActionEventResult::success()
72        } else {
73            ActionEventResult::failure("error")
74        };
75
76        ActionEventBuilder::new(tick, WorkerId(0), action)
77            .result(result)
78            .duration(Duration::from_millis(50))
79            .build()
80    }
81
82    #[test]
83    fn test_dumpable_stats() {
84        let stats = Arc::new(RwLock::new(SwarmStats::new()));
85
86        {
87            let mut s = stats.write().unwrap();
88            s.record(&make_event(1, "CheckStatus", true));
89            s.record(&make_event(2, "ReadLogs", true));
90            s.record(&make_event(3, "CheckStatus", false));
91        }
92
93        let dumpable = DumpableStats::new(Arc::clone(&stats));
94        let snapshot = dumpable.snapshot(0).unwrap();
95
96        assert_eq!(snapshot["global"]["total_visits"], 3);
97        assert_eq!(snapshot["global"]["total_successes"], 2);
98        assert!(snapshot["actions"]["CheckStatus"].is_object());
99    }
100}