1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
6use std::time::{Duration, Instant};
7
8#[derive(Debug, Default)]
10pub struct SessionMetrics {
11 pub navigation_count: AtomicUsize,
13 pub expansion_count: AtomicUsize,
15 pub search_count: AtomicUsize,
17 pub context_add_count: AtomicUsize,
19 pub context_remove_count: AtomicUsize,
21 pub blocks_visited: AtomicUsize,
23 pub edges_followed: AtomicUsize,
25 pub total_execution_time_us: AtomicU64,
27 pub error_count: AtomicUsize,
29 pub budget_warnings: AtomicUsize,
31}
32
33impl SessionMetrics {
34 pub fn new() -> Self {
35 Self::default()
36 }
37
38 pub fn record_navigation(&self) {
39 self.navigation_count.fetch_add(1, Ordering::Relaxed);
40 }
41
42 pub fn record_expansion(&self, blocks_count: usize) {
43 self.expansion_count.fetch_add(1, Ordering::Relaxed);
44 self.blocks_visited
45 .fetch_add(blocks_count, Ordering::Relaxed);
46 }
47
48 pub fn record_search(&self) {
49 self.search_count.fetch_add(1, Ordering::Relaxed);
50 }
51
52 pub fn record_context_add(&self, count: usize) {
53 self.context_add_count.fetch_add(count, Ordering::Relaxed);
54 }
55
56 pub fn record_context_remove(&self) {
57 self.context_remove_count.fetch_add(1, Ordering::Relaxed);
58 }
59
60 pub fn record_traversal(&self) {
62 self.navigation_count.fetch_add(1, Ordering::Relaxed);
63 }
64
65 pub fn record_edges_followed(&self, count: usize) {
66 self.edges_followed.fetch_add(count, Ordering::Relaxed);
67 }
68
69 pub fn record_execution_time(&self, duration: Duration) {
70 self.total_execution_time_us
71 .fetch_add(duration.as_micros() as u64, Ordering::Relaxed);
72 }
73
74 pub fn record_error(&self) {
75 self.error_count.fetch_add(1, Ordering::Relaxed);
76 }
77
78 pub fn record_budget_warning(&self) {
79 self.budget_warnings.fetch_add(1, Ordering::Relaxed);
80 }
81
82 pub fn snapshot(&self) -> MetricsSnapshot {
84 MetricsSnapshot {
85 navigation_count: self.navigation_count.load(Ordering::Relaxed),
86 expansion_count: self.expansion_count.load(Ordering::Relaxed),
87 search_count: self.search_count.load(Ordering::Relaxed),
88 context_add_count: self.context_add_count.load(Ordering::Relaxed),
89 context_remove_count: self.context_remove_count.load(Ordering::Relaxed),
90 blocks_visited: self.blocks_visited.load(Ordering::Relaxed),
91 edges_followed: self.edges_followed.load(Ordering::Relaxed),
92 total_execution_time_us: self.total_execution_time_us.load(Ordering::Relaxed),
93 error_count: self.error_count.load(Ordering::Relaxed),
94 budget_warnings: self.budget_warnings.load(Ordering::Relaxed),
95 captured_at: Utc::now(),
96 }
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct MetricsSnapshot {
103 pub navigation_count: usize,
104 pub expansion_count: usize,
105 pub search_count: usize,
106 pub context_add_count: usize,
107 pub context_remove_count: usize,
108 pub blocks_visited: usize,
109 pub edges_followed: usize,
110 pub total_execution_time_us: u64,
111 pub error_count: usize,
112 pub budget_warnings: usize,
113 pub captured_at: DateTime<Utc>,
114}
115
116impl MetricsSnapshot {
117 pub fn total_operations(&self) -> usize {
118 self.navigation_count
119 + self.expansion_count
120 + self.search_count
121 + self.context_add_count
122 + self.context_remove_count
123 }
124
125 pub fn total_execution_time(&self) -> Duration {
126 Duration::from_micros(self.total_execution_time_us)
127 }
128
129 pub fn average_operation_time(&self) -> Option<Duration> {
130 let total = self.total_operations();
131 if total == 0 {
132 None
133 } else {
134 Some(Duration::from_micros(
135 self.total_execution_time_us / total as u64,
136 ))
137 }
138 }
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct OperationMetrics {
144 pub operation: String,
146 pub duration: Duration,
148 pub blocks_processed: usize,
150 pub success: bool,
152 pub timestamp: DateTime<Utc>,
154}
155
156impl OperationMetrics {
157 pub fn start(operation: &str) -> OperationMetricsBuilder {
158 OperationMetricsBuilder {
159 operation: operation.to_string(),
160 start: Instant::now(),
161 blocks_processed: 0,
162 }
163 }
164}
165
166pub struct OperationMetricsBuilder {
168 operation: String,
169 start: Instant,
170 blocks_processed: usize,
171}
172
173impl OperationMetricsBuilder {
174 pub fn blocks(mut self, count: usize) -> Self {
175 self.blocks_processed = count;
176 self
177 }
178
179 pub fn finish(self, success: bool) -> OperationMetrics {
180 OperationMetrics {
181 operation: self.operation,
182 duration: self.start.elapsed(),
183 blocks_processed: self.blocks_processed,
184 success,
185 timestamp: Utc::now(),
186 }
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_session_metrics() {
196 let metrics = SessionMetrics::new();
197
198 metrics.record_navigation();
199 metrics.record_expansion(5);
200 metrics.record_search();
201
202 let snapshot = metrics.snapshot();
203 assert_eq!(snapshot.navigation_count, 1);
204 assert_eq!(snapshot.expansion_count, 1);
205 assert_eq!(snapshot.search_count, 1);
206 assert_eq!(snapshot.blocks_visited, 5);
207 }
208
209 #[test]
210 fn test_operation_metrics() {
211 let op = OperationMetrics::start("navigate").blocks(3).finish(true);
212
213 assert_eq!(op.operation, "navigate");
214 assert!(op.success);
215 assert_eq!(op.blocks_processed, 3);
216 }
217}