Skip to main content

hojicha_core/debug/
tracer.rs

1//! Command and message tracing utilities
2
3use std::collections::VecDeque;
4use std::fmt::{self, Debug, Display};
5use std::time::{Duration, Instant};
6
7/// Trace levels for different types of events
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9pub struct TraceLevel(u8);
10
11impl TraceLevel {
12    /// No tracing
13    pub const NONE: TraceLevel = TraceLevel(0);
14    /// Trace commands only
15    pub const COMMANDS: TraceLevel = TraceLevel(1);
16    /// Trace messages only
17    pub const MESSAGES: TraceLevel = TraceLevel(2);
18    /// Trace events only
19    pub const EVENTS: TraceLevel = TraceLevel(4);
20    /// Trace performance metrics
21    pub const METRICS: TraceLevel = TraceLevel(8);
22    /// Trace everything
23    pub const ALL: TraceLevel = TraceLevel(15);
24
25    /// Check if a specific level is enabled
26    pub fn contains(&self, other: TraceLevel) -> bool {
27        self.0 & other.0 != 0
28    }
29
30    /// Combine trace levels
31    #[must_use]
32    pub fn combine(&self, other: TraceLevel) -> TraceLevel {
33        TraceLevel(self.0 | other.0)
34    }
35
36    /// Parse from string (comma-separated)
37    pub fn parse(s: &str) -> TraceLevel {
38        let mut level = TraceLevel::NONE;
39        for part in s.split(',') {
40            match part.trim().to_lowercase().as_str() {
41                "commands" | "cmd" => level = level.combine(TraceLevel::COMMANDS),
42                "messages" | "msg" => level = level.combine(TraceLevel::MESSAGES),
43                "events" | "evt" => level = level.combine(TraceLevel::EVENTS),
44                "metrics" | "perf" => level = level.combine(TraceLevel::METRICS),
45                "all" => return TraceLevel::ALL,
46                _ => {}
47            }
48        }
49        level
50    }
51}
52
53impl Default for TraceLevel {
54    fn default() -> Self {
55        TraceLevel::NONE
56    }
57}
58
59/// Types of events that can be traced
60#[derive(Debug, Clone)]
61pub enum TraceEvent {
62    /// Command execution started
63    CommandStart {
64        /// Unique identifier for the command
65        id: u64,
66        /// Name of the command
67        name: String,
68        /// When the command started
69        timestamp: Instant,
70    },
71    /// Command execution completed
72    CommandEnd {
73        /// Unique identifier for the command
74        id: u64,
75        /// How long the command took to execute
76        duration: Duration,
77        /// Result of the command execution
78        result: String,
79    },
80    /// Message sent
81    MessageSent {
82        /// Unique identifier for the message
83        id: u64,
84        /// String representation of the message
85        message: String,
86        /// When the message was sent
87        timestamp: Instant,
88    },
89    /// Message received
90    MessageReceived {
91        /// Unique identifier for the message
92        id: u64,
93        /// String representation of the message
94        message: String,
95        /// When the message was received
96        timestamp: Instant,
97    },
98    /// Event processed
99    EventProcessed {
100        /// Type of event that was processed
101        event_type: String,
102        /// When the event was processed
103        timestamp: Instant,
104    },
105    /// Frame rendered
106    FrameRendered {
107        /// Time taken to render the frame
108        duration: Duration,
109        /// Current frames per second
110        fps: f32,
111    },
112    /// Custom trace event
113    Custom {
114        /// Label for the custom event
115        label: String,
116        /// Additional data for the event
117        data: String,
118    },
119}
120
121impl Display for TraceEvent {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        match self {
124            TraceEvent::CommandStart { id, name, .. } => {
125                write!(f, "[CMD_START #{:04}] {}", id, name)
126            }
127            TraceEvent::CommandEnd {
128                id,
129                duration,
130                result,
131            } => {
132                write!(f, "[CMD_END   #{:04}] {:?} - {}", id, duration, result)
133            }
134            TraceEvent::MessageSent { id, message, .. } => {
135                write!(f, "[MSG_SENT  #{:04}] {}", id, message)
136            }
137            TraceEvent::MessageReceived { id, message, .. } => {
138                write!(f, "[MSG_RECV  #{:04}] {}", id, message)
139            }
140            TraceEvent::EventProcessed { event_type, .. } => {
141                write!(f, "[EVENT     ] {}", event_type)
142            }
143            TraceEvent::FrameRendered { duration, fps } => {
144                write!(f, "[FRAME     ] {:?} @ {:.1} FPS", duration, fps)
145            }
146            TraceEvent::Custom { label, data } => {
147                write!(f, "[{}] {}", label, data)
148            }
149        }
150    }
151}
152
153/// Tracer for recording and outputting debug events
154pub struct Tracer {
155    level: TraceLevel,
156    buffer: VecDeque<TraceEvent>,
157    buffer_size: usize,
158    next_id: u64,
159    start_time: Instant,
160}
161
162impl Tracer {
163    /// Create a new tracer with the specified level
164    pub fn new(level: TraceLevel) -> Self {
165        Self {
166            level,
167            buffer: VecDeque::with_capacity(1000),
168            buffer_size: 1000,
169            next_id: 1,
170            start_time: Instant::now(),
171        }
172    }
173
174    /// Get the next unique ID for tracing
175    pub fn next_id(&mut self) -> u64 {
176        let id = self.next_id;
177        self.next_id += 1;
178        id
179    }
180
181    /// Trace an event
182    pub fn trace(&mut self, event: TraceEvent) {
183        // Check if this type of event should be traced
184        let should_trace = match &event {
185            TraceEvent::CommandStart { .. } | TraceEvent::CommandEnd { .. } => {
186                self.level.contains(TraceLevel::COMMANDS)
187            }
188            TraceEvent::MessageSent { .. } | TraceEvent::MessageReceived { .. } => {
189                self.level.contains(TraceLevel::MESSAGES)
190            }
191            TraceEvent::EventProcessed { .. } => self.level.contains(TraceLevel::EVENTS),
192            TraceEvent::FrameRendered { .. } => self.level.contains(TraceLevel::METRICS),
193            TraceEvent::Custom { .. } => true,
194        };
195
196        if should_trace {
197            // Output immediately
198            let elapsed = self.start_time.elapsed();
199            eprintln!("[{:>8.3}s] {}", elapsed.as_secs_f32(), event);
200
201            // Store in buffer
202            if self.buffer.len() >= self.buffer_size {
203                self.buffer.pop_front();
204            }
205            self.buffer.push_back(event);
206        }
207    }
208
209    /// Get the current trace buffer
210    pub fn get_buffer(&self) -> Vec<TraceEvent> {
211        self.buffer.iter().cloned().collect()
212    }
213
214    /// Clear the trace buffer
215    pub fn clear_buffer(&mut self) {
216        self.buffer.clear();
217    }
218
219    /// Flush any pending output
220    pub fn flush(&mut self) {
221        // In the future, this could write to a file or send to a network endpoint
222        // For now, we output to stderr immediately so nothing to flush
223    }
224}