Skip to main content

hojicha_core/debug/
metrics.rs

1//! Performance metrics collection
2
3use std::collections::VecDeque;
4use std::time::{Duration, Instant};
5
6/// Metrics for a single frame
7#[derive(Debug, Clone)]
8pub struct FrameMetrics {
9    /// Time taken to process update
10    pub update_duration: Duration,
11    /// Time taken to render view
12    pub view_duration: Duration,
13    /// Total frame time
14    pub frame_duration: Duration,
15    /// Number of events processed
16    pub events_processed: usize,
17    /// Number of commands executed
18    pub commands_executed: usize,
19    /// Frame timestamp
20    pub timestamp: Instant,
21}
22
23impl FrameMetrics {
24    /// Calculate FPS from frame duration
25    pub fn fps(&self) -> f32 {
26        if self.frame_duration.as_secs_f32() > 0.0 {
27            1.0 / self.frame_duration.as_secs_f32()
28        } else {
29            0.0
30        }
31    }
32}
33
34/// Performance metrics collector
35#[derive(Debug, Clone)]
36pub struct PerformanceMetrics {
37    /// Recent frame metrics (rolling window)
38    frames: VecDeque<FrameMetrics>,
39    /// Maximum number of frames to keep
40    max_frames: usize,
41    /// Total events processed
42    total_events: usize,
43    /// Total commands executed
44    total_commands: usize,
45    /// Start time
46    start_time: Instant,
47    /// Current frame being measured
48    current_frame: Option<FrameBuilder>,
49}
50
51#[derive(Debug, Clone)]
52struct FrameBuilder {
53    update_start: Option<Instant>,
54    update_duration: Option<Duration>,
55    view_start: Option<Instant>,
56    view_duration: Option<Duration>,
57    frame_start: Instant,
58    events_processed: usize,
59    commands_executed: usize,
60}
61
62impl PerformanceMetrics {
63    /// Create a new performance metrics collector
64    pub fn new() -> Self {
65        Self::with_capacity(1000)
66    }
67
68    /// Create with a specific frame buffer capacity
69    pub fn with_capacity(max_frames: usize) -> Self {
70        Self {
71            frames: VecDeque::with_capacity(max_frames),
72            max_frames,
73            total_events: 0,
74            total_commands: 0,
75            start_time: Instant::now(),
76            current_frame: None,
77        }
78    }
79
80    /// Start measuring a new frame
81    pub fn start_frame(&mut self) {
82        self.current_frame = Some(FrameBuilder {
83            update_start: None,
84            update_duration: None,
85            view_start: None,
86            view_duration: None,
87            frame_start: Instant::now(),
88            events_processed: 0,
89            commands_executed: 0,
90        });
91    }
92
93    /// Start measuring update phase
94    pub fn start_update(&mut self) {
95        if let Some(frame) = &mut self.current_frame {
96            frame.update_start = Some(Instant::now());
97        }
98    }
99
100    /// End measuring update phase
101    pub fn end_update(&mut self) {
102        if let Some(frame) = &mut self.current_frame {
103            if let Some(start) = frame.update_start {
104                frame.update_duration = Some(start.elapsed());
105            }
106        }
107    }
108
109    /// Start measuring view phase
110    pub fn start_view(&mut self) {
111        if let Some(frame) = &mut self.current_frame {
112            frame.view_start = Some(Instant::now());
113        }
114    }
115
116    /// End measuring view phase
117    pub fn end_view(&mut self) {
118        if let Some(frame) = &mut self.current_frame {
119            if let Some(start) = frame.view_start {
120                frame.view_duration = Some(start.elapsed());
121            }
122        }
123    }
124
125    /// Record an event being processed
126    pub fn record_event(&mut self) {
127        self.total_events += 1;
128        if let Some(frame) = &mut self.current_frame {
129            frame.events_processed += 1;
130        }
131    }
132
133    /// Record a command being executed
134    pub fn record_command(&mut self) {
135        self.total_commands += 1;
136        if let Some(frame) = &mut self.current_frame {
137            frame.commands_executed += 1;
138        }
139    }
140
141    /// End the current frame and record metrics
142    pub fn end_frame(&mut self) {
143        if let Some(builder) = self.current_frame.take() {
144            let metrics = FrameMetrics {
145                update_duration: builder.update_duration.unwrap_or_default(),
146                view_duration: builder.view_duration.unwrap_or_default(),
147                frame_duration: builder.frame_start.elapsed(),
148                events_processed: builder.events_processed,
149                commands_executed: builder.commands_executed,
150                timestamp: builder.frame_start,
151            };
152            self.record_frame(metrics);
153        }
154    }
155
156    /// Record frame metrics
157    pub fn record_frame(&mut self, metrics: FrameMetrics) {
158        // Update totals
159        self.total_events += metrics.events_processed;
160        self.total_commands += metrics.commands_executed;
161
162        // Store frame
163        if self.frames.len() >= self.max_frames {
164            self.frames.pop_front();
165        }
166        self.frames.push_back(metrics);
167    }
168
169    /// Get average FPS over recent frames
170    pub fn average_fps(&self) -> f32 {
171        if self.frames.is_empty() {
172            return 0.0;
173        }
174
175        let total_duration: Duration = self.frames.iter().map(|f| f.frame_duration).sum();
176
177        if total_duration.as_secs_f32() > 0.0 {
178            self.frames.len() as f32 / total_duration.as_secs_f32()
179        } else {
180            0.0
181        }
182    }
183
184    /// Get current FPS (based on last frame)
185    pub fn current_fps(&self) -> f32 {
186        self.frames.back().map_or(0.0, FrameMetrics::fps)
187    }
188
189    /// Get average frame time
190    pub fn average_frame_time(&self) -> Duration {
191        if self.frames.is_empty() {
192            return Duration::ZERO;
193        }
194
195        let total: Duration = self.frames.iter().map(|f| f.frame_duration).sum();
196
197        total / self.frames.len() as u32
198    }
199
200    /// Get statistics summary
201    pub fn summary(&self) -> MetricsSummary {
202        MetricsSummary {
203            average_fps: self.average_fps(),
204            current_fps: self.current_fps(),
205            average_frame_time: self.average_frame_time(),
206            total_events: self.total_events,
207            total_commands: self.total_commands,
208            uptime: self.start_time.elapsed(),
209            frame_count: self.frames.len(),
210        }
211    }
212
213    /// Clear all metrics
214    pub fn clear(&mut self) {
215        self.frames.clear();
216        self.total_events = 0;
217        self.total_commands = 0;
218        self.start_time = Instant::now();
219    }
220}
221
222impl Default for PerformanceMetrics {
223    fn default() -> Self {
224        Self::new()
225    }
226}
227
228/// Summary of performance metrics
229#[derive(Debug, Clone)]
230pub struct MetricsSummary {
231    /// Average frames per second across all measurements
232    pub average_fps: f32,
233    /// Current frames per second (instantaneous)
234    pub current_fps: f32,
235    /// Average time taken to render each frame
236    pub average_frame_time: Duration,
237    /// Total number of events processed
238    pub total_events: usize,
239    /// Total number of commands executed
240    pub total_commands: usize,
241    /// Total time the application has been running
242    pub uptime: Duration,
243    /// Total number of frames rendered
244    pub frame_count: usize,
245}
246
247impl std::fmt::Display for MetricsSummary {
248    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249        write!(
250            f,
251            "FPS: {:.1} (avg: {:.1}) | Frame: {:?} | Events: {} | Commands: {} | Uptime: {:?}",
252            self.current_fps,
253            self.average_fps,
254            self.average_frame_time,
255            self.total_events,
256            self.total_commands,
257            self.uptime
258        )
259    }
260}