memscope_rs/cli/commands/html_from_json/
debug_logger.rs

1//! Debug logging and performance monitoring module
2//!
3//! This module provides comprehensive logging, debugging information,
4//! and performance monitoring for the HTML generation process.
5
6use std::collections::HashMap;
7use std::fmt;
8use std::sync::{Arc, Mutex};
9use std::time::Instant;
10
11/// Debug logging levels
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
13pub enum LogLevel {
14    /// Only critical errors
15    Error = 0,
16    /// Warnings and errors
17    Warn = 1,
18    /// General information
19    Info = 2,
20    /// Detailed debugging information
21    Debug = 3,
22    /// Very detailed trace information
23    Trace = 4,
24}
25
26impl fmt::Display for LogLevel {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        match self {
29            LogLevel::Error => write!(f, "ERROR"),
30            LogLevel::Warn => write!(f, "WARN "),
31            LogLevel::Info => write!(f, "INFO "),
32            LogLevel::Debug => write!(f, "Debug"),
33            LogLevel::Trace => write!(f, "TRACE"),
34        }
35    }
36}
37
38/// Performance timing information
39#[derive(Debug, Clone)]
40pub struct TimingInfo {
41    /// Operation name
42    pub operation: String,
43    /// Start time
44    pub start_time: Instant,
45    /// End time (if completed)
46    pub end_time: Option<Instant>,
47    /// Duration in milliseconds
48    pub duration_ms: Option<u64>,
49    /// Additional metadata
50    pub metadata: HashMap<String, String>,
51}
52
53impl TimingInfo {
54    /// Create a new timing info for an operation
55    pub fn new(operation: String) -> Self {
56        Self {
57            operation,
58            start_time: Instant::now(),
59            end_time: None,
60            duration_ms: None,
61            metadata: HashMap::new(),
62        }
63    }
64
65    /// Complete the timing measurement
66    pub fn complete(&mut self) {
67        let end_time = Instant::now();
68        let duration = end_time.duration_since(self.start_time);
69        self.end_time = Some(end_time);
70        self.duration_ms = Some(duration.as_millis() as u64);
71    }
72
73    /// Add metadata to the timing info
74    pub fn add_metadata(&mut self, key: String, value: String) {
75        self.metadata.insert(key, value);
76    }
77
78    /// Get duration in milliseconds
79    pub fn get_duration_ms(&self) -> Option<u64> {
80        self.duration_ms
81    }
82}
83
84/// Progress tracking information
85#[derive(Debug, Clone)]
86pub struct ProgressInfo {
87    /// Current step
88    pub current_step: usize,
89    /// Total steps
90    pub total_steps: usize,
91    /// Current operation description
92    pub operation: String,
93    /// Items processed in current step
94    pub items_processed: usize,
95    /// Total items in current step
96    pub total_items: usize,
97    /// Start time of current operation
98    pub start_time: Instant,
99}
100
101impl ProgressInfo {
102    /// Create new progress info
103    pub fn new(total_steps: usize, operation: String) -> Self {
104        Self {
105            current_step: 0,
106            total_steps,
107            operation,
108            items_processed: 0,
109            total_items: 0,
110            start_time: Instant::now(),
111        }
112    }
113
114    /// Update progress to next step
115    pub fn next_step(&mut self, operation: String, total_items: usize) {
116        self.current_step += 1;
117        self.operation = operation;
118        self.items_processed = 0;
119        self.total_items = total_items;
120        self.start_time = Instant::now();
121    }
122
123    /// Update items processed in current step
124    pub fn update_items(&mut self, processed: usize) {
125        self.items_processed = processed;
126    }
127
128    /// Get overall progress percentage
129    pub fn get_overall_progress(&self) -> f64 {
130        if self.total_steps == 0 {
131            return 100.0;
132        }
133
134        let step_progress = if self.total_items > 0 {
135            self.items_processed as f64 / self.total_items as f64
136        } else {
137            1.0
138        };
139
140        ((self.current_step as f64 + step_progress) / self.total_steps as f64) * 100.0
141    }
142
143    /// Get current step progress percentage
144    pub fn get_step_progress(&self) -> f64 {
145        if self.total_items == 0 {
146            return 100.0;
147        }
148        (self.items_processed as f64 / self.total_items as f64) * 100.0
149    }
150}
151
152/// Debug logger configuration
153#[derive(Debug, Clone)]
154pub struct DebugConfig {
155    /// Logging level
156    pub log_level: LogLevel,
157    /// Enable performance timing
158    pub enable_timing: bool,
159    /// Enable progress reporting
160    pub enable_progress: bool,
161    /// Enable memory usage tracking
162    pub enable_memory_tracking: bool,
163    /// Enable detailed file operations logging
164    pub enable_file_ops: bool,
165    /// Enable JSON processing details
166    pub enable_json_details: bool,
167    /// Progress reporting interval in milliseconds
168    pub progress_interval_ms: u64,
169    /// Output timestamps in logs
170    pub include_timestamps: bool,
171}
172
173impl Default for DebugConfig {
174    fn default() -> Self {
175        Self {
176            log_level: LogLevel::Info,
177            enable_timing: true,
178            enable_progress: true,
179            enable_memory_tracking: false,
180            enable_file_ops: false,
181            enable_json_details: false,
182            progress_interval_ms: 1000,
183            include_timestamps: true,
184        }
185    }
186}
187
188/// Performance statistics
189#[derive(Debug, Default, Clone)]
190pub struct PerformanceStats {
191    /// Total processing time
192    pub total_time_ms: u64,
193    /// File discovery time
194    pub discovery_time_ms: u64,
195    /// File loading time
196    pub loading_time_ms: u64,
197    /// Data normalization time
198    pub normalization_time_ms: u64,
199    /// Data integration time
200    pub integration_time_ms: u64,
201    /// Template generation time
202    pub template_time_ms: u64,
203    /// Number of files processed
204    pub files_processed: usize,
205    /// Total data size processed
206    pub data_size_bytes: usize,
207    /// Memory peak usage
208    pub peak_memory_bytes: usize,
209    /// Number of errors encountered
210    pub error_count: usize,
211    /// Number of warnings
212    pub warning_count: usize,
213}
214
215impl PerformanceStats {
216    /// Calculate throughput in MB/s
217    pub fn get_throughput_mb_per_sec(&self) -> f64 {
218        if self.total_time_ms == 0 {
219            return 0.0;
220        }
221        (self.data_size_bytes as f64 / 1024.0 / 1024.0) / (self.total_time_ms as f64 / 1000.0)
222    }
223
224    /// Get processing efficiency score (0-100)
225    pub fn get_efficiency_score(&self) -> f64 {
226        let base_score = 100.0;
227        let error_penalty = (self.error_count as f64) * 10.0;
228        let warning_penalty = (self.warning_count as f64) * 2.0;
229
230        (base_score - error_penalty - warning_penalty).max(0.0)
231    }
232}
233
234/// Debug logger for HTML generation
235pub struct DebugLogger {
236    /// Configuration
237    config: DebugConfig,
238    /// Active timing operations
239    active_timings: Arc<Mutex<HashMap<String, TimingInfo>>>,
240    /// Completed timing operations
241    completed_timings: Arc<Mutex<Vec<TimingInfo>>>,
242    /// Progress information
243    progress: Arc<Mutex<Option<ProgressInfo>>>,
244    /// Performance statistics
245    stats: Arc<Mutex<PerformanceStats>>,
246    /// Start time of the logger
247    start_time: Instant,
248    /// Last progress report time
249    last_progress_time: Arc<Mutex<Instant>>,
250}
251
252impl DebugLogger {
253    /// Create a new debug logger with default configuration
254    pub fn new() -> Self {
255        Self::with_config(DebugConfig::default())
256    }
257
258    /// Create a new debug logger with custom configuration
259    pub fn with_config(config: DebugConfig) -> Self {
260        Self {
261            config,
262            active_timings: Arc::new(Mutex::new(HashMap::new())),
263            completed_timings: Arc::new(Mutex::new(Vec::new())),
264            progress: Arc::new(Mutex::new(None)),
265            stats: Arc::new(Mutex::new(PerformanceStats::default())),
266            start_time: Instant::now(),
267            last_progress_time: Arc::new(Mutex::new(Instant::now())),
268        }
269    }
270
271    /// Log a message at the specified level
272    pub fn log(&self, level: LogLevel, message: &str) {
273        if level <= self.config.log_level {
274            let timestamp = if self.config.include_timestamps {
275                let elapsed = self.start_time.elapsed();
276                format!("[{:>8.3}s] ", elapsed.as_secs_f64())
277            } else {
278                String::new()
279            };
280
281            let level_icon = match level {
282                LogLevel::Error => "❌",
283                LogLevel::Warn => "⚠️ ",
284                LogLevel::Info => "ℹ️ ",
285                LogLevel::Debug => "🔍",
286                LogLevel::Trace => "🔎",
287            };
288
289            tracing::info!("{}{} {} {}", timestamp, level_icon, level, message);
290        }
291    }
292
293    /// Log an error message
294    pub fn error(&self, message: &str) {
295        self.log(LogLevel::Error, message);
296        if let Ok(mut stats) = self.stats.lock() {
297            stats.error_count += 1;
298        }
299    }
300
301    /// Log a warning message
302    pub fn warn(&self, message: &str) {
303        self.log(LogLevel::Warn, message);
304        if let Ok(mut stats) = self.stats.lock() {
305            stats.warning_count += 1;
306        }
307    }
308
309    /// Log an info message
310    pub fn info(&self, message: &str) {
311        self.log(LogLevel::Info, message);
312    }
313
314    /// Log a debug message
315    pub fn debug(&self, message: &str) {
316        self.log(LogLevel::Debug, message);
317    }
318
319    /// Log a trace message
320    pub fn trace(&self, message: &str) {
321        self.log(LogLevel::Trace, message);
322    }
323
324    /// Start timing an operation
325    pub fn start_timing(&self, operation: &str) -> String {
326        if !self.config.enable_timing {
327            return operation.to_string();
328        }
329
330        let timing_id = format!("{}_{}", operation, self.start_time.elapsed().as_millis());
331        let timing_info = TimingInfo::new(operation.to_string());
332
333        if let Ok(mut timings) = self.active_timings.lock() {
334            timings.insert(timing_id.clone(), timing_info);
335        }
336
337        self.debug(&format!("Started timing: {operation}"));
338        timing_id
339    }
340
341    /// End timing an operation
342    pub fn end_timing(&self, timing_id: &str) -> Option<u64> {
343        if !self.config.enable_timing {
344            return None;
345        }
346
347        let duration = if let Ok(mut active) = self.active_timings.lock() {
348            if let Some(mut timing) = active.remove(timing_id) {
349                timing.complete();
350                let duration = timing.get_duration_ms();
351
352                self.debug(&format!(
353                    "Completed timing: {} in {}ms",
354                    timing.operation,
355                    duration.unwrap_or(0)
356                ));
357
358                if let Ok(mut completed) = self.completed_timings.lock() {
359                    completed.push(timing);
360                }
361
362                duration
363            } else {
364                None
365            }
366        } else {
367            None
368        };
369
370        duration
371    }
372
373    /// Start progress tracking
374    pub fn start_progress(&self, total_steps: usize, initial_operation: &str) {
375        if !self.config.enable_progress {
376            return;
377        }
378
379        let progress_info = ProgressInfo::new(total_steps, initial_operation.to_string());
380
381        if let Ok(mut progress) = self.progress.lock() {
382            *progress = Some(progress_info);
383        }
384
385        self.info(&format!("Started progress tracking: {total_steps} steps"));
386    }
387
388    /// Update progress to next step
389    pub fn next_progress_step(&self, operation: &str, total_items: usize) {
390        if !self.config.enable_progress {
391            return;
392        }
393
394        if let Ok(mut progress) = self.progress.lock() {
395            if let Some(ref mut prog) = *progress {
396                prog.next_step(operation.to_string(), total_items);
397                self.info(&format!(
398                    "Progress: Step {}/{} - {operation}",
399                    prog.current_step, prog.total_steps
400                ));
401            }
402        }
403    }
404
405    /// Update items processed in current step
406    pub fn update_progress_items(&self, processed: usize) {
407        if !self.config.enable_progress {
408            return;
409        }
410
411        let should_report = if let Ok(mut last_time) = self.last_progress_time.lock() {
412            let now = Instant::now();
413            let elapsed = now.duration_since(*last_time);
414            if elapsed.as_millis() >= self.config.progress_interval_ms as u128 {
415                *last_time = now;
416                true
417            } else {
418                false
419            }
420        } else {
421            false
422        };
423
424        if let Ok(mut progress) = self.progress.lock() {
425            if let Some(ref mut prog) = *progress {
426                prog.update_items(processed);
427
428                if should_report {
429                    let overall = prog.get_overall_progress();
430                    let step = prog.get_step_progress();
431                    self.debug(&format!(
432                        "Progress: {overall:.1}% overall, {step:.1}% current step ({}/{})",
433                        prog.items_processed, prog.total_items
434                    ));
435                }
436            }
437        }
438    }
439
440    /// Log file operation
441    pub fn log_file_operation(&self, operation: &str, file_path: &str, size_bytes: Option<usize>) {
442        if !self.config.enable_file_ops {
443            return;
444        }
445
446        let size_info = if let Some(size) = size_bytes {
447            format!(" ({:.1} KB)", size as f64 / 1024.0)
448        } else {
449            String::new()
450        };
451
452        self.debug(&format!("File {operation}: {file_path}{size_info}"));
453    }
454
455    /// Log JSON processing details
456    pub fn log_json_processing(
457        &self,
458        file_type: &str,
459        objects_count: usize,
460        processing_time_ms: u64,
461    ) {
462        if !self.config.enable_json_details {
463            return;
464        }
465
466        self.debug(&format!(
467            "JSON processing: {file_type} - {objects_count} objects in {processing_time_ms}ms",
468        ));
469    }
470
471    /// Update performance statistics
472    pub fn update_stats<F>(&self, updater: F)
473    where
474        F: FnOnce(&mut PerformanceStats),
475    {
476        if let Ok(mut stats) = self.stats.lock() {
477            updater(&mut stats);
478        }
479    }
480
481    /// Get current performance statistics
482    pub fn get_stats(&self) -> PerformanceStats {
483        if let Ok(stats) = self.stats.lock() {
484            stats.clone()
485        } else {
486            PerformanceStats::default()
487        }
488    }
489
490    /// Print comprehensive performance report
491    pub fn print_performance_report(&self) {
492        if !self.config.enable_timing {
493            return;
494        }
495
496        let stats = self.get_stats();
497        let total_elapsed = self.start_time.elapsed().as_millis() as u64;
498
499        tracing::info!("\n📊 Performance Report:");
500        tracing::info!("   Total time: {}ms", total_elapsed);
501        tracing::info!(
502            "   Discovery: {}ms ({:.1}%)",
503            stats.discovery_time_ms,
504            (stats.discovery_time_ms as f64 / total_elapsed as f64) * 100.0
505        );
506        tracing::info!(
507            "   Loading: {}ms ({:.1}%)",
508            stats.loading_time_ms,
509            (stats.loading_time_ms as f64 / total_elapsed as f64) * 100.0
510        );
511        tracing::info!(
512            "   Normalization: {}ms ({:.1}%)",
513            stats.normalization_time_ms,
514            (stats.normalization_time_ms as f64 / total_elapsed as f64) * 100.0
515        );
516        tracing::info!(
517            "   Integration: {}ms ({:.1}%)",
518            stats.integration_time_ms,
519            (stats.integration_time_ms as f64 / total_elapsed as f64) * 100.0
520        );
521        tracing::info!(
522            "   Template: {}ms ({:.1}%)",
523            stats.template_time_ms,
524            (stats.template_time_ms as f64 / total_elapsed as f64) * 100.0
525        );
526
527        tracing::info!("\n📈 Processing Statistics:");
528        tracing::info!("   Files processed: {}", stats.files_processed);
529        tracing::info!(
530            "   Data size: {:.1} MB",
531            stats.data_size_bytes as f64 / 1024.0 / 1024.0
532        );
533        tracing::info!(
534            "   Throughput: {:.1} MB/s",
535            stats.get_throughput_mb_per_sec()
536        );
537        tracing::info!(
538            "   Efficiency score: {:.1}/100",
539            stats.get_efficiency_score()
540        );
541
542        if stats.error_count > 0 || stats.warning_count > 0 {
543            tracing::info!("\n⚠️  Issues:");
544            if stats.error_count > 0 {
545                tracing::info!("   Errors: {}", stats.error_count);
546            }
547            if stats.warning_count > 0 {
548                tracing::info!("   Warnings: {}", stats.warning_count);
549            }
550        }
551
552        // Print detailed timing breakdown
553        if let Ok(completed) = self.completed_timings.lock() {
554            if !completed.is_empty() {
555                tracing::info!("\n🔍 Detailed Timing Breakdown:");
556                for timing in completed.iter() {
557                    if let Some(duration) = timing.duration_ms {
558                        tracing::info!("   {}: {}ms", timing.operation, duration);
559                        for (key, value) in &timing.metadata {
560                            tracing::info!("     {}: {}", key, value);
561                        }
562                    }
563                }
564            }
565        }
566    }
567
568    /// Print memory usage information
569    pub fn print_memory_info(&self) {
570        if !self.config.enable_memory_tracking {
571            return;
572        }
573
574        let stats = self.get_stats();
575        if stats.peak_memory_bytes > 0 {
576            tracing::info!("\n💾 Memory Usage:");
577            tracing::info!(
578                "   Peak memory: {:.1} MB",
579                stats.peak_memory_bytes as f64 / 1024.0 / 1024.0
580            );
581
582            if stats.data_size_bytes > 0 {
583                let memory_efficiency =
584                    (stats.data_size_bytes as f64 / stats.peak_memory_bytes as f64) * 100.0;
585                tracing::info!("   Memory efficiency: {:.1}%", memory_efficiency);
586            }
587        }
588    }
589}
590
591impl Default for DebugLogger {
592    fn default() -> Self {
593        Self::new()
594    }
595}
596
597#[cfg(test)]
598mod tests {
599    use super::*;
600    use std::thread;
601    use std::time::Duration;
602
603    #[test]
604    fn test_debug_logger_creation() {
605        let logger = DebugLogger::new();
606        assert_eq!(logger.config.log_level, LogLevel::Info);
607        assert!(logger.config.enable_timing);
608    }
609
610    #[test]
611    fn test_timing_operations() {
612        let logger = DebugLogger::new();
613        let timing_id = logger.start_timing("test_operation");
614
615        // Simulate some work
616        thread::sleep(Duration::from_millis(10));
617
618        let duration = logger.end_timing(&timing_id);
619        assert!(duration.is_some());
620        assert!(duration.expect("Failed to get duration") >= 10);
621    }
622
623    #[test]
624    fn test_progress_tracking() {
625        let logger = DebugLogger::new();
626        logger.start_progress(3, "Initial operation");
627
628        logger.next_progress_step("Step 1", 10);
629        logger.update_progress_items(5);
630
631        {
632            let progress = logger.progress.lock().expect("Failed to acquire lock");
633            if let Some(ref prog) = *progress {
634                assert_eq!(prog.current_step, 1);
635                assert_eq!(prog.items_processed, 5);
636                assert_eq!(prog.total_items, 10);
637                assert_eq!(prog.get_step_progress(), 50.0);
638            }
639        }
640    }
641
642    #[test]
643    fn test_performance_stats() {
644        let logger = DebugLogger::new();
645
646        logger.update_stats(|stats| {
647            stats.files_processed = 5;
648            stats.data_size_bytes = 1024 * 1024; // 1MB
649            stats.total_time_ms = 1000; // 1 second
650        });
651
652        let stats = logger.get_stats();
653        assert_eq!(stats.files_processed, 5);
654        assert_eq!(stats.get_throughput_mb_per_sec(), 1.0);
655    }
656
657    #[test]
658    fn test_log_levels() {
659        let config = DebugConfig {
660            log_level: LogLevel::Debug,
661            ..Default::default()
662        };
663        let logger = DebugLogger::with_config(config);
664
665        // These should not panic
666        logger.error("Test error");
667        logger.warn("Test warning");
668        logger.info("Test info");
669        logger.debug("Test debug");
670        logger.trace("Test trace"); // This should be filtered out
671    }
672}