forgekit_runtime/
metrics.rs1use std::sync::atomic::{AtomicU64, Ordering};
4use std::sync::Arc;
5use std::time::Duration;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum MetricKind {
10 GraphQuery,
12 Search,
14 CfgAnalysis,
16 CacheHit,
18 CacheMiss,
20 Reindex,
22}
23
24#[derive(Clone, Debug)]
28pub struct RuntimeMetrics {
29 inner: Arc<MetricsInner>,
30}
31
32#[derive(Debug)]
33struct MetricsInner {
34 counts: [AtomicU64; 6],
36 total_time_ns: AtomicU64,
38 cache_hits: AtomicU64,
40 cache_misses: AtomicU64,
42}
43
44impl RuntimeMetrics {
45 pub fn new() -> Self {
47 Self {
48 inner: Arc::new(MetricsInner {
49 counts: [
50 AtomicU64::new(0), AtomicU64::new(0), AtomicU64::new(0), AtomicU64::new(0), AtomicU64::new(0), AtomicU64::new(0), ],
57 total_time_ns: AtomicU64::new(0),
58 cache_hits: AtomicU64::new(0),
59 cache_misses: AtomicU64::new(0),
60 }),
61 }
62 }
63
64 pub fn record(&self, kind: MetricKind) {
66 let index = kind as usize;
67 self.inner.counts[index].fetch_add(1, Ordering::Relaxed);
68 }
69
70 pub fn record_timing(&self, kind: MetricKind, duration: Duration) {
72 self.record(kind);
73 self.inner
74 .total_time_ns
75 .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed);
76 }
77
78 pub fn record_cache_access(&self, hit: bool) {
80 if hit {
81 self.inner.cache_hits.fetch_add(1, Ordering::Relaxed);
82 self.record(MetricKind::CacheHit);
83 } else {
84 self.inner.cache_misses.fetch_add(1, Ordering::Relaxed);
85 self.record(MetricKind::CacheMiss);
86 }
87 }
88
89 pub fn count(&self, kind: MetricKind) -> u64 {
91 self.inner.counts[kind as usize].load(Ordering::Relaxed)
92 }
93
94 pub fn total_time(&self) -> Duration {
96 Duration::from_nanos(self.inner.total_time_ns.load(Ordering::Relaxed))
97 }
98
99 pub fn cache_hit_rate(&self) -> f64 {
101 let hits = self.inner.cache_hits.load(Ordering::Relaxed);
102 let misses = self.inner.cache_misses.load(Ordering::Relaxed);
103 let total = hits + misses;
104
105 if total == 0 {
106 return 0.0;
107 }
108
109 hits as f64 / total as f64
110 }
111
112 pub fn summary(&self) -> MetricsSummary {
114 MetricsSummary {
115 graph_queries: self.count(MetricKind::GraphQuery),
116 searches: self.count(MetricKind::Search),
117 cfg_analyses: self.count(MetricKind::CfgAnalysis),
118 reindex_ops: self.count(MetricKind::Reindex),
119 total_time: self.total_time(),
120 cache_hit_rate: self.cache_hit_rate(),
121 }
122 }
123
124 pub fn reset(&self) {
126 for count in &self.inner.counts {
127 count.store(0, Ordering::Relaxed);
128 }
129 self.inner.total_time_ns.store(0, Ordering::Relaxed);
130 self.inner.cache_hits.store(0, Ordering::Relaxed);
131 self.inner.cache_misses.store(0, Ordering::Relaxed);
132 }
133}
134
135impl Default for RuntimeMetrics {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141#[derive(Debug, Clone)]
143pub struct MetricsSummary {
144 pub graph_queries: u64,
146 pub searches: u64,
148 pub cfg_analyses: u64,
150 pub reindex_ops: u64,
152 pub total_time: Duration,
154 pub cache_hit_rate: f64,
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn test_metrics_record() {
164 let metrics = RuntimeMetrics::new();
165
166 metrics.record(MetricKind::GraphQuery);
167 metrics.record(MetricKind::GraphQuery);
168
169 assert_eq!(metrics.count(MetricKind::GraphQuery), 2);
170 assert_eq!(metrics.count(MetricKind::Search), 0);
171 }
172
173 #[test]
174 fn test_metrics_timing() {
175 let metrics = RuntimeMetrics::new();
176
177 metrics.record_timing(MetricKind::Search, Duration::from_millis(100));
178
179 assert_eq!(metrics.count(MetricKind::Search), 1);
180 assert_eq!(metrics.total_time(), Duration::from_millis(100));
181 }
182
183 #[test]
184 fn test_cache_hit_rate() {
185 let metrics = RuntimeMetrics::new();
186
187 metrics.record_cache_access(true);
188 metrics.record_cache_access(true);
189 metrics.record_cache_access(false);
190
191 assert!((metrics.cache_hit_rate() - 0.666).abs() < 0.01);
193 }
194
195 #[test]
196 fn test_metrics_reset() {
197 let metrics = RuntimeMetrics::new();
198
199 metrics.record(MetricKind::GraphQuery);
200 metrics.record_cache_access(true);
201
202 metrics.reset();
203
204 assert_eq!(metrics.count(MetricKind::GraphQuery), 0);
205 assert_eq!(metrics.cache_hit_rate(), 0.0);
206 }
207
208 #[test]
209 fn test_metrics_summary() {
210 let metrics = RuntimeMetrics::new();
211
212 metrics.record(MetricKind::GraphQuery);
213 metrics.record(MetricKind::Search);
214 metrics.record(MetricKind::CfgAnalysis);
215 metrics.record_cache_access(true);
216
217 let summary = metrics.summary();
218
219 assert_eq!(summary.graph_queries, 1);
220 assert_eq!(summary.searches, 1);
221 assert_eq!(summary.cfg_analyses, 1);
222 assert_eq!(summary.cache_hit_rate, 1.0);
223 }
224}