Coverage Report

Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/realizar/src/gpu/diagnostics.rs
Line
Count
Source
1
//! GPU Diagnostics Module (PMAT-802)
2
//!
3
//! Extracted from gpu/mod.rs - Logging, metrics, and debugging infrastructure.
4
//!
5
//! ## Contents
6
//! - `Logger`, `LogConfig`, `LogLevel`, `LogEntry` - Structured logging (IMP-079)
7
//! - `PhaseTimer`, `MemoryTracker`, `MemoryReport` - Performance tracking (IMP-080)
8
//! - `DiagnosticsCollector`, `DebugMode`, `RequestCapture`, `StateDump` - Debug tools (IMP-081)
9
10
use std::collections::HashMap;
11
12
// ============================================================================
13
// M32: Production Logging & Diagnostics (IMP-079, IMP-080, IMP-081)
14
// ============================================================================
15
16
/// Log level (IMP-079)
17
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
18
pub enum LogLevel {
19
    /// Trace level (most verbose)
20
    Trace,
21
    /// Debug level
22
    Debug,
23
    /// Info level
24
    Info,
25
    /// Warning level
26
    Warn,
27
    /// Error level
28
    Error,
29
}
30
31
impl LogLevel {
32
1
    fn as_str(self) -> &'static str {
33
1
        match self {
34
0
            Self::Trace => "TRACE",
35
0
            Self::Debug => "DEBUG",
36
1
            Self::Info => "INFO",
37
0
            Self::Warn => "WARN",
38
0
            Self::Error => "ERROR",
39
        }
40
1
    }
41
}
42
43
/// Log entry (IMP-079)
44
#[derive(Debug, Clone)]
45
pub struct LogEntry {
46
    level: LogLevel,
47
    message: String,
48
    timestamp: u64,
49
    correlation_id: Option<String>,
50
    fields: HashMap<String, String>,
51
}
52
53
impl LogEntry {
54
    /// Create new log entry
55
    #[must_use]
56
3
    pub fn new(level: LogLevel, message: &str) -> Self {
57
        use std::time::{SystemTime, UNIX_EPOCH};
58
59
3
        let timestamp = SystemTime::now()
60
3
            .duration_since(UNIX_EPOCH)
61
3
            .unwrap_or_default()
62
3
            .as_millis() as u64;
63
64
3
        Self {
65
3
            level,
66
3
            message: message.to_string(),
67
3
            timestamp,
68
3
            correlation_id: None,
69
3
            fields: HashMap::new(),
70
3
        }
71
3
    }
72
73
    /// Set correlation ID
74
    #[must_use]
75
1
    pub fn with_correlation_id(mut self, id: &str) -> Self {
76
1
        self.correlation_id = Some(id.to_string());
77
1
        self
78
1
    }
79
80
    /// Add custom field
81
    #[must_use]
82
2
    pub fn with_field(mut self, key: &str, value: &str) -> Self {
83
2
        self.fields.insert(key.to_string(), value.to_string());
84
2
        self
85
2
    }
86
87
    /// Get correlation ID
88
    #[must_use]
89
1
    pub fn correlation_id(&self) -> Option<&str> {
90
1
        self.correlation_id.as_deref()
91
1
    }
92
93
    /// Get log level
94
    #[must_use]
95
1
    pub fn level(&self) -> LogLevel {
96
1
        self.level
97
1
    }
98
99
    /// Get timestamp
100
    #[must_use]
101
1
    pub fn timestamp(&self) -> u64 {
102
1
        self.timestamp
103
1
    }
104
105
    /// Convert to JSON
106
    #[must_use]
107
1
    pub fn to_json(&self) -> String {
108
        use std::fmt::Write;
109
110
1
        let mut json = format!(
111
1
            "{{\"level\":\"{}\",\"message\":\"{}\",\"timestamp\":{}",
112
1
            self.level.as_str(),
113
            self.message,
114
            self.timestamp
115
        );
116
117
1
        if let Some(ref id) = self.correlation_id {
118
1
            let _ = write!(json, ",\"correlation_id\":\"{}\"", id);
119
1
        
}0
120
121
3
        for (
key2
,
value2
) in &self.fields {
122
2
            let _ = write!(json, ",\"{}\":\"{}\"", key, value);
123
2
        }
124
125
1
        json.push('}');
126
1
        json
127
1
    }
128
}
129
130
/// Log configuration (IMP-079)
131
#[derive(Debug, Clone)]
132
pub struct LogConfig {
133
    default_level: LogLevel,
134
    json_format: bool,
135
    module_levels: HashMap<String, LogLevel>,
136
}
137
138
impl LogConfig {
139
    /// Create new log config
140
    #[must_use]
141
1
    pub fn new() -> Self {
142
1
        Self {
143
1
            default_level: LogLevel::Info,
144
1
            json_format: false,
145
1
            module_levels: HashMap::new(),
146
1
        }
147
1
    }
148
149
    /// Set default log level
150
    #[must_use]
151
1
    pub fn with_level(mut self, level: LogLevel) -> Self {
152
1
        self.default_level = level;
153
1
        self
154
1
    }
155
156
    /// Enable JSON format
157
    #[must_use]
158
1
    pub fn with_json_format(mut self, enabled: bool) -> Self {
159
1
        self.json_format = enabled;
160
1
        self
161
1
    }
162
163
    /// Set module-specific log level
164
    #[must_use]
165
1
    pub fn with_module_level(mut self, module: &str, level: LogLevel) -> Self {
166
1
        self.module_levels.insert(module.to_string(), level);
167
1
        self
168
1
    }
169
}
170
171
impl Default for LogConfig {
172
0
    fn default() -> Self {
173
0
        Self::new()
174
0
    }
175
}
176
177
/// Logger (IMP-079)
178
pub struct Logger {
179
    config: LogConfig,
180
}
181
182
impl Logger {
183
    /// Create new logger
184
    #[must_use]
185
1
    pub fn new(config: LogConfig) -> Self {
186
1
        Self { config }
187
1
    }
188
189
    /// Check if log level is enabled for module
190
    #[must_use]
191
3
    pub fn is_enabled(&self, level: LogLevel, module: &str) -> bool {
192
3
        let min_level = self
193
3
            .config
194
3
            .module_levels
195
3
            .get(module)
196
3
            .copied()
197
3
            .unwrap_or(self.config.default_level);
198
3
        level >= min_level
199
3
    }
200
}
201
202
/// Phase timer for latency breakdown (IMP-080)
203
pub struct PhaseTimer {
204
    phases: std::sync::Mutex<HashMap<String, (Option<std::time::Instant>, u64)>>,
205
}
206
207
impl PhaseTimer {
208
    /// Create new phase timer
209
    #[must_use]
210
1
    pub fn new() -> Self {
211
1
        Self {
212
1
            phases: std::sync::Mutex::new(HashMap::new()),
213
1
        }
214
1
    }
215
216
    /// Start timing a phase
217
2
    pub fn start_phase(&self, name: &str) {
218
2
        let mut phases = self.phases.lock().expect("mutex poisoned");
219
2
        phases.insert(name.to_string(), (Some(std::time::Instant::now()), 0));
220
2
    }
221
222
    /// End timing a phase
223
2
    pub fn end_phase(&self, name: &str) {
224
2
        let mut phases = self.phases.lock().expect("mutex poisoned");
225
2
        if let Some((Some(start_time), _)) = phases.get(name) {
226
2
            let elapsed = start_time.elapsed().as_micros() as u64;
227
2
            phases.insert(name.to_string(), (None, elapsed));
228
2
        
}0
229
2
    }
230
231
    /// Get timing breakdown
232
    #[must_use]
233
2
    pub fn breakdown(&self) -> HashMap<String, u64> {
234
2
        let phases = self.phases.lock().expect("mutex poisoned");
235
4
        
phases.iter()2
.
map2
(|(k, (_, v))| (k.clone(), *v)).
collect2
()
236
2
    }
237
}
238
239
impl Default for PhaseTimer {
240
0
    fn default() -> Self {
241
0
        Self::new()
242
0
    }
243
}
244
245
/// Memory report (IMP-080)
246
#[derive(Debug, Clone)]
247
pub struct MemoryReport {
248
    /// Peak memory usage in bytes
249
    pub peak_bytes: u64,
250
    /// Current memory usage in bytes
251
    pub current_bytes: u64,
252
    /// Total allocation count
253
    pub allocation_count: u64,
254
}
255
256
/// Memory tracker (IMP-080)
257
pub struct MemoryTracker {
258
    current: std::sync::atomic::AtomicU64,
259
    peak: std::sync::atomic::AtomicU64,
260
    allocation_count: std::sync::atomic::AtomicU64,
261
}
262
263
impl MemoryTracker {
264
    /// Create new memory tracker
265
    #[must_use]
266
2
    pub fn new() -> Self {
267
2
        Self {
268
2
            current: std::sync::atomic::AtomicU64::new(0),
269
2
            peak: std::sync::atomic::AtomicU64::new(0),
270
2
            allocation_count: std::sync::atomic::AtomicU64::new(0),
271
2
        }
272
2
    }
273
274
    /// Record memory allocation
275
3
    pub fn record_allocation(&self, _name: &str, bytes: u64) {
276
3
        let new_current = self
277
3
            .current
278
3
            .fetch_add(bytes, std::sync::atomic::Ordering::SeqCst)
279
3
            + bytes;
280
3
        self.allocation_count
281
3
            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
282
283
        // Update peak if necessary
284
3
        let mut peak = self.peak.load(std::sync::atomic::Ordering::SeqCst);
285
3
        while new_current > peak {
286
3
            match self.peak.compare_exchange_weak(
287
3
                peak,
288
3
                new_current,
289
3
                std::sync::atomic::Ordering::SeqCst,
290
3
                std::sync::atomic::Ordering::Relaxed,
291
3
            ) {
292
3
                Ok(_) => break,
293
0
                Err(current_peak) => peak = current_peak,
294
            }
295
        }
296
3
    }
297
298
    /// Record memory deallocation
299
2
    pub fn record_deallocation(&self, _name: &str, bytes: u64) {
300
2
        self.current
301
2
            .fetch_sub(bytes, std::sync::atomic::Ordering::SeqCst);
302
2
    }
303
304
    /// Get memory report
305
    #[must_use]
306
1
    pub fn report(&self) -> MemoryReport {
307
1
        MemoryReport {
308
1
            peak_bytes: self.peak.load(std::sync::atomic::Ordering::SeqCst),
309
1
            current_bytes: self.current.load(std::sync::atomic::Ordering::SeqCst),
310
1
            allocation_count: self
311
1
                .allocation_count
312
1
                .load(std::sync::atomic::Ordering::SeqCst),
313
1
        }
314
1
    }
315
}
316
317
impl Default for MemoryTracker {
318
0
    fn default() -> Self {
319
0
        Self::new()
320
0
    }
321
}
322
323
/// Diagnostics summary (IMP-080)
324
#[derive(Debug, Clone)]
325
pub struct DiagnosticsSummary {
326
    /// Number of requests tracked
327
    pub request_count: u64,
328
}
329
330
/// Diagnostics collector (IMP-080)
331
pub struct DiagnosticsCollector {
332
    /// Request count (pub(crate) for test access)
333
    pub(crate) request_count: std::sync::atomic::AtomicU64,
334
    #[allow(dead_code)]
335
    timings: std::sync::Mutex<Vec<HashMap<String, u64>>>,
336
    #[allow(dead_code)]
337
    memory_snapshots: std::sync::Mutex<Vec<MemoryReport>>,
338
}
339
340
impl DiagnosticsCollector {
341
    /// Create new diagnostics collector
342
    #[must_use]
343
2
    pub fn new() -> Self {
344
2
        Self {
345
2
            request_count: std::sync::atomic::AtomicU64::new(0),
346
2
            timings: std::sync::Mutex::new(Vec::new()),
347
2
            memory_snapshots: std::sync::Mutex::new(Vec::new()),
348
2
        }
349
2
    }
350
351
    /// Record request timing
352
1
    pub fn record_request_timing(&self, _request_id: &str, timing: HashMap<String, u64>) {
353
1
        self.request_count
354
1
            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
355
1
        self.timings.lock().expect("mutex poisoned").push(timing);
356
1
    }
357
358
    /// Record memory snapshot
359
1
    pub fn record_memory_snapshot(&self, report: MemoryReport) {
360
1
        self.memory_snapshots
361
1
            .lock()
362
1
            .expect("mutex poisoned")
363
1
            .push(report);
364
1
    }
365
366
    /// Get diagnostics summary
367
    #[must_use]
368
1
    pub fn summary(&self) -> DiagnosticsSummary {
369
1
        DiagnosticsSummary {
370
1
            request_count: self.request_count.load(std::sync::atomic::Ordering::SeqCst),
371
1
        }
372
1
    }
373
}
374
375
impl Default for DiagnosticsCollector {
376
0
    fn default() -> Self {
377
0
        Self::new()
378
0
    }
379
}
380
381
/// Debug mode controller (IMP-081)
382
pub struct DebugMode {
383
    enabled: std::sync::atomic::AtomicBool,
384
}
385
386
impl DebugMode {
387
    /// Create new debug mode
388
    #[must_use]
389
2
    pub fn new() -> Self {
390
2
        Self {
391
2
            enabled: std::sync::atomic::AtomicBool::new(false),
392
2
        }
393
2
    }
394
395
    /// Check if debug mode is enabled
396
    #[must_use]
397
3
    pub fn is_enabled(&self) -> bool {
398
3
        self.enabled.load(std::sync::atomic::Ordering::SeqCst)
399
3
    }
400
401
    /// Enable debug mode
402
1
    pub fn enable(&self) {
403
1
        self.enabled
404
1
            .store(true, std::sync::atomic::Ordering::SeqCst);
405
1
    }
406
407
    /// Disable debug mode
408
    #[allow(dead_code)]
409
0
    pub fn disable(&self) {
410
0
        self.enabled
411
0
            .store(false, std::sync::atomic::Ordering::SeqCst);
412
0
    }
413
}
414
415
impl Default for DebugMode {
416
0
    fn default() -> Self {
417
0
        Self::new()
418
0
    }
419
}
420
421
/// Request capture for replay (IMP-081)
422
#[derive(Debug, Clone)]
423
pub struct RequestCapture {
424
    input: String,
425
    params: HashMap<String, String>,
426
}
427
428
impl RequestCapture {
429
    /// Create new request capture
430
    #[must_use]
431
2
    pub fn new() -> Self {
432
2
        Self {
433
2
            input: String::new(),
434
2
            params: HashMap::new(),
435
2
        }
436
2
    }
437
438
    /// Set input
439
    #[must_use]
440
1
    pub fn with_input(mut self, input: &str) -> Self {
441
1
        self.input = input.to_string();
442
1
        self
443
1
    }
444
445
    /// Add parameter
446
    #[must_use]
447
2
    pub fn with_params(mut self, key: &str, value: &str) -> Self {
448
2
        self.params.insert(key.to_string(), value.to_string());
449
2
        self
450
2
    }
451
452
    /// Get input
453
    #[must_use]
454
2
    pub fn input(&self) -> &str {
455
2
        &self.input
456
2
    }
457
458
    /// Get params
459
    #[must_use]
460
1
    pub fn params(&self) -> &HashMap<String, String> {
461
1
        &self.params
462
1
    }
463
464
    /// Serialize to JSON
465
    #[must_use]
466
1
    pub fn to_json(&self) -> String {
467
1
        let params_json: Vec<String> = self
468
1
            .params
469
1
            .iter()
470
2
            .
map1
(|(k, v)| format!("\"{}\":\"{}\"", k, v))
471
1
            .collect();
472
1
        format!(
473
1
            "{{\"input\":\"{}\",\"params\":{{{}}}}}",
474
            self.input,
475
1
            params_json.join(",")
476
        )
477
1
    }
478
479
    /// Deserialize from JSON (simple implementation)
480
    ///
481
    /// # Errors
482
    /// Returns error if JSON is malformed or missing input field.
483
1
    pub fn from_json(json: &str) -> std::result::Result<Self, &'static str> {
484
        // Simple extraction - production would use serde
485
1
        let input_start = json.find("\"input\":\"").ok_or("Missing input")
?0
;
486
1
        let input_rest = &json[input_start + 9..];
487
1
        let input_end = input_rest.find('"').ok_or("Invalid input")
?0
;
488
1
        let input = &input_rest[..input_end];
489
490
1
        Ok(Self {
491
1
            input: input.to_string(),
492
1
            params: HashMap::new(),
493
1
        })
494
1
    }
495
}
496
497
impl Default for RequestCapture {
498
0
    fn default() -> Self {
499
0
        Self::new()
500
0
    }
501
}
502
503
/// State dump for debugging (IMP-081)
504
#[derive(Debug, Clone)]
505
pub struct StateDump {
506
    error: String,
507
    stack_trace: String,
508
    state: HashMap<String, String>,
509
}
510
511
impl StateDump {
512
    /// Create new state dump
513
    #[must_use]
514
2
    pub fn new() -> Self {
515
2
        Self {
516
2
            error: String::new(),
517
2
            stack_trace: String::new(),
518
2
            state: HashMap::new(),
519
2
        }
520
2
    }
521
522
    /// Set error
523
    #[must_use]
524
1
    pub fn with_error(mut self, error: &str) -> Self {
525
1
        self.error = error.to_string();
526
1
        self
527
1
    }
528
529
    /// Set stack trace
530
    #[must_use]
531
1
    pub fn with_stack_trace(mut self, trace: &str) -> Self {
532
1
        self.stack_trace = trace.to_string();
533
1
        self
534
1
    }
535
536
    /// Add state
537
    #[must_use]
538
2
    pub fn with_state(mut self, key: &str, value: &str) -> Self {
539
2
        self.state.insert(key.to_string(), value.to_string());
540
2
        self
541
2
    }
542
543
    /// Get error
544
    #[must_use]
545
1
    pub fn error(&self) -> &str {
546
1
        &self.error
547
1
    }
548
549
    /// Get stack trace
550
    #[must_use]
551
1
    pub fn stack_trace(&self) -> &str {
552
1
        &self.stack_trace
553
1
    }
554
555
    /// Get state
556
    #[must_use]
557
1
    pub fn state(&self) -> &HashMap<String, String> {
558
1
        &self.state
559
1
    }
560
561
    /// Convert to JSON
562
    #[must_use]
563
1
    pub fn to_json(&self) -> String {
564
1
        let state_json: Vec<String> = self
565
1
            .state
566
1
            .iter()
567
2
            .
map1
(|(k, v)| format!("\"{}\":\"{}\"", k, v))
568
1
            .collect();
569
1
        format!(
570
1
            "{{\"error\":\"{}\",\"stack_trace\":\"{}\",\"state\":{{{}}}}}",
571
            self.error,
572
1
            self.stack_trace.replace('\n', "\\n"),
573
1
            state_json.join(",")
574
        )
575
1
    }
576
}
577
578
impl Default for StateDump {
579
0
    fn default() -> Self {
580
0
        Self::new()
581
0
    }
582
}