1use fxhash::FxHashMap;
8use once_cell::sync::Lazy;
9use parking_lot::{Mutex, RwLock};
10use serde::{Deserialize, Serialize};
11use std::collections::VecDeque;
12use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
13use std::sync::Arc;
14use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
15
16pub static PERF_MONITOR: Lazy<PerformanceMonitor> = Lazy::new(PerformanceMonitor::new);
18
19#[derive(Debug)]
21pub struct PerformanceMonitor {
22 real_time: Arc<RealTimeMetrics>,
24 history: Arc<RwLock<PerformanceHistory>>,
26 system_tracker: Arc<Mutex<SystemResourceTracker>>,
28 profiles: Arc<RwLock<FxHashMap<String, OperationProfile>>>,
30 config: MonitoringConfig,
32}
33
34#[derive(Debug)]
36pub struct RealTimeMetrics {
37 pub files_processed: AtomicU64,
39 pub files_filtered: AtomicU64,
40 pub files_cached: AtomicU64,
41 pub files_failed: AtomicU64,
42 pub bytes_processed: AtomicU64,
43 pub bytes_read: AtomicU64,
44
45 pub total_scan_time_us: AtomicU64,
47 pub io_time_us: AtomicU64,
48 pub cpu_time_us: AtomicU64,
49 pub git_time_us: AtomicU64,
50 pub filter_time_us: AtomicU64,
51 pub parallel_time_us: AtomicU64,
52
53 pub peak_memory_bytes: AtomicU64,
55 pub current_memory_bytes: AtomicU64,
56 pub memory_allocations: AtomicU64,
57
58 pub active_threads: AtomicUsize,
60 pub peak_threads: AtomicUsize,
61 pub context_switches: AtomicU64,
62
63 pub cache_hits: AtomicU64,
65 pub cache_misses: AtomicU64,
66 pub cache_evictions: AtomicU64,
67
68 pub io_errors: AtomicU64,
70 pub git_errors: AtomicU64,
71 pub parsing_errors: AtomicU64,
72 pub other_errors: AtomicU64,
73}
74
75#[derive(Debug)]
77struct PerformanceHistory {
78 snapshots: VecDeque<PerformanceSnapshot>,
80 max_snapshots: usize,
82 aggregated: AggregatedStats,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct PerformanceSnapshot {
89 pub timestamp: u64,
90 pub files_per_second: f64,
91 pub bytes_per_second: f64,
92 pub memory_usage_mb: f64,
93 pub cpu_utilization: f64,
94 pub io_wait_percentage: f64,
95 pub cache_hit_rate: f64,
96 pub error_rate: f64,
97 pub active_threads: usize,
98 pub queue_depth: usize,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct AggregatedStats {
104 pub avg_throughput_fps: f64, pub p50_latency_ms: f64,
106 pub p95_latency_ms: f64,
107 pub p99_latency_ms: f64,
108 pub max_memory_mb: f64,
109 pub avg_memory_mb: f64,
110 pub total_files_processed: u64,
111 pub total_bytes_processed: u64,
112 pub total_runtime_seconds: f64,
113 pub cache_efficiency: f64,
114 pub error_percentage: f64,
115}
116
117#[derive(Debug)]
119struct SystemResourceTracker {
120 cpu_samples: VecDeque<CpuSample>,
122 memory_samples: VecDeque<MemorySample>,
124 io_stats: IoStats,
126 last_sample_time: Instant,
128}
129
130#[derive(Debug, Clone)]
132struct CpuSample {
133 timestamp: Instant,
134 user_time: Duration,
135 system_time: Duration,
136 idle_time: Duration,
137}
138
139#[derive(Debug, Clone)]
141struct MemorySample {
142 timestamp: Instant,
143 rss_bytes: u64, vms_bytes: u64, heap_bytes: u64, available_bytes: u64, }
148
149#[derive(Debug, Default)]
151struct IoStats {
152 pub reads_completed: u64,
153 pub writes_completed: u64,
154 pub bytes_read: u64,
155 pub bytes_written: u64,
156 pub read_time_ms: u64,
157 pub write_time_ms: u64,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct OperationProfile {
163 pub operation_name: String,
164 pub call_count: u64,
165 pub total_time_us: u64,
166 pub min_time_us: u64,
167 pub max_time_us: u64,
168 pub avg_time_us: u64,
169 pub p95_time_us: u64,
170 pub success_count: u64,
171 pub error_count: u64,
172 pub bytes_processed: u64,
173 pub last_updated: u64,
174}
175
176#[derive(Debug, Clone)]
178pub struct MonitoringConfig {
179 pub enable_profiling: bool,
181 pub sample_interval_ms: u64,
183 pub max_history_snapshots: usize,
185 pub report_interval_secs: u64,
187 pub track_memory: bool,
189 pub track_io: bool,
191 pub profile_operations: bool,
193}
194
195#[derive(Debug)]
197pub struct PerfTimer {
198 start_time: Instant,
199 operation_name: String,
200 bytes_hint: Option<u64>,
201}
202
203#[macro_export]
205macro_rules! perf_timer {
206 ($operation:expr) => {
207 $crate::performance::PerfTimer::start($operation)
208 };
209 ($operation:expr, $bytes:expr) => {
210 $crate::performance::PerfTimer::start_with_bytes($operation, $bytes)
211 };
212}
213
214impl Default for MonitoringConfig {
215 fn default() -> Self {
216 Self {
217 enable_profiling: true,
218 sample_interval_ms: 1000,
219 max_history_snapshots: 3600, report_interval_secs: 30,
221 track_memory: true,
222 track_io: true,
223 profile_operations: true,
224 }
225 }
226}
227
228impl PerformanceMonitor {
229 pub fn new() -> Self {
231 Self {
232 real_time: Arc::new(RealTimeMetrics::new()),
233 history: Arc::new(RwLock::new(PerformanceHistory::new(3600))),
234 system_tracker: Arc::new(Mutex::new(SystemResourceTracker::new())),
235 profiles: Arc::new(RwLock::new(FxHashMap::default())),
236 config: MonitoringConfig::default(),
237 }
238 }
239
240 pub fn start_monitoring(&self) {
242 if !self.config.enable_profiling {
243 return;
244 }
245
246 let real_time = Arc::clone(&self.real_time);
247 let history = Arc::clone(&self.history);
248 let system_tracker = Arc::clone(&self.system_tracker);
249 let config = self.config.clone();
250
251 tokio::spawn(async move {
253 let mut interval =
254 tokio::time::interval(Duration::from_millis(config.sample_interval_ms));
255
256 loop {
257 interval.tick().await;
258
259 let mut tracker = system_tracker.lock();
261 tracker.sample_system_metrics();
262 drop(tracker);
263
264 let snapshot = Self::create_snapshot(&real_time, &system_tracker);
266
267 let mut hist = history.write();
269 hist.add_snapshot(snapshot);
270 }
271 });
272
273 log::info!("Performance monitoring started");
274 }
275
276 pub fn record_file_processed(&self, bytes: u64, duration: Duration) {
278 self.real_time
279 .files_processed
280 .fetch_add(1, Ordering::Relaxed);
281 self.real_time
282 .bytes_processed
283 .fetch_add(bytes, Ordering::Relaxed);
284 self.real_time
285 .total_scan_time_us
286 .fetch_add(duration.as_micros() as u64, Ordering::Relaxed);
287 }
288
289 pub fn record_file_filtered(&self) {
291 self.real_time
292 .files_filtered
293 .fetch_add(1, Ordering::Relaxed);
294 }
295
296 pub fn record_file_cached(&self) {
298 self.real_time.files_cached.fetch_add(1, Ordering::Relaxed);
299 self.real_time.cache_hits.fetch_add(1, Ordering::Relaxed);
300 }
301
302 pub fn record_file_failed(&self) {
304 self.real_time.files_failed.fetch_add(1, Ordering::Relaxed);
305 }
306
307 pub fn record_io_operation(&self, bytes: u64, duration: Duration) {
309 self.real_time
310 .bytes_read
311 .fetch_add(bytes, Ordering::Relaxed);
312 self.real_time
313 .io_time_us
314 .fetch_add(duration.as_micros() as u64, Ordering::Relaxed);
315 }
316
317 pub fn record_git_operation(&self, duration: Duration) {
319 self.real_time
320 .git_time_us
321 .fetch_add(duration.as_micros() as u64, Ordering::Relaxed);
322 }
323
324 pub fn record_cache_miss(&self) {
326 self.real_time.cache_misses.fetch_add(1, Ordering::Relaxed);
327 }
328
329 pub fn record_error(&self, error_type: ErrorType) {
331 match error_type {
332 ErrorType::Io => self.real_time.io_errors.fetch_add(1, Ordering::Relaxed),
333 ErrorType::Git => self.real_time.git_errors.fetch_add(1, Ordering::Relaxed),
334 ErrorType::Parsing => self
335 .real_time
336 .parsing_errors
337 .fetch_add(1, Ordering::Relaxed),
338 ErrorType::Other => self.real_time.other_errors.fetch_add(1, Ordering::Relaxed),
339 };
340 }
341
342 pub fn update_memory_usage(&self, bytes: u64) {
344 self.real_time
345 .current_memory_bytes
346 .store(bytes, Ordering::Relaxed);
347
348 self.real_time
350 .peak_memory_bytes
351 .fetch_max(bytes, Ordering::Relaxed);
352 }
353
354 pub fn update_thread_count(&self, count: usize) {
356 self.real_time
357 .active_threads
358 .store(count, Ordering::Relaxed);
359
360 let mut peak = self.real_time.peak_threads.load(Ordering::Relaxed);
362 while peak < count {
363 match self.real_time.peak_threads.compare_exchange_weak(
364 peak,
365 count,
366 Ordering::Relaxed,
367 Ordering::Relaxed,
368 ) {
369 Ok(_) => break,
370 Err(x) => peak = x,
371 }
372 }
373 }
374
375 pub fn profile_operation(&self, name: &str, duration: Duration, bytes: u64, success: bool) {
377 if !self.config.profile_operations {
378 return;
379 }
380
381 let mut profiles = self.profiles.write();
382 let profile = profiles
383 .entry(name.to_string())
384 .or_insert_with(|| OperationProfile::new(name));
385
386 profile.record(duration, bytes, success);
387 }
388
389 pub fn get_current_snapshot(&self) -> PerformanceSnapshot {
391 Self::create_snapshot(&self.real_time, &self.system_tracker)
392 }
393
394 pub fn get_aggregated_stats(&self) -> AggregatedStats {
396 let history = self.history.read();
397 history.aggregated.clone()
398 }
399
400 pub fn get_operation_profiles(&self) -> FxHashMap<String, OperationProfile> {
402 self.profiles.read().clone()
403 }
404
405 pub fn generate_report(&self) -> PerformanceReport {
407 let snapshot = self.get_current_snapshot();
408 let aggregated = self.get_aggregated_stats();
409 let profiles = self.get_operation_profiles();
410
411 PerformanceReport {
412 current: snapshot,
413 aggregated,
414 top_operations: Self::get_top_operations(&profiles, 10),
415 bottlenecks: self.identify_bottlenecks(),
416 recommendations: self.generate_recommendations(),
417 timestamp: SystemTime::now()
418 .duration_since(UNIX_EPOCH)
419 .unwrap()
420 .as_secs(),
421 }
422 }
423
424 pub fn reset_metrics(&self) {
426 self.real_time.reset();
428
429 let mut history = self.history.write();
431 history.snapshots.clear();
432 history.aggregated = AggregatedStats::default();
433
434 let mut profiles = self.profiles.write();
436 profiles.clear();
437
438 log::info!("Performance metrics reset");
439 }
440
441 fn create_snapshot(
443 real_time: &RealTimeMetrics,
444 system_tracker: &Arc<Mutex<SystemResourceTracker>>,
445 ) -> PerformanceSnapshot {
446 let tracker = system_tracker.lock();
447
448 let files_processed = real_time.files_processed.load(Ordering::Relaxed);
449 let bytes_processed = real_time.bytes_processed.load(Ordering::Relaxed);
450 let scan_time_us = real_time.total_scan_time_us.load(Ordering::Relaxed);
451 let cache_hits = real_time.cache_hits.load(Ordering::Relaxed);
452 let cache_misses = real_time.cache_misses.load(Ordering::Relaxed);
453
454 let files_per_second = if scan_time_us > 0 {
455 files_processed as f64 / (scan_time_us as f64 / 1_000_000.0)
456 } else {
457 0.0
458 };
459
460 let bytes_per_second = if scan_time_us > 0 {
461 bytes_processed as f64 / (scan_time_us as f64 / 1_000_000.0)
462 } else {
463 0.0
464 };
465
466 let cache_hit_rate = if cache_hits + cache_misses > 0 {
467 cache_hits as f64 / (cache_hits + cache_misses) as f64
468 } else {
469 0.0
470 };
471
472 PerformanceSnapshot {
473 timestamp: SystemTime::now()
474 .duration_since(UNIX_EPOCH)
475 .unwrap()
476 .as_secs(),
477 files_per_second,
478 bytes_per_second,
479 memory_usage_mb: real_time.current_memory_bytes.load(Ordering::Relaxed) as f64
480 / (1024.0 * 1024.0),
481 cpu_utilization: tracker.get_cpu_utilization(),
482 io_wait_percentage: tracker.get_io_wait_percentage(),
483 cache_hit_rate,
484 error_rate: 0.0, active_threads: real_time.active_threads.load(Ordering::Relaxed),
486 queue_depth: 0, }
488 }
489
490 fn identify_bottlenecks(&self) -> Vec<String> {
492 let mut bottlenecks = Vec::new();
493 let snapshot = self.get_current_snapshot();
494
495 if snapshot.cpu_utilization > 80.0 {
496 bottlenecks.push("High CPU utilization".to_string());
497 }
498
499 if snapshot.io_wait_percentage > 20.0 {
500 bottlenecks.push("High I/O wait time".to_string());
501 }
502
503 if snapshot.cache_hit_rate < 0.5 {
504 bottlenecks.push("Low cache hit rate".to_string());
505 }
506
507 if snapshot.memory_usage_mb > 1000.0 {
508 bottlenecks.push("High memory usage".to_string());
509 }
510
511 bottlenecks
512 }
513
514 fn generate_recommendations(&self) -> Vec<String> {
516 let mut recommendations = Vec::new();
517 let bottlenecks = self.identify_bottlenecks();
518
519 for bottleneck in &bottlenecks {
520 match bottleneck.as_str() {
521 "High CPU utilization" => {
522 recommendations.push(
523 "Consider reducing parallelism or optimizing CPU-intensive operations"
524 .to_string(),
525 );
526 }
527 "High I/O wait time" => {
528 recommendations.push(
529 "Consider using faster storage or implementing better I/O batching"
530 .to_string(),
531 );
532 }
533 "Low cache hit rate" => {
534 recommendations.push(
535 "Increase cache size or improve cache warming strategies".to_string(),
536 );
537 }
538 "High memory usage" => {
539 recommendations.push(
540 "Consider reducing batch sizes or implementing memory streaming"
541 .to_string(),
542 );
543 }
544 _ => {}
545 }
546 }
547
548 recommendations
549 }
550
551 fn get_top_operations(
553 profiles: &FxHashMap<String, OperationProfile>,
554 limit: usize,
555 ) -> Vec<OperationProfile> {
556 let mut ops: Vec<_> = profiles.values().cloned().collect();
557 ops.sort_by(|a, b| b.total_time_us.cmp(&a.total_time_us));
558 ops.into_iter().take(limit).collect()
559 }
560}
561
562#[derive(Debug, Clone, Copy)]
564pub enum ErrorType {
565 Io,
566 Git,
567 Parsing,
568 Other,
569}
570
571#[derive(Debug, Serialize, Deserialize)]
573pub struct PerformanceReport {
574 pub current: PerformanceSnapshot,
575 pub aggregated: AggregatedStats,
576 pub top_operations: Vec<OperationProfile>,
577 pub bottlenecks: Vec<String>,
578 pub recommendations: Vec<String>,
579 pub timestamp: u64,
580}
581
582impl RealTimeMetrics {
583 fn new() -> Self {
584 Self {
585 files_processed: AtomicU64::new(0),
586 files_filtered: AtomicU64::new(0),
587 files_cached: AtomicU64::new(0),
588 files_failed: AtomicU64::new(0),
589 bytes_processed: AtomicU64::new(0),
590 bytes_read: AtomicU64::new(0),
591 total_scan_time_us: AtomicU64::new(0),
592 io_time_us: AtomicU64::new(0),
593 cpu_time_us: AtomicU64::new(0),
594 git_time_us: AtomicU64::new(0),
595 filter_time_us: AtomicU64::new(0),
596 parallel_time_us: AtomicU64::new(0),
597 peak_memory_bytes: AtomicU64::new(0),
598 current_memory_bytes: AtomicU64::new(0),
599 memory_allocations: AtomicU64::new(0),
600 active_threads: AtomicUsize::new(0),
601 peak_threads: AtomicUsize::new(0),
602 context_switches: AtomicU64::new(0),
603 cache_hits: AtomicU64::new(0),
604 cache_misses: AtomicU64::new(0),
605 cache_evictions: AtomicU64::new(0),
606 io_errors: AtomicU64::new(0),
607 git_errors: AtomicU64::new(0),
608 parsing_errors: AtomicU64::new(0),
609 other_errors: AtomicU64::new(0),
610 }
611 }
612
613 fn reset(&self) {
614 self.files_processed.store(0, Ordering::Relaxed);
616 self.files_filtered.store(0, Ordering::Relaxed);
617 self.files_cached.store(0, Ordering::Relaxed);
618 self.files_failed.store(0, Ordering::Relaxed);
619 self.bytes_processed.store(0, Ordering::Relaxed);
620 self.bytes_read.store(0, Ordering::Relaxed);
621 self.total_scan_time_us.store(0, Ordering::Relaxed);
622 self.io_time_us.store(0, Ordering::Relaxed);
623 self.cpu_time_us.store(0, Ordering::Relaxed);
624 self.git_time_us.store(0, Ordering::Relaxed);
625 self.filter_time_us.store(0, Ordering::Relaxed);
626 self.parallel_time_us.store(0, Ordering::Relaxed);
627 self.peak_memory_bytes.store(0, Ordering::Relaxed);
628 self.current_memory_bytes.store(0, Ordering::Relaxed);
629 self.memory_allocations.store(0, Ordering::Relaxed);
630 self.active_threads.store(0, Ordering::Relaxed);
631 self.peak_threads.store(0, Ordering::Relaxed);
632 self.context_switches.store(0, Ordering::Relaxed);
633 self.cache_hits.store(0, Ordering::Relaxed);
634 self.cache_misses.store(0, Ordering::Relaxed);
635 self.cache_evictions.store(0, Ordering::Relaxed);
636 self.io_errors.store(0, Ordering::Relaxed);
637 self.git_errors.store(0, Ordering::Relaxed);
638 self.parsing_errors.store(0, Ordering::Relaxed);
639 self.other_errors.store(0, Ordering::Relaxed);
640 }
641}
642
643impl PerformanceHistory {
644 fn new(max_snapshots: usize) -> Self {
645 Self {
646 snapshots: VecDeque::with_capacity(max_snapshots),
647 max_snapshots,
648 aggregated: AggregatedStats::default(),
649 }
650 }
651
652 fn add_snapshot(&mut self, snapshot: PerformanceSnapshot) {
653 if self.snapshots.len() >= self.max_snapshots {
654 self.snapshots.pop_front();
655 }
656 self.snapshots.push_back(snapshot);
657 self.update_aggregated_stats();
658 }
659
660 fn update_aggregated_stats(&mut self) {
661 if self.snapshots.is_empty() {
662 return;
663 }
664
665 let mut throughputs: Vec<f64> = self.snapshots.iter().map(|s| s.files_per_second).collect();
666 throughputs.sort_by(|a, b| a.partial_cmp(b).unwrap());
667
668 self.aggregated.avg_throughput_fps =
669 throughputs.iter().sum::<f64>() / throughputs.len() as f64;
670
671 if !throughputs.is_empty() {
673 self.aggregated.p50_latency_ms = throughputs[throughputs.len() / 2];
674 self.aggregated.p95_latency_ms = throughputs[(throughputs.len() * 95) / 100];
675 self.aggregated.p99_latency_ms = throughputs[(throughputs.len() * 99) / 100];
676 }
677
678 self.aggregated.max_memory_mb = self
679 .snapshots
680 .iter()
681 .map(|s| s.memory_usage_mb)
682 .fold(0.0, f64::max);
683
684 self.aggregated.avg_memory_mb = self
685 .snapshots
686 .iter()
687 .map(|s| s.memory_usage_mb)
688 .sum::<f64>()
689 / self.snapshots.len() as f64;
690 }
691}
692
693impl SystemResourceTracker {
694 fn new() -> Self {
695 Self {
696 cpu_samples: VecDeque::with_capacity(60), memory_samples: VecDeque::with_capacity(60),
698 io_stats: IoStats::default(),
699 last_sample_time: Instant::now(),
700 }
701 }
702
703 fn sample_system_metrics(&mut self) {
704 let now = Instant::now();
705
706 if let Some(cpu_sample) = self.sample_cpu() {
708 self.cpu_samples.push_back(cpu_sample);
709 if self.cpu_samples.len() > 60 {
710 self.cpu_samples.pop_front();
711 }
712 }
713
714 if let Some(memory_sample) = self.sample_memory() {
716 self.memory_samples.push_back(memory_sample);
717 if self.memory_samples.len() > 60 {
718 self.memory_samples.pop_front();
719 }
720 }
721
722 self.last_sample_time = now;
723 }
724
725 fn sample_cpu(&self) -> Option<CpuSample> {
726 #[cfg(unix)]
728 {
729 self.sample_cpu_unix()
730 }
731 #[cfg(not(unix))]
732 {
733 None }
735 }
736
737 #[cfg(unix)]
738 fn sample_cpu_unix(&self) -> Option<CpuSample> {
739 use std::fs;
740
741 if let Ok(contents) = fs::read_to_string("/proc/stat") {
742 let line = contents.lines().next()?;
743 let parts: Vec<&str> = line.split_whitespace().collect();
744
745 if parts.len() >= 4 && parts[0] == "cpu" {
746 let user: u64 = parts[1].parse().ok()?;
747 let system: u64 = parts[3].parse().ok()?;
748 let idle: u64 = parts[4].parse().ok()?;
749
750 return Some(CpuSample {
751 timestamp: Instant::now(),
752 user_time: Duration::from_secs(user / 100), system_time: Duration::from_secs(system / 100),
754 idle_time: Duration::from_secs(idle / 100),
755 });
756 }
757 }
758
759 None
760 }
761
762 fn sample_memory(&self) -> Option<MemorySample> {
763 #[cfg(unix)]
765 {
766 self.sample_memory_unix()
767 }
768 #[cfg(not(unix))]
769 {
770 None }
772 }
773
774 #[cfg(unix)]
775 fn sample_memory_unix(&self) -> Option<MemorySample> {
776 use std::fs;
777
778 if let Ok(contents) = fs::read_to_string("/proc/self/status") {
780 let mut rss_kb = 0u64;
781 let mut vms_kb = 0u64;
782
783 for line in contents.lines() {
784 if line.starts_with("VmRSS:") {
785 if let Some(value) = line.split_whitespace().nth(1) {
786 rss_kb = value.parse().unwrap_or(0);
787 }
788 } else if line.starts_with("VmSize:") {
789 if let Some(value) = line.split_whitespace().nth(1) {
790 vms_kb = value.parse().unwrap_or(0);
791 }
792 }
793 }
794
795 return Some(MemorySample {
796 timestamp: Instant::now(),
797 rss_bytes: rss_kb * 1024,
798 vms_bytes: vms_kb * 1024,
799 heap_bytes: 0, available_bytes: 0, });
802 }
803
804 None
805 }
806
807 fn get_cpu_utilization(&self) -> f64 {
808 if self.cpu_samples.len() < 2 {
809 return 0.0;
810 }
811
812 let recent = &self.cpu_samples[self.cpu_samples.len() - 1];
813 let previous = &self.cpu_samples[self.cpu_samples.len() - 2];
814
815 let total_time = recent.user_time + recent.system_time + recent.idle_time;
816 let prev_total_time = previous.user_time + previous.system_time + previous.idle_time;
817
818 let delta_total = total_time.saturating_sub(prev_total_time);
819 let delta_idle = recent.idle_time.saturating_sub(previous.idle_time);
820
821 if delta_total.as_secs_f64() > 0.0 {
822 100.0 * (1.0 - delta_idle.as_secs_f64() / delta_total.as_secs_f64())
823 } else {
824 0.0
825 }
826 }
827
828 fn get_io_wait_percentage(&self) -> f64 {
829 10.0 }
832}
833
834impl OperationProfile {
835 fn new(name: &str) -> Self {
836 Self {
837 operation_name: name.to_string(),
838 call_count: 0,
839 total_time_us: 0,
840 min_time_us: u64::MAX,
841 max_time_us: 0,
842 avg_time_us: 0,
843 p95_time_us: 0,
844 success_count: 0,
845 error_count: 0,
846 bytes_processed: 0,
847 last_updated: SystemTime::now()
848 .duration_since(UNIX_EPOCH)
849 .unwrap()
850 .as_secs(),
851 }
852 }
853
854 fn record(&mut self, duration: Duration, bytes: u64, success: bool) {
855 let time_us = duration.as_micros() as u64;
856
857 self.call_count += 1;
858 self.total_time_us += time_us;
859 self.min_time_us = self.min_time_us.min(time_us);
860 self.max_time_us = self.max_time_us.max(time_us);
861 self.avg_time_us = self.total_time_us / self.call_count;
862 self.bytes_processed += bytes;
863
864 if success {
865 self.success_count += 1;
866 } else {
867 self.error_count += 1;
868 }
869
870 self.last_updated = SystemTime::now()
871 .duration_since(UNIX_EPOCH)
872 .unwrap()
873 .as_secs();
874 }
875}
876
877impl PerfTimer {
878 pub fn start(operation_name: &str) -> Self {
880 Self {
881 start_time: Instant::now(),
882 operation_name: operation_name.to_string(),
883 bytes_hint: None,
884 }
885 }
886
887 pub fn start_with_bytes(operation_name: &str, bytes: u64) -> Self {
889 Self {
890 start_time: Instant::now(),
891 operation_name: operation_name.to_string(),
892 bytes_hint: Some(bytes),
893 }
894 }
895
896 pub fn finish_success(self) {
898 let duration = self.start_time.elapsed();
899 let bytes = self.bytes_hint.unwrap_or(0);
900
901 PERF_MONITOR.profile_operation(&self.operation_name, duration, bytes, true);
902 }
903
904 pub fn finish_error(self) {
906 let duration = self.start_time.elapsed();
907 let bytes = self.bytes_hint.unwrap_or(0);
908
909 PERF_MONITOR.profile_operation(&self.operation_name, duration, bytes, false);
910 }
911}
912
913impl Drop for PerfTimer {
914 fn drop(&mut self) {
915 let duration = self.start_time.elapsed();
917 let bytes = self.bytes_hint.unwrap_or(0);
918
919 PERF_MONITOR.profile_operation(&self.operation_name, duration, bytes, true);
920 }
921}
922
923impl Default for AggregatedStats {
924 fn default() -> Self {
925 Self {
926 avg_throughput_fps: 0.0,
927 p50_latency_ms: 0.0,
928 p95_latency_ms: 0.0,
929 p99_latency_ms: 0.0,
930 max_memory_mb: 0.0,
931 avg_memory_mb: 0.0,
932 total_files_processed: 0,
933 total_bytes_processed: 0,
934 total_runtime_seconds: 0.0,
935 cache_efficiency: 0.0,
936 error_percentage: 0.0,
937 }
938 }
939}
940
941#[cfg(test)]
942mod tests {
943 use super::*;
944 use std::thread;
945
946 #[test]
947 fn test_performance_monitor_creation() {
948 let monitor = PerformanceMonitor::new();
949 let snapshot = monitor.get_current_snapshot();
950
951 assert_eq!(snapshot.files_per_second, 0.0);
952 assert_eq!(snapshot.cache_hit_rate, 0.0);
953 }
954
955 #[test]
956 fn test_real_time_metrics() {
957 let monitor = PerformanceMonitor::new();
958
959 monitor.record_file_processed(1024, Duration::from_millis(10));
961 monitor.record_file_cached();
962 monitor.record_cache_miss();
963
964 let snapshot = monitor.get_current_snapshot();
965 assert!(snapshot.files_per_second > 0.0);
966 }
967
968 #[test]
969 fn test_perf_timer() {
970 let _timer = PerfTimer::start("test_operation");
971 thread::sleep(Duration::from_millis(1));
972 }
974
975 #[test]
976 fn test_perf_timer_macro() {
977 let _timer = perf_timer!("macro_test");
978 thread::sleep(Duration::from_millis(1));
979
980 let _timer2 = perf_timer!("macro_test_with_bytes", 1024);
981 thread::sleep(Duration::from_millis(1));
982 }
983
984 #[test]
985 fn test_operation_profile() {
986 let mut profile = OperationProfile::new("test_op");
987
988 profile.record(Duration::from_millis(10), 1024, true);
989 profile.record(Duration::from_millis(20), 2048, false);
990
991 assert_eq!(profile.call_count, 2);
992 assert_eq!(profile.success_count, 1);
993 assert_eq!(profile.error_count, 1);
994 assert_eq!(profile.bytes_processed, 3072);
995 }
996
997 #[test]
998 fn test_error_recording() {
999 let monitor = PerformanceMonitor::new();
1000
1001 monitor.record_error(ErrorType::Io);
1002 monitor.record_error(ErrorType::Git);
1003 monitor.record_error(ErrorType::Parsing);
1004
1005 assert_eq!(monitor.real_time.io_errors.load(Ordering::Relaxed), 1);
1006 assert_eq!(monitor.real_time.git_errors.load(Ordering::Relaxed), 1);
1007 assert_eq!(monitor.real_time.parsing_errors.load(Ordering::Relaxed), 1);
1008 }
1009
1010 #[test]
1011 fn test_memory_tracking() {
1012 let monitor = PerformanceMonitor::new();
1013
1014 monitor.update_memory_usage(1024 * 1024); monitor.update_memory_usage(2048 * 1024); monitor.update_memory_usage(512 * 1024); assert_eq!(
1019 monitor
1020 .real_time
1021 .current_memory_bytes
1022 .load(Ordering::Relaxed),
1023 512 * 1024
1024 );
1025 assert_eq!(
1026 monitor.real_time.peak_memory_bytes.load(Ordering::Relaxed),
1027 2048 * 1024
1028 );
1029 }
1030
1031 #[test]
1032 fn test_performance_report() {
1033 let monitor = PerformanceMonitor::new();
1034
1035 monitor.record_file_processed(1024, Duration::from_millis(10));
1037 monitor.record_file_cached();
1038 monitor.update_memory_usage(1024 * 1024);
1039
1040 let report = monitor.generate_report();
1041
1042 assert!(report.timestamp > 0);
1043 assert!(report.current.files_per_second >= 0.0);
1044 }
1045
1046 #[test]
1047 fn test_bottleneck_identification() {
1048 let monitor = PerformanceMonitor::new();
1049
1050 monitor.update_memory_usage(2000 * 1024 * 1024); let bottlenecks = monitor.identify_bottlenecks();
1054 assert!(bottlenecks.iter().any(|b| b.contains("memory")));
1055 }
1056
1057 #[test]
1058 fn test_metrics_reset() {
1059 let monitor = PerformanceMonitor::new();
1060
1061 monitor.record_file_processed(1024, Duration::from_millis(10));
1063 monitor.record_cache_miss();
1064
1065 assert!(monitor.real_time.files_processed.load(Ordering::Relaxed) > 0);
1067
1068 monitor.reset_metrics();
1070 assert_eq!(monitor.real_time.files_processed.load(Ordering::Relaxed), 0);
1071 assert_eq!(monitor.real_time.cache_misses.load(Ordering::Relaxed), 0);
1072 }
1073}