1use dashmap::DashMap;
4use std::collections::HashMap;
5use std::sync::Arc;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::time::{Duration, Instant};
8
9#[derive(Debug, Clone)]
11pub struct RoleSystemMetrics {
12 pub permission_checks: Arc<AtomicU64>,
14 pub cache_hits: Arc<AtomicU64>,
16 pub cache_misses: Arc<AtomicU64>,
18 pub role_assignments: Arc<AtomicU64>,
20 pub role_removals: Arc<AtomicU64>,
22 pub role_elevations: Arc<AtomicU64>,
24 pub permission_check_durations: Arc<DashMap<String, Duration>>,
26 pub error_counts: Arc<DashMap<String, AtomicU64>>,
28 pub subject_activity: Arc<DashMap<String, AtomicU64>>,
30}
31
32impl Default for RoleSystemMetrics {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl RoleSystemMetrics {
39 pub fn new() -> Self {
41 Self {
42 permission_checks: Arc::new(AtomicU64::new(0)),
43 cache_hits: Arc::new(AtomicU64::new(0)),
44 cache_misses: Arc::new(AtomicU64::new(0)),
45 role_assignments: Arc::new(AtomicU64::new(0)),
46 role_removals: Arc::new(AtomicU64::new(0)),
47 role_elevations: Arc::new(AtomicU64::new(0)),
48 permission_check_durations: Arc::new(DashMap::new()),
49 error_counts: Arc::new(DashMap::new()),
50 subject_activity: Arc::new(DashMap::new()),
51 }
52 }
53
54 pub fn record_permission_check(&self, duration: Duration) {
56 self.permission_checks.fetch_add(1, Ordering::Relaxed);
57
58 let bucket = self.duration_to_bucket(duration);
60 self.permission_check_durations
61 .entry(bucket)
62 .and_modify(|existing| {
63 if duration > *existing {
64 *existing = duration;
65 }
66 })
67 .or_insert(duration);
68 }
69
70 pub fn record_cache_hit(&self) {
72 self.cache_hits.fetch_add(1, Ordering::Relaxed);
73 }
74
75 pub fn record_cache_miss(&self) {
77 self.cache_misses.fetch_add(1, Ordering::Relaxed);
78 }
79
80 pub fn record_role_assignment(&self, subject_id: &str) {
82 self.role_assignments.fetch_add(1, Ordering::Relaxed);
83 self.record_subject_activity(subject_id);
84 }
85
86 pub fn record_role_removal(&self, subject_id: &str) {
88 self.role_removals.fetch_add(1, Ordering::Relaxed);
89 self.record_subject_activity(subject_id);
90 }
91
92 pub fn record_role_elevation(&self, subject_id: &str) {
94 self.role_elevations.fetch_add(1, Ordering::Relaxed);
95 self.record_subject_activity(subject_id);
96 }
97
98 pub fn record_error(&self, error_type: &str) {
100 self.error_counts
101 .entry(error_type.to_string())
102 .and_modify(|count| {
103 count.fetch_add(1, Ordering::Relaxed);
104 })
105 .or_insert_with(|| AtomicU64::new(1));
106 }
107
108 pub fn record_subject_activity(&self, subject_id: &str) {
110 self.subject_activity
111 .entry(subject_id.to_string())
112 .and_modify(|count| {
113 count.fetch_add(1, Ordering::Relaxed);
114 })
115 .or_insert_with(|| AtomicU64::new(1));
116 }
117
118 pub fn cache_hit_ratio(&self) -> f64 {
120 let hits = self.cache_hits.load(Ordering::Relaxed);
121 let misses = self.cache_misses.load(Ordering::Relaxed);
122 let total = hits + misses;
123
124 if total == 0 {
125 0.0
126 } else {
127 hits as f64 / total as f64
128 }
129 }
130
131 pub fn summary(&self) -> MetricsSummary {
133 MetricsSummary {
134 permission_checks: self.permission_checks.load(Ordering::Relaxed),
135 cache_hits: self.cache_hits.load(Ordering::Relaxed),
136 cache_misses: self.cache_misses.load(Ordering::Relaxed),
137 cache_hit_ratio: self.cache_hit_ratio(),
138 role_assignments: self.role_assignments.load(Ordering::Relaxed),
139 role_removals: self.role_removals.load(Ordering::Relaxed),
140 role_elevations: self.role_elevations.load(Ordering::Relaxed),
141 error_counts: self
142 .error_counts
143 .iter()
144 .map(|entry| (entry.key().clone(), entry.value().load(Ordering::Relaxed)))
145 .collect(),
146 active_subjects: self.subject_activity.len() as u64,
147 }
148 }
149
150 pub fn reset(&self) {
152 self.permission_checks.store(0, Ordering::Relaxed);
153 self.cache_hits.store(0, Ordering::Relaxed);
154 self.cache_misses.store(0, Ordering::Relaxed);
155 self.role_assignments.store(0, Ordering::Relaxed);
156 self.role_removals.store(0, Ordering::Relaxed);
157 self.role_elevations.store(0, Ordering::Relaxed);
158 self.permission_check_durations.clear();
159 self.error_counts.clear();
160 self.subject_activity.clear();
161 }
162
163 fn duration_to_bucket(&self, duration: Duration) -> String {
164 let micros = duration.as_micros();
165 match micros {
166 0..=99 => "0-99μs".to_string(),
167 100..=999 => "100-999μs".to_string(),
168 1000..=9999 => "1-9ms".to_string(),
169 10000..=99999 => "10-99ms".to_string(),
170 100000..=999999 => "100-999ms".to_string(),
171 _ => "1s+".to_string(),
172 }
173 }
174}
175
176#[derive(Debug, Clone)]
178pub struct MetricsSummary {
179 pub permission_checks: u64,
180 pub cache_hits: u64,
181 pub cache_misses: u64,
182 pub cache_hit_ratio: f64,
183 pub role_assignments: u64,
184 pub role_removals: u64,
185 pub role_elevations: u64,
186 pub error_counts: HashMap<String, u64>,
187 pub active_subjects: u64,
188}
189
190pub struct MetricsTimer {
192 start: Instant,
193 metrics: Arc<RoleSystemMetrics>,
194 operation: String,
195}
196
197impl MetricsTimer {
198 pub fn new(metrics: Arc<RoleSystemMetrics>, operation: impl Into<String>) -> Self {
200 Self {
201 start: Instant::now(),
202 metrics,
203 operation: operation.into(),
204 }
205 }
206}
207
208impl Drop for MetricsTimer {
209 fn drop(&mut self) {
210 let duration = self.start.elapsed();
211 if self.operation == "permission_check" {
212 self.metrics.record_permission_check(duration);
213 }
214 }
215}
216
217pub trait MetricsProvider {
219 fn metrics(&self) -> &RoleSystemMetrics;
221
222 fn start_timer(&self, operation: &str) -> MetricsTimer {
224 MetricsTimer::new(Arc::new(self.metrics().clone()), operation.to_string())
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use std::thread;
232 use std::time::Duration as StdDuration;
233
234 #[test]
235 fn test_metrics_basic_operations() {
236 let metrics = RoleSystemMetrics::new();
237
238 metrics.record_permission_check(Duration::from_micros(500));
240 assert_eq!(metrics.permission_checks.load(Ordering::Relaxed), 1);
241
242 metrics.record_cache_hit();
244 metrics.record_cache_miss();
245 assert_eq!(metrics.cache_hits.load(Ordering::Relaxed), 1);
246 assert_eq!(metrics.cache_misses.load(Ordering::Relaxed), 1);
247 assert_eq!(metrics.cache_hit_ratio(), 0.5);
248
249 metrics.record_role_assignment("user1");
251 metrics.record_role_removal("user2");
252 metrics.record_role_elevation("user3");
253 assert_eq!(metrics.role_assignments.load(Ordering::Relaxed), 1);
254 assert_eq!(metrics.role_removals.load(Ordering::Relaxed), 1);
255 assert_eq!(metrics.role_elevations.load(Ordering::Relaxed), 1);
256
257 metrics.record_error("ValidationError");
259 metrics.record_error("ValidationError");
260 assert_eq!(
261 metrics
262 .error_counts
263 .get("ValidationError")
264 .unwrap()
265 .load(Ordering::Relaxed),
266 2
267 );
268 }
269
270 #[test]
271 fn test_metrics_summary() {
272 let metrics = RoleSystemMetrics::new();
273
274 metrics.record_permission_check(Duration::from_millis(1));
275 metrics.record_cache_hit();
276 metrics.record_role_assignment("user1");
277 metrics.record_error("TestError");
278
279 let summary = metrics.summary();
280 assert_eq!(summary.permission_checks, 1);
281 assert_eq!(summary.cache_hits, 1);
282 assert_eq!(summary.role_assignments, 1);
283 assert_eq!(summary.error_counts.get("TestError"), Some(&1));
284 assert_eq!(summary.active_subjects, 1);
285 }
286
287 #[test]
288 fn test_metrics_reset() {
289 let metrics = RoleSystemMetrics::new();
290
291 metrics.record_permission_check(Duration::from_millis(1));
292 metrics.record_cache_hit();
293 metrics.record_role_assignment("user1");
294
295 metrics.reset();
296
297 let summary = metrics.summary();
298 assert_eq!(summary.permission_checks, 0);
299 assert_eq!(summary.cache_hits, 0);
300 assert_eq!(summary.role_assignments, 0);
301 assert_eq!(summary.active_subjects, 0);
302 }
303
304 #[test]
305 fn test_metrics_timer() {
306 let metrics = Arc::new(RoleSystemMetrics::new());
307
308 {
309 let _timer = MetricsTimer::new(metrics.clone(), "permission_check");
310 thread::sleep(StdDuration::from_millis(1));
311 } assert_eq!(metrics.permission_checks.load(Ordering::Relaxed), 1);
314 }
315
316 #[test]
317 fn test_duration_bucketing() {
318 let metrics = RoleSystemMetrics::new();
319
320 assert_eq!(
321 metrics.duration_to_bucket(Duration::from_micros(50)),
322 "0-99μs"
323 );
324 assert_eq!(
325 metrics.duration_to_bucket(Duration::from_micros(500)),
326 "100-999μs"
327 );
328 assert_eq!(
329 metrics.duration_to_bucket(Duration::from_millis(5)),
330 "1-9ms"
331 );
332 assert_eq!(
333 metrics.duration_to_bucket(Duration::from_millis(50)),
334 "10-99ms"
335 );
336 assert_eq!(
337 metrics.duration_to_bucket(Duration::from_millis(500)),
338 "100-999ms"
339 );
340 assert_eq!(metrics.duration_to_bucket(Duration::from_secs(2)), "1s+");
341 }
342}