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
44 .last_frame_stats
45 .lock()
46 .unwrap_or_else(|e| e.into_inner())
47 }
48
49 pub fn update_from_frame_stats(&self, render_stats: &RenderStats) {
51 let frame_start_us = 0u32; let frame_end_us = (render_stats.gpu_frame_total_time_ms * 1000.0) as u32;
55 let main_pass_duration_us = (render_stats.gpu_main_pass_time_ms * 1000.0) as u32;
56
57 let main_pass_begin_us = (frame_end_us - main_pass_duration_us) / 2;
59 let main_pass_end_us = main_pass_begin_us + main_pass_duration_us;
60
61 let mut hook_timings = [None; 4];
62 hook_timings[GpuHook::FrameStart as usize] = Some(frame_start_us);
63 hook_timings[GpuHook::MainPassBegin as usize] = Some(main_pass_begin_us);
64 hook_timings[GpuHook::MainPassEnd as usize] = Some(main_pass_end_us);
65 hook_timings[GpuHook::FrameEnd as usize] = Some(frame_end_us);
66
67 let report = GpuReport {
68 frame_number: render_stats.frame_number,
69 hook_timings_us: hook_timings,
70 cpu_preparation_time_us: Some((render_stats.cpu_preparation_time_ms * 1000.0) as u32),
72 cpu_submission_time_us: Some(
73 (render_stats.cpu_render_submission_time_ms * 1000.0) as u32,
74 ),
75 draw_calls: render_stats.draw_calls,
76 triangles_rendered: render_stats.triangles_rendered,
77 };
78
79 let mut last_stats = self
80 .last_frame_stats
81 .lock()
82 .unwrap_or_else(|e| e.into_inner());
83 *last_stats = Some(report);
84 }
85}
86
87impl ResourceMonitor for GpuMonitor {
88 fn monitor_id(&self) -> Cow<'static, str> {
89 Cow::Owned(format!("Gpu_{}", self.system_name))
90 }
91
92 fn resource_type(&self) -> MonitoredResourceType {
93 MonitoredResourceType::Gpu
94 }
95
96 fn get_usage_report(&self) -> ResourceUsageReport {
97 ResourceUsageReport::default()
99 }
100
101 fn get_gpu_report(&self) -> Option<GpuReport> {
102 self.get_gpu_report()
103 }
104
105 fn get_metrics(
106 &self,
107 ) -> Vec<(
108 khora_core::telemetry::metrics::MetricId,
109 khora_core::telemetry::metrics::MetricValue,
110 )> {
111 use khora_core::telemetry::metrics::{MetricId, MetricValue};
112 let mut metrics = Vec::new();
113
114 if let Some(report) = self.get_gpu_report() {
115 metrics.push((
116 MetricId::new("renderer", "draw_calls"),
117 MetricValue::Gauge(report.draw_calls as f64),
118 ));
119 metrics.push((
120 MetricId::new("renderer", "triangles"),
121 MetricValue::Gauge(report.triangles_rendered as f64),
122 ));
123
124 if let Some(total_ms) = report.frame_total_duration_us() {
125 metrics.push((
126 MetricId::new("renderer", "frame_time"),
127 MetricValue::Gauge(total_ms as f64 / 1000.0),
128 ));
129 }
130
131 if let Some(main_ms) = report.main_pass_duration_us() {
132 metrics.push((
133 MetricId::new("renderer", "gpu_time"),
134 MetricValue::Gauge(main_ms as f64 / 1000.0),
135 ));
136 }
137 }
138
139 metrics
140 }
141
142 fn as_any(&self) -> &dyn std::any::Any {
143 self
144 }
145
146 fn update(&self) {
147 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn gpu_monitor_creation() {
158 let monitor = GpuMonitor::new("TestGPU".to_string());
159 assert_eq!(monitor.monitor_id(), "Gpu_TestGPU");
160 assert_eq!(monitor.resource_type(), MonitoredResourceType::Gpu);
161 }
162
163 #[test]
164 fn gpu_monitor_update_stats() {
165 let monitor = GpuMonitor::new("TestGPU".to_string());
166
167 assert!(monitor.get_gpu_report().is_none());
169
170 let render_stats = RenderStats {
172 frame_number: 42,
173 cpu_preparation_time_ms: 1.0,
174 cpu_render_submission_time_ms: 0.5,
175 gpu_main_pass_time_ms: 16.67,
176 gpu_frame_total_time_ms: 16.67,
177 draw_calls: 100,
178 triangles_rendered: 1000,
179 vram_usage_estimate_mb: 256.0,
180 };
181
182 monitor.update_from_frame_stats(&render_stats);
184
185 let report = monitor.get_gpu_report();
187 assert!(report.is_some());
188
189 let report = report.unwrap();
190 assert_eq!(report.frame_number, 42);
191 assert_eq!(report.cpu_preparation_time_us, Some(1000)); assert_eq!(report.cpu_submission_time_us, Some(500)); }
194
195 #[test]
196 fn gpu_report_hook_methods() {
197 let monitor = GpuMonitor::new("TestGPU".to_string());
198
199 let render_stats = RenderStats {
200 frame_number: 1,
201 cpu_preparation_time_ms: 0.1,
202 cpu_render_submission_time_ms: 0.05,
203 gpu_main_pass_time_ms: 16.67,
204 gpu_frame_total_time_ms: 17.0,
205 draw_calls: 50,
206 triangles_rendered: 500,
207 vram_usage_estimate_mb: 128.0,
208 };
209
210 monitor.update_from_frame_stats(&render_stats);
211 let report = monitor.get_gpu_report().unwrap();
212
213 assert_eq!(report.frame_number, 1);
215
216 assert_eq!(report.frame_total_duration_us(), Some(17000)); assert_eq!(report.main_pass_duration_us(), Some(16670)); }
220
221 #[test]
222 fn gpu_report_missing_data() {
223 let monitor = GpuMonitor::new("TestGPU".to_string());
224
225 let render_stats = RenderStats {
226 frame_number: 1,
227 cpu_preparation_time_ms: 0.0,
228 cpu_render_submission_time_ms: 0.0,
229 gpu_main_pass_time_ms: 0.0,
230 gpu_frame_total_time_ms: 0.0,
231 draw_calls: 0,
232 triangles_rendered: 0,
233 vram_usage_estimate_mb: 0.0,
234 };
235
236 monitor.update_from_frame_stats(&render_stats);
237 let report = monitor.get_gpu_report().unwrap();
238
239 assert_eq!(report.frame_total_duration_us(), Some(0)); assert_eq!(report.main_pass_duration_us(), Some(0)); }
243}