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 Some(slowest) = self.performance_metrics.iter().max_by(|a, b| {
327            a.duration_ms.partial_cmp(&b.duration_ms).unwrap_or(std::cmp::Ordering::Equal)
328        }) else {
329            return "No performance metrics available".to_string();
330        };
331
332        format!(
333            "Performance Summary:\n\
334             - Total operations: {}\n\
335             - Total time: {:.2}ms\n\
336             - Average time: {:.2}ms\n\
337             - Slowest operation: {} ({:.2}ms)",
338            total_operations, total_time, avg_time, slowest.operation, slowest.duration_ms
339        )
340    }
341
342    /// Export logs as formatted string
343    pub fn export_logs(&self) -> String {
344        let mut output = String::new();
345        output.push_str("=== TrustformersWasm Debug Export ===\n\n");
346
347        output.push_str(&format!(
348            "Config: enabled={}, level={:?}, perf_tracking={}, mem_tracking={}, gpu_profiling={}\n\n",
349            self.config.enabled,
350            self.config.log_level,
351            self.config.performance_tracking,
352            self.config.memory_tracking,
353            self.config.gpu_profiling
354        ));
355
356        output.push_str("=== Log Entries ===\n");
357        for entry in &self.log_entries {
358            output.push_str(&format!(
359                "[{:.3}] {:?} [{}] {}\n",
360                entry.timestamp, entry.level, entry.category, entry.message
361            ));
362        }
363
364        output.push_str("\n=== Performance Metrics ===\n");
365        for metric in &self.performance_metrics {
366            output.push_str(&format!(
367                "{}: {:.2}ms (mem: {}โ†’{}, ฮ”{})\n",
368                metric.operation,
369                metric.duration_ms,
370                metric.memory_before,
371                metric.memory_after,
372                metric.memory_delta
373            ));
374        }
375
376        output.push_str("\n=== Summary ===\n");
377        output.push_str(&self.get_performance_summary());
378
379        output
380    }
381
382    /// Clear all logs and metrics
383    pub fn clear(&mut self) {
384        self.log_entries.clear();
385        self.performance_metrics.clear();
386        self.active_timers.clear();
387
388        if self.config.console_output {
389            web_sys::console::log_1(&"๐Ÿงน Debug logs cleared".into());
390        }
391    }
392
393    /// Get number of log entries
394    #[wasm_bindgen(getter)]
395    pub fn log_count(&self) -> usize {
396        self.log_entries.len()
397    }
398
399    /// Get number of performance metrics
400    #[wasm_bindgen(getter)]
401    pub fn metrics_count(&self) -> usize {
402        self.performance_metrics.len()
403    }
404
405    // Private helper methods
406
407    fn log(
408        &mut self,
409        level: LogLevel,
410        message: &str,
411        category: &str,
412        source_file: Option<String>,
413        source_line: Option<u32>,
414        metadata: Option<serde_json::Value>,
415    ) {
416        if !self.config.enabled || level as u8 > self.config.log_level as u8 {
417            return;
418        }
419
420        let entry = LogEntry {
421            timestamp: Date::now(),
422            level,
423            message: message.to_string(),
424            category: category.to_string(),
425            source_file,
426            source_line,
427            metadata,
428        };
429
430        self.log_entries.push(entry);
431
432        if self.config.console_output {
433            let level_prefix = match level {
434                LogLevel::Error => "โŒ",
435                LogLevel::Warn => "โš ๏ธ",
436                LogLevel::Info => "โ„น๏ธ",
437                LogLevel::Debug => "๐Ÿ›",
438                LogLevel::Trace => "๐Ÿ”",
439            };
440
441            let formatted_message = format!("{} [{}] {}", level_prefix, category, message);
442
443            match level {
444                LogLevel::Error => web_sys::console::error_1(&formatted_message.into()),
445                LogLevel::Warn => web_sys::console::warn_1(&formatted_message.into()),
446                LogLevel::Info => web_sys::console::info_1(&formatted_message.into()),
447                LogLevel::Debug | LogLevel::Trace => {
448                    web_sys::console::log_1(&formatted_message.into())
449                },
450            }
451        }
452
453        self.trim_logs();
454    }
455
456    fn trim_logs(&mut self) {
457        if self.log_entries.len() > self.config.max_log_entries {
458            let excess = self.log_entries.len() - self.config.max_log_entries;
459            self.log_entries.drain(0..excess);
460        }
461    }
462
463    fn trim_metrics(&mut self) {
464        if self.performance_metrics.len() > self.config.max_log_entries {
465            let excess = self.performance_metrics.len() - self.config.max_log_entries;
466            self.performance_metrics.drain(0..excess);
467        }
468    }
469}
470
471/// Global debug logger instance
472static GLOBAL_LOGGER: Mutex<Option<DebugLogger>> = Mutex::new(None);
473
474/// Initialize the global debug logger
475#[wasm_bindgen]
476pub fn init_debug_logger(config: DebugConfig) {
477    if let Ok(mut logger) = GLOBAL_LOGGER.lock() {
478        *logger = Some(DebugLogger::new(config));
479    }
480}
481
482/// Get the global debug logger
483#[wasm_bindgen]
484pub fn get_debug_logger() -> Option<DebugLogger> {
485    GLOBAL_LOGGER.lock().ok().and_then(|logger| logger.clone())
486}
487
488/// Macro for easy logging with automatic category detection
489#[macro_export]
490macro_rules! debug_log {
491    (error, $msg:expr) => {
492        if let Some(mut logger) = get_debug_logger() {
493            logger.error($msg, module_path!());
494        }
495    };
496    (warn, $msg:expr) => {
497        if let Some(mut logger) = get_debug_logger() {
498            logger.warn($msg, module_path!());
499        }
500    };
501    (info, $msg:expr) => {
502        if let Some(mut logger) = get_debug_logger() {
503            logger.info($msg, module_path!());
504        }
505    };
506    (debug, $msg:expr) => {
507        if let Some(mut logger) = get_debug_logger() {
508            logger.debug($msg, module_path!());
509        }
510    };
511    (trace, $msg:expr) => {
512        if let Some(mut logger) = get_debug_logger() {
513            logger.trace($msg, module_path!());
514        }
515    };
516}
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521
522    #[test]
523    fn test_debug_config() {
524        let mut config = DebugConfig::new();
525        assert!(!config.enabled);
526
527        config = config.enable().set_log_level(LogLevel::Debug);
528        assert!(config.enabled);
529        assert_eq!(config.log_level as u8, LogLevel::Debug as u8);
530    }
531
532    #[test]
533    fn test_logger_creation() {
534        let config = DebugConfig::development();
535        let logger = DebugLogger::new(config);
536        assert_eq!(logger.log_count(), 0);
537        assert_eq!(logger.metrics_count(), 0);
538    }
539}