khora_infra/telemetry/
gpu_monitor.rs1use std::borrow::Cow;
18use std::sync::Mutex;
19
20use khora_core::renderer::api::core::{GpuHook, RenderStats};
21use khora_core::telemetry::monitoring::{
22 GpuReport, MonitoredResourceType, ResourceMonitor, ResourceUsageReport,
23};
24
25#[derive(Debug)]
27pub struct GpuMonitor {
28 system_name: String,
29 last_frame_stats: Mutex<Option<GpuReport>>,
30}
31
32impl GpuMonitor {
33 pub fn new(system_name: String) -> Self {
35 Self {
36 system_name,
37 last_frame_stats: Mutex::new(None),
38 }
39 }
40
41 pub fn get_gpu_report(&self) -> Option<GpuReport> {
43 *self.last_frame_stats.lock().unwrap()
44 }
45
46 pub fn update_from_frame_stats(&self, render_stats: &RenderStats) {
48 let frame_start_us = 0u32; let frame_end_us = (render_stats.gpu_frame_total_time_ms * 1000.0) as u32;
52 let main_pass_duration_us = (render_stats.gpu_main_pass_time_ms * 1000.0) as u32;
53
54 let main_pass_begin_us = (frame_end_us - main_pass_duration_us) / 2;
56 let main_pass_end_us = main_pass_begin_us + main_pass_duration_us;
57
58 let mut hook_timings = [None; 4];
59 hook_timings[GpuHook::FrameStart as usize] = Some(frame_start_us);
60 hook_timings[GpuHook::MainPassBegin as usize] = Some(main_pass_begin_us);
61 hook_timings[GpuHook::MainPassEnd as usize] = Some(main_pass_end_us);
62 hook_timings[GpuHook::FrameEnd as usize] = Some(frame_end_us);
63
64 let report = GpuReport {
65 frame_number: render_stats.frame_number,
66 hook_timings_us: hook_timings,
67 cpu_preparation_time_us: Some((render_stats.cpu_preparation_time_ms * 1000.0) as u32),
69 cpu_submission_time_us: Some(
70 (render_stats.cpu_render_submission_time_ms * 1000.0) as u32,
71 ),
72 draw_calls: render_stats.draw_calls,
73 triangles_rendered: render_stats.triangles_rendered,
74 };
75
76 let mut last_stats = self.last_frame_stats.lock().unwrap();
77 *last_stats = Some(report);
78 }
79}
80
81impl ResourceMonitor for GpuMonitor {
82 fn monitor_id(&self) -> Cow<'static, str> {
83 Cow::Owned(format!("Gpu_{}", self.system_name))
84 }
85
86 fn resource_type(&self) -> MonitoredResourceType {
87 MonitoredResourceType::Gpu
88 }
89
90 fn get_usage_report(&self) -> ResourceUsageReport {
91 ResourceUsageReport::default()
93 }
94
95 fn get_gpu_report(&self) -> Option<GpuReport> {
96 self.get_gpu_report()
97 }
98
99 fn get_metrics(
100 &self,
101 ) -> Vec<(
102 khora_core::telemetry::metrics::MetricId,
103 khora_core::telemetry::metrics::MetricValue,
104 )> {
105 use khora_core::telemetry::metrics::{MetricId, MetricValue};
106 let mut metrics = Vec::new();
107
108 if let Some(report) = self.get_gpu_report() {
109 metrics.push((
110 MetricId::new("renderer", "draw_calls"),
111 MetricValue::Gauge(report.draw_calls as f64),
112 ));
113 metrics.push((
114 MetricId::new("renderer", "triangles"),
115 MetricValue::Gauge(report.triangles_rendered as f64),
116 ));
117
118 if let Some(total_ms) = report.frame_total_duration_us() {
119 metrics.push((
120 MetricId::new("renderer", "frame_time"),
121 MetricValue::Gauge(total_ms as f64 / 1000.0),
122 ));
123 }
124
125 if let Some(main_ms) = report.main_pass_duration_us() {
126 metrics.push((
127 MetricId::new("renderer", "gpu_time"),
128 MetricValue::Gauge(main_ms as f64 / 1000.0),
129 ));
130 }
131 }
132
133 metrics
134 }
135
136 fn as_any(&self) -> &dyn std::any::Any {
137 self
138 }
139
140 fn update(&self) {
141 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn gpu_monitor_creation() {
152 let monitor = GpuMonitor::new("TestGPU".to_string());
153 assert_eq!(monitor.monitor_id(), "Gpu_TestGPU");
154 assert_eq!(monitor.resource_type(), MonitoredResourceType::Gpu);
155 }
156
157 #[test]
158 fn gpu_monitor_update_stats() {
159 let monitor = GpuMonitor::new("TestGPU".to_string());
160
161 assert!(monitor.get_gpu_report().is_none());
163
164 let render_stats = RenderStats {
166 frame_number: 42,
167 cpu_preparation_time_ms: 1.0,
168 cpu_render_submission_time_ms: 0.5,
169 gpu_main_pass_time_ms: 16.67,
170 gpu_frame_total_time_ms: 16.67,
171 draw_calls: 100,
172 triangles_rendered: 1000,
173 vram_usage_estimate_mb: 256.0,
174 };
175
176 monitor.update_from_frame_stats(&render_stats);
178
179 let report = monitor.get_gpu_report();
181 assert!(report.is_some());
182
183 let report = report.unwrap();
184 assert_eq!(report.frame_number, 42);
185 assert_eq!(report.cpu_preparation_time_us, Some(1000)); assert_eq!(report.cpu_submission_time_us, Some(500)); }
188
189 #[test]
190 fn gpu_report_hook_methods() {
191 let monitor = GpuMonitor::new("TestGPU".to_string());
192
193 let render_stats = RenderStats {
194 frame_number: 1,
195 cpu_preparation_time_ms: 0.1,
196 cpu_render_submission_time_ms: 0.05,
197 gpu_main_pass_time_ms: 16.67,
198 gpu_frame_total_time_ms: 17.0,
199 draw_calls: 50,
200 triangles_rendered: 500,
201 vram_usage_estimate_mb: 128.0,
202 };
203
204 monitor.update_from_frame_stats(&render_stats);
205 let report = monitor.get_gpu_report().unwrap();
206
207 assert_eq!(report.frame_number, 1);
209
210 assert_eq!(report.frame_total_duration_us(), Some(17000)); assert_eq!(report.main_pass_duration_us(), Some(16670)); }
214
215 #[test]
216 fn gpu_report_missing_data() {
217 let monitor = GpuMonitor::new("TestGPU".to_string());
218
219 let render_stats = RenderStats {
220 frame_number: 1,
221 cpu_preparation_time_ms: 0.0,
222 cpu_render_submission_time_ms: 0.0,
223 gpu_main_pass_time_ms: 0.0,
224 gpu_frame_total_time_ms: 0.0,
225 draw_calls: 0,
226 triangles_rendered: 0,
227 vram_usage_estimate_mb: 0.0,
228 };
229
230 monitor.update_from_frame_stats(&render_stats);
231 let report = monitor.get_gpu_report().unwrap();
232
233 assert_eq!(report.frame_total_duration_us(), Some(0)); assert_eq!(report.main_pass_duration_us(), Some(0)); }
237}