1use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
4use std::sync::Arc;
5use std::time::{Duration, Instant};
6
7#[derive(Debug)]
9pub struct PerfCounter {
10 count: AtomicU64,
12 total_time_ns: AtomicU64,
14 min_time_ns: AtomicU64,
16 max_time_ns: AtomicU64,
18}
19
20impl PerfCounter {
21 pub fn new() -> Self {
23 Self {
24 count: AtomicU64::new(0),
25 total_time_ns: AtomicU64::new(0),
26 min_time_ns: AtomicU64::new(u64::MAX),
27 max_time_ns: AtomicU64::new(0),
28 }
29 }
30
31 pub fn record(&self, duration: Duration) {
33 let nanos = duration.as_nanos() as u64;
34
35 self.count.fetch_add(1, Ordering::Relaxed);
36 self.total_time_ns.fetch_add(nanos, Ordering::Relaxed);
37
38 let mut current_min = self.min_time_ns.load(Ordering::Relaxed);
40 while nanos < current_min {
41 match self.min_time_ns.compare_exchange_weak(
42 current_min,
43 nanos,
44 Ordering::Relaxed,
45 Ordering::Relaxed,
46 ) {
47 Ok(_) => break,
48 Err(x) => current_min = x,
49 }
50 }
51
52 let mut current_max = self.max_time_ns.load(Ordering::Relaxed);
54 while nanos > current_max {
55 match self.max_time_ns.compare_exchange_weak(
56 current_max,
57 nanos,
58 Ordering::Relaxed,
59 Ordering::Relaxed,
60 ) {
61 Ok(_) => break,
62 Err(x) => current_max = x,
63 }
64 }
65 }
66
67 pub fn stats(&self) -> PerfStats {
69 let count = self.count.load(Ordering::Relaxed);
70 let total_ns = self.total_time_ns.load(Ordering::Relaxed);
71 let min_ns = self.min_time_ns.load(Ordering::Relaxed);
72 let max_ns = self.max_time_ns.load(Ordering::Relaxed);
73
74 let avg_ns = if count > 0 { total_ns / count } else { 0 };
75
76 PerfStats {
77 count,
78 total_time: Duration::from_nanos(total_ns),
79 avg_time: Duration::from_nanos(avg_ns),
80 min_time: if min_ns == u64::MAX {
81 Duration::ZERO
82 } else {
83 Duration::from_nanos(min_ns)
84 },
85 max_time: Duration::from_nanos(max_ns),
86 }
87 }
88
89 pub fn reset(&self) {
91 self.count.store(0, Ordering::Relaxed);
92 self.total_time_ns.store(0, Ordering::Relaxed);
93 self.min_time_ns.store(u64::MAX, Ordering::Relaxed);
94 self.max_time_ns.store(0, Ordering::Relaxed);
95 }
96}
97
98impl Default for PerfCounter {
99 fn default() -> Self {
100 Self::new()
101 }
102}
103
104#[derive(Debug, Clone)]
106pub struct PerfStats {
107 pub count: u64,
109 pub total_time: Duration,
111 pub avg_time: Duration,
113 pub min_time: Duration,
115 pub max_time: Duration,
117}
118
119impl PerfStats {
120 pub fn ops_per_sec(&self) -> f64 {
122 if self.total_time.is_zero() {
123 0.0
124 } else {
125 self.count as f64 / self.total_time.as_secs_f64()
126 }
127 }
128
129 pub fn throughput_bps(&self, bytes_per_op: u64) -> f64 {
131 self.ops_per_sec() * bytes_per_op as f64
132 }
133}
134
135pub struct PerfTimer<'a> {
137 counter: &'a PerfCounter,
138 start: Instant,
139}
140
141impl<'a> PerfTimer<'a> {
142 pub fn new(counter: &'a PerfCounter) -> Self {
144 Self {
145 counter,
146 start: Instant::now(),
147 }
148 }
149}
150
151impl<'a> Drop for PerfTimer<'a> {
152 fn drop(&mut self) {
153 let duration = self.start.elapsed();
154 self.counter.record(duration);
155 }
156}
157
158#[derive(Debug)]
160pub struct MemoryTracker {
161 allocated: AtomicUsize,
163 peak: AtomicUsize,
165 total_allocs: AtomicU64,
167 total_deallocs: AtomicU64,
169}
170
171impl MemoryTracker {
172 pub fn new() -> Self {
174 Self {
175 allocated: AtomicUsize::new(0),
176 peak: AtomicUsize::new(0),
177 total_allocs: AtomicU64::new(0),
178 total_deallocs: AtomicU64::new(0),
179 }
180 }
181
182 pub fn record_alloc(&self, size: usize) {
184 let new_allocated = self.allocated.fetch_add(size, Ordering::Relaxed) + size;
185 self.total_allocs.fetch_add(1, Ordering::Relaxed);
186
187 let mut current_peak = self.peak.load(Ordering::Relaxed);
189 while new_allocated > current_peak {
190 match self.peak.compare_exchange_weak(
191 current_peak,
192 new_allocated,
193 Ordering::Relaxed,
194 Ordering::Relaxed,
195 ) {
196 Ok(_) => break,
197 Err(x) => current_peak = x,
198 }
199 }
200 }
201
202 pub fn record_dealloc(&self, size: usize) {
204 self.allocated.fetch_sub(size, Ordering::Relaxed);
205 self.total_deallocs.fetch_add(1, Ordering::Relaxed);
206 }
207
208 pub fn current_usage(&self) -> usize {
210 self.allocated.load(Ordering::Relaxed)
211 }
212
213 pub fn peak_usage(&self) -> usize {
215 self.peak.load(Ordering::Relaxed)
216 }
217
218 pub fn stats(&self) -> MemoryStats {
220 MemoryStats {
221 current: self.allocated.load(Ordering::Relaxed),
222 peak: self.peak.load(Ordering::Relaxed),
223 total_allocs: self.total_allocs.load(Ordering::Relaxed),
224 total_deallocs: self.total_deallocs.load(Ordering::Relaxed),
225 }
226 }
227
228 pub fn reset(&self) {
230 self.allocated.store(0, Ordering::Relaxed);
231 self.peak.store(0, Ordering::Relaxed);
232 self.total_allocs.store(0, Ordering::Relaxed);
233 self.total_deallocs.store(0, Ordering::Relaxed);
234 }
235}
236
237impl Default for MemoryTracker {
238 fn default() -> Self {
239 Self::new()
240 }
241}
242
243#[derive(Debug, Clone)]
245pub struct MemoryStats {
246 pub current: usize,
248 pub peak: usize,
250 pub total_allocs: u64,
252 pub total_deallocs: u64,
254}
255
256impl MemoryStats {
257 pub fn alloc_rate(&self, duration: Duration) -> f64 {
259 if duration.is_zero() {
260 0.0
261 } else {
262 self.total_allocs as f64 / duration.as_secs_f64()
263 }
264 }
265
266 pub fn avg_alloc_size(&self) -> f64 {
268 if self.total_allocs > 0 {
269 self.peak as f64 / self.total_allocs as f64
270 } else {
271 0.0
272 }
273 }
274}
275
276pub struct PerfRegistry {
278 counters: std::collections::HashMap<String, Arc<PerfCounter>>,
279 memory_tracker: Arc<MemoryTracker>,
280}
281
282impl PerfRegistry {
283 pub fn new() -> Self {
285 Self {
286 counters: std::collections::HashMap::new(),
287 memory_tracker: Arc::new(MemoryTracker::new()),
288 }
289 }
290
291 pub fn counter(&mut self, name: &str) -> Arc<PerfCounter> {
293 self.counters
294 .entry(name.to_string())
295 .or_insert_with(|| Arc::new(PerfCounter::new()))
296 .clone()
297 }
298
299 pub fn memory_tracker(&self) -> Arc<MemoryTracker> {
301 self.memory_tracker.clone()
302 }
303
304 pub fn all_stats(&self) -> std::collections::HashMap<String, PerfStats> {
306 self.counters
307 .iter()
308 .map(|(name, counter)| (name.clone(), counter.stats()))
309 .collect()
310 }
311
312 pub fn reset_all(&self) {
314 for counter in self.counters.values() {
315 counter.reset();
316 }
317 self.memory_tracker.reset();
318 }
319}
320
321impl Default for PerfRegistry {
322 fn default() -> Self {
323 Self::new()
324 }
325}
326
327#[macro_export]
329macro_rules! time_operation {
330 ($counter:expr, $operation:expr) => {{
331 let _timer = $crate::perf::PerfTimer::new($counter);
332 $operation
333 }};
334}
335
336#[macro_export]
338macro_rules! time_async_operation {
339 ($counter:expr, $operation:expr) => {{
340 let start = std::time::Instant::now();
341 let result = $operation.await;
342 $counter.record(start.elapsed());
343 result
344 }};
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350 use std::thread;
351 use std::time::Duration;
352
353 #[test]
354 fn test_perf_counter() {
355 let counter = PerfCounter::new();
356
357 counter.record(Duration::from_millis(10));
358 counter.record(Duration::from_millis(20));
359 counter.record(Duration::from_millis(5));
360
361 let stats = counter.stats();
362 assert_eq!(stats.count, 3);
363 assert_eq!(stats.min_time, Duration::from_millis(5));
364 assert_eq!(stats.max_time, Duration::from_millis(20));
365 assert!(stats.avg_time >= Duration::from_millis(11));
366 assert!(stats.avg_time <= Duration::from_millis(12));
367 }
368
369 #[test]
370 fn test_perf_timer() {
371 let counter = PerfCounter::new();
372
373 {
374 let _timer = PerfTimer::new(&counter);
375 thread::sleep(Duration::from_millis(10));
376 }
377
378 let stats = counter.stats();
379 assert_eq!(stats.count, 1);
380 assert!(stats.total_time >= Duration::from_millis(9));
381 }
382
383 #[test]
384 fn test_memory_tracker() {
385 let tracker = MemoryTracker::new();
386
387 tracker.record_alloc(1024);
388 assert_eq!(tracker.current_usage(), 1024);
389 assert_eq!(tracker.peak_usage(), 1024);
390
391 tracker.record_alloc(2048);
392 assert_eq!(tracker.current_usage(), 3072);
393 assert_eq!(tracker.peak_usage(), 3072);
394
395 tracker.record_dealloc(1024);
396 assert_eq!(tracker.current_usage(), 2048);
397 assert_eq!(tracker.peak_usage(), 3072); }
399
400 #[test]
401 fn test_perf_registry() {
402 let mut registry = PerfRegistry::new();
403
404 let counter1 = registry.counter("test1");
405 let counter2 = registry.counter("test2");
406
407 counter1.record(Duration::from_millis(10));
408 counter2.record(Duration::from_millis(20));
409
410 let all_stats = registry.all_stats();
411 assert_eq!(all_stats.len(), 2);
412 assert!(all_stats.contains_key("test1"));
413 assert!(all_stats.contains_key("test2"));
414 }
415
416 #[test]
417 fn test_concurrent_perf_counter() {
418 let counter = Arc::new(PerfCounter::new());
419 let mut handles = vec![];
420
421 for _ in 0..10 {
422 let counter_clone = counter.clone();
423 handles.push(thread::spawn(move || {
424 for _ in 0..100 {
425 counter_clone.record(Duration::from_nanos(1000));
426 }
427 }));
428 }
429
430 for handle in handles {
431 handle.join().unwrap();
432 }
433
434 let stats = counter.stats();
435 assert_eq!(stats.count, 1000);
436 }
437}