Skip to main content

trustformers_wasm/
debug.rs

1//! Debug mode with comprehensive logging and performance monitoring
2
3use js_sys::Date;
4use serde::{Deserialize, Serialize};
5use std::string::{String, ToString};
6use std::sync::Mutex;
7use std::vec::Vec;
8use wasm_bindgen::prelude::*;
9
10/// Log levels for filtering output
11#[wasm_bindgen]
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13pub enum LogLevel {
14    Error = 0,
15    Warn = 1,
16    Info = 2,
17    Debug = 3,
18    Trace = 4,
19}
20
21/// Performance metrics for operations
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct PerformanceMetrics {
24    pub operation: String,
25    pub start_time: f64,
26    pub end_time: f64,
27    pub duration_ms: f64,
28    pub memory_before: usize,
29    pub memory_after: usize,
30    pub memory_delta: i64,
31    pub gpu_memory_used: usize,
32}
33
34/// Debug configuration
35#[wasm_bindgen]
36#[derive(Debug, Clone)]
37pub struct DebugConfig {
38    enabled: bool,
39    log_level: LogLevel,
40    console_output: bool,
41    performance_tracking: bool,
42    memory_tracking: bool,
43    gpu_profiling: bool,
44    max_log_entries: usize,
45}
46
47impl Default for DebugConfig {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53#[wasm_bindgen]
54impl DebugConfig {
55    /// Create a new debug configuration
56    #[wasm_bindgen(constructor)]
57    pub fn new() -> Self {
58        Self {
59            enabled: false,
60            log_level: LogLevel::Info,
61            console_output: true,
62            performance_tracking: false,
63            memory_tracking: false,
64            gpu_profiling: false,
65            max_log_entries: 1000,
66        }
67    }
68
69    /// Enable debug mode
70    pub fn enable(mut self) -> Self {
71        self.enabled = true;
72        self
73    }
74
75    /// Disable debug mode
76    pub fn disable(mut self) -> Self {
77        self.enabled = false;
78        self
79    }
80
81    /// Set log level
82    pub fn set_log_level(mut self, level: LogLevel) -> Self {
83        self.log_level = level;
84        self
85    }
86
87    /// Enable/disable console output
88    pub fn set_console_output(mut self, enabled: bool) -> Self {
89        self.console_output = enabled;
90        self
91    }
92
93    /// Enable/disable performance tracking
94    pub fn set_performance_tracking(mut self, enabled: bool) -> Self {
95        self.performance_tracking = enabled;
96        self
97    }
98
99    /// Enable/disable memory tracking
100    pub fn set_memory_tracking(mut self, enabled: bool) -> Self {
101        self.memory_tracking = enabled;
102        self
103    }
104
105    /// Enable/disable GPU profiling
106    pub fn set_gpu_profiling(mut self, enabled: bool) -> Self {
107        self.gpu_profiling = enabled;
108        self
109    }
110
111    /// Set maximum number of log entries to retain
112    pub fn set_max_log_entries(mut self, max: usize) -> Self {
113        self.max_log_entries = max;
114        self
115    }
116
117    /// Create a development configuration with detailed logging
118    pub fn development() -> Self {
119        Self {
120            enabled: true,
121            log_level: LogLevel::Debug,
122            console_output: true,
123            performance_tracking: true,
124            memory_tracking: true,
125            gpu_profiling: true,
126            max_log_entries: 2000,
127        }
128    }
129
130    /// Create a production configuration with minimal logging
131    pub fn production() -> Self {
132        Self {
133            enabled: true,
134            log_level: LogLevel::Warn,
135            console_output: false,
136            performance_tracking: false,
137            memory_tracking: false,
138            gpu_profiling: false,
139            max_log_entries: 100,
140        }
141    }
142}
143
144/// Log entry with metadata
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct LogEntry {
147    pub timestamp: f64,
148    pub level: LogLevel,
149    pub message: String,
150    pub category: String,
151    pub source_file: Option<String>,
152    pub source_line: Option<u32>,
153    pub metadata: Option<serde_json::Value>,
154}
155
156/// Debug logger with performance monitoring
157#[wasm_bindgen]
158#[derive(Clone)]
159pub struct DebugLogger {
160    config: DebugConfig,
161    log_entries: Vec<LogEntry>,
162    performance_metrics: Vec<PerformanceMetrics>,
163    active_timers: Vec<(String, f64)>,
164}
165
166#[wasm_bindgen]
167impl DebugLogger {
168    /// Create a new debug logger
169    #[wasm_bindgen(constructor)]
170    pub fn new(config: DebugConfig) -> Self {
171        Self {
172            config,
173            log_entries: Vec::new(),
174            performance_metrics: Vec::new(),
175            active_timers: Vec::new(),
176        }
177    }
178
179    /// Log an error message
180    pub fn error(&mut self, message: &str, category: &str) {
181        self.log(LogLevel::Error, message, category, None, None, None);
182    }
183
184    /// Log a warning message
185    pub fn warn(&mut self, message: &str, category: &str) {
186        self.log(LogLevel::Warn, message, category, None, None, None);
187    }
188
189    /// Log an info message
190    pub fn info(&mut self, message: &str, category: &str) {
191        self.log(LogLevel::Info, message, category, None, None, None);
192    }
193
194    /// Log a debug message
195    pub fn debug(&mut self, message: &str, category: &str) {
196        self.log(LogLevel::Debug, message, category, None, None, None);
197    }
198
199    /// Log a trace message
200    pub fn trace(&mut self, message: &str, category: &str) {
201        self.log(LogLevel::Trace, message, category, None, None, None);
202    }
203
204    /// Start a performance timer for an operation
205    pub fn start_timer(&mut self, operation: &str) {
206        if !self.config.enabled || !self.config.performance_tracking {
207            return;
208        }
209
210        let start_time = Date::now();
211        self.active_timers.push((operation.to_string(), start_time));
212
213        if self.config.console_output {
214            web_sys::console::time_with_label(&format!("โฑ๏ธ {}", operation));
215        }
216    }
217
218    /// End a performance timer and record metrics
219    pub fn end_timer(&mut self, operation: &str) -> Option<f64> {
220        if !self.config.enabled || !self.config.performance_tracking {
221            return None;
222        }
223
224        let end_time = Date::now();
225
226        // Find and remove the timer
227        if let Some(pos) = self.active_timers.iter().position(|(op, _)| op == operation) {
228            let (_, start_time) = self.active_timers.remove(pos);
229            let duration_ms = end_time - start_time;
230
231            let memory_before = 0; // Would get from memory tracking
232            let memory_after = crate::get_wasm_memory_usage();
233            let memory_delta = memory_after as i64 - memory_before as i64;
234
235            let metrics = PerformanceMetrics {
236                operation: operation.to_string(),
237                start_time,
238                end_time,
239                duration_ms,
240                memory_before,
241                memory_after,
242                memory_delta,
243                gpu_memory_used: 0, // Would get from GPU profiling
244            };
245
246            self.performance_metrics.push(metrics);
247
248            if self.config.console_output {
249                web_sys::console::time_end_with_label(&format!("โฑ๏ธ {}", operation));
250                web_sys::console::log_1(
251                    &format!("๐Ÿš€ {} completed in {:.2}ms", operation, duration_ms).into(),
252                );
253            }
254
255            self.trim_metrics();
256            Some(duration_ms)
257        } else {
258            if self.config.console_output {
259                web_sys::console::warn_1(
260                    &format!("โš ๏ธ Timer '{}' was not started", operation).into(),
261                );
262            }
263            None
264        }
265    }
266
267    /// Log model loading operation
268    pub fn log_model_loading(&mut self, model_name: &str, size_bytes: usize, source: &str) {
269        let message = format!(
270            "Loading model '{}' ({:.2} MB) from {}",
271            model_name,
272            size_bytes as f64 / 1_048_576.0,
273            source
274        );
275        self.info(&message, "model_loading");
276    }
277
278    /// Log inference operation
279    pub fn log_inference(&mut self, model_name: &str, input_shape: &[usize], device: &str) {
280        let message = format!(
281            "Running inference on model '{}' with input shape {:?} on {}",
282            model_name, input_shape, device
283        );
284        self.debug(&message, "inference");
285    }
286
287    /// Log memory usage
288    pub fn log_memory_usage(&mut self, context: &str) {
289        if !self.config.enabled || !self.config.memory_tracking {
290            return;
291        }
292
293        let wasm_memory = crate::get_wasm_memory_usage();
294        let message = format!(
295            "{}: WASM memory usage: {:.2} MB",
296            context,
297            wasm_memory as f64 / 1_048_576.0
298        );
299        self.debug(&message, "memory");
300    }
301
302    /// Log GPU operation
303    pub fn log_gpu_operation(&mut self, operation: &str, buffer_size: usize) {
304        if !self.config.enabled || !self.config.gpu_profiling {
305            return;
306        }
307
308        let message = format!(
309            "GPU operation '{}' with buffer size: {:.2} MB",
310            operation,
311            buffer_size as f64 / 1_048_576.0
312        );
313        self.debug(&message, "gpu");
314    }
315
316    /// Get performance summary
317    pub fn get_performance_summary(&self) -> String {
318        if self.performance_metrics.is_empty() {
319            return "No performance metrics available".to_string();
320        }
321
322        let total_operations = self.performance_metrics.len();
323        let total_time: f64 = self.performance_metrics.iter().map(|m| m.duration_ms).sum();
324        let avg_time = total_time / total_operations as f64;
325
326        let slowest = self
327            .performance_metrics
328            .iter()
329            .max_by(|a, b| {
330                a.duration_ms.partial_cmp(&b.duration_ms).unwrap_or(std::cmp::Ordering::Equal)
331            })
332            .expect("performance_metrics is not empty after length check");
333
334        format!(
335            "Performance Summary:\n\
336             - Total operations: {}\n\
337             - Total time: {:.2}ms\n\
338             - Average time: {:.2}ms\n\
339             - Slowest operation: {} ({:.2}ms)",
340            total_operations, total_time, avg_time, slowest.operation, slowest.duration_ms
341        )
342    }
343
344    /// Export logs as formatted string
345    pub fn export_logs(&self) -> String {
346        let mut output = String::new();
347        output.push_str("=== TrustformersWasm Debug Export ===\n\n");
348
349        output.push_str(&format!(
350            "Config: enabled={}, level={:?}, perf_tracking={}, mem_tracking={}, gpu_profiling={}\n\n",
351            self.config.enabled,
352            self.config.log_level,
353            self.config.performance_tracking,
354            self.config.memory_tracking,
355            self.config.gpu_profiling
356        ));
357
358        output.push_str("=== Log Entries ===\n");
359        for entry in &self.log_entries {
360            output.push_str(&format!(
361                "[{:.3}] {:?} [{}] {}\n",
362                entry.timestamp, entry.level, entry.category, entry.message
363            ));
364        }
365
366        output.push_str("\n=== Performance Metrics ===\n");
367        for metric in &self.performance_metrics {
368            output.push_str(&format!(
369                "{}: {:.2}ms (mem: {}โ†’{}, ฮ”{})\n",
370                metric.operation,
371                metric.duration_ms,
372                metric.memory_before,
373                metric.memory_after,
374                metric.memory_delta
375            ));
376        }
377
378        output.push_str("\n=== Summary ===\n");
379        output.push_str(&self.get_performance_summary());
380
381        output
382    }
383
384    /// Clear all logs and metrics
385    pub fn clear(&mut self) {
386        self.log_entries.clear();
387        self.performance_metrics.clear();
388        self.active_timers.clear();
389
390        if self.config.console_output {
391            web_sys::console::log_1(&"๐Ÿงน Debug logs cleared".into());
392        }
393    }
394
395    /// Get number of log entries
396    #[wasm_bindgen(getter)]
397    pub fn log_count(&self) -> usize {
398        self.log_entries.len()
399    }
400
401    /// Get number of performance metrics
402    #[wasm_bindgen(getter)]
403    pub fn metrics_count(&self) -> usize {
404        self.performance_metrics.len()
405    }
406
407    // Private helper methods
408
409    fn log(
410        &mut self,
411        level: LogLevel,
412        message: &str,
413        category: &str,
414        source_file: Option<String>,
415        source_line: Option<u32>,
416        metadata: Option<serde_json::Value>,
417    ) {
418        if !self.config.enabled || level as u8 > self.config.log_level as u8 {
419            return;
420        }
421
422        let entry = LogEntry {
423            timestamp: Date::now(),
424            level,
425            message: message.to_string(),
426            category: category.to_string(),
427            source_file,
428            source_line,
429            metadata,
430        };
431
432        self.log_entries.push(entry);
433
434        if self.config.console_output {
435            let level_prefix = match level {
436                LogLevel::Error => "โŒ",
437                LogLevel::Warn => "โš ๏ธ",
438                LogLevel::Info => "โ„น๏ธ",
439                LogLevel::Debug => "๐Ÿ›",
440                LogLevel::Trace => "๐Ÿ”",
441            };
442
443            let formatted_message = format!("{} [{}] {}", level_prefix, category, message);
444
445            match level {
446                LogLevel::Error => web_sys::console::error_1(&formatted_message.into()),
447                LogLevel::Warn => web_sys::console::warn_1(&formatted_message.into()),
448                LogLevel::Info => web_sys::console::info_1(&formatted_message.into()),
449                LogLevel::Debug | LogLevel::Trace => {
450                    web_sys::console::log_1(&formatted_message.into())
451                },
452            }
453        }
454
455        self.trim_logs();
456    }
457
458    fn trim_logs(&mut self) {
459        if self.log_entries.len() > self.config.max_log_entries {
460            let excess = self.log_entries.len() - self.config.max_log_entries;
461            self.log_entries.drain(0..excess);
462        }
463    }
464
465    fn trim_metrics(&mut self) {
466        if self.performance_metrics.len() > self.config.max_log_entries {
467            let excess = self.performance_metrics.len() - self.config.max_log_entries;
468            self.performance_metrics.drain(0..excess);
469        }
470    }
471}
472
473/// Global debug logger instance
474static GLOBAL_LOGGER: Mutex<Option<DebugLogger>> = Mutex::new(None);
475
476/// Initialize the global debug logger
477#[wasm_bindgen]
478pub fn init_debug_logger(config: DebugConfig) {
479    if let Ok(mut logger) = GLOBAL_LOGGER.lock() {
480        *logger = Some(DebugLogger::new(config));
481    }
482}
483
484/// Get the global debug logger
485#[wasm_bindgen]
486pub fn get_debug_logger() -> Option<DebugLogger> {
487    GLOBAL_LOGGER.lock().ok().and_then(|logger| logger.clone())
488}
489
490/// Macro for easy logging with automatic category detection
491#[macro_export]
492macro_rules! debug_log {
493    (error, $msg:expr) => {
494        if let Some(mut logger) = get_debug_logger() {
495            logger.error($msg, module_path!());
496        }
497    };
498    (warn, $msg:expr) => {
499        if let Some(mut logger) = get_debug_logger() {
500            logger.warn($msg, module_path!());
501        }
502    };
503    (info, $msg:expr) => {
504        if let Some(mut logger) = get_debug_logger() {
505            logger.info($msg, module_path!());
506        }
507    };
508    (debug, $msg:expr) => {
509        if let Some(mut logger) = get_debug_logger() {
510            logger.debug($msg, module_path!());
511        }
512    };
513    (trace, $msg:expr) => {
514        if let Some(mut logger) = get_debug_logger() {
515            logger.trace($msg, module_path!());
516        }
517    };
518}
519
520#[cfg(test)]
521mod tests {
522    use super::*;
523
524    #[test]
525    fn test_debug_config() {
526        let mut config = DebugConfig::new();
527        assert!(!config.enabled);
528
529        config = config.enable().set_log_level(LogLevel::Debug);
530        assert!(config.enabled);
531        assert_eq!(config.log_level as u8, LogLevel::Debug as u8);
532    }
533
534    #[test]
535    fn test_logger_creation() {
536        let config = DebugConfig::development();
537        let logger = DebugLogger::new(config);
538        assert_eq!(logger.log_count(), 0);
539        assert_eq!(logger.metrics_count(), 0);
540    }
541}