1use std::time::Duration;
2
3use crate::gpu::{GpuBackend, GpuVideoInput};
4
5#[derive(Debug, Clone, Default, PartialEq, Eq)]
6pub struct GpuEncodeTelemetry {
7 pub upload_attempts: u64,
8 pub upload_successes: u64,
9 pub upload_failures: u64,
10 pub encode_attempts: u64,
11 pub encode_successes: u64,
12 pub encode_failures: u64,
13 pub cuda_frames: u64,
14 pub metal_frames: u64,
15 pub vulkan_frames: u64,
16 pub estimated_upload_bytes: u64,
17 pub upload_time_us: u128,
18 pub encode_time_us: u128,
19 pub last_error: Option<String>,
20 pub recent_events: Vec<GpuEncodeEvent>,
21}
22
23impl GpuEncodeTelemetry {
24 const MAX_RECENT_EVENTS: usize = 128;
25
26 pub fn record_upload_started(&mut self, descriptor: &GpuUploadDescriptor) {
27 self.upload_attempts = self.upload_attempts.saturating_add(1);
28 self.estimated_upload_bytes = self
29 .estimated_upload_bytes
30 .saturating_add(descriptor.estimated_bytes);
31 match descriptor.backend {
32 GpuBackend::Cuda => self.cuda_frames = self.cuda_frames.saturating_add(1),
33 GpuBackend::Metal => self.metal_frames = self.metal_frames.saturating_add(1),
34 GpuBackend::Vulkan => self.vulkan_frames = self.vulkan_frames.saturating_add(1),
35 }
36 self.push_event(GpuEncodeEvent::started(GpuEncodeStage::Upload, descriptor));
37 }
38
39 pub fn record_upload_finished(&mut self, descriptor: &GpuUploadDescriptor, elapsed: Duration) {
40 self.upload_successes = self.upload_successes.saturating_add(1);
41 self.upload_time_us = self.upload_time_us.saturating_add(elapsed.as_micros());
42 self.push_event(GpuEncodeEvent::finished(
43 GpuEncodeStage::Upload,
44 descriptor,
45 elapsed,
46 ));
47 }
48
49 pub fn record_upload_failed(
50 &mut self,
51 descriptor: &GpuUploadDescriptor,
52 elapsed: Duration,
53 message: impl Into<String>,
54 ) {
55 let message = message.into();
56 self.upload_failures = self.upload_failures.saturating_add(1);
57 self.upload_time_us = self.upload_time_us.saturating_add(elapsed.as_micros());
58 self.last_error = Some(message.clone());
59 self.push_event(GpuEncodeEvent::failed(
60 GpuEncodeStage::Upload,
61 descriptor,
62 elapsed,
63 message,
64 ));
65 }
66
67 pub fn record_encode_started(&mut self, descriptor: &GpuUploadDescriptor) {
68 self.encode_attempts = self.encode_attempts.saturating_add(1);
69 self.push_event(GpuEncodeEvent::started(GpuEncodeStage::Encode, descriptor));
70 }
71
72 pub fn record_encode_finished(&mut self, descriptor: &GpuUploadDescriptor, elapsed: Duration) {
73 self.encode_successes = self.encode_successes.saturating_add(1);
74 self.encode_time_us = self.encode_time_us.saturating_add(elapsed.as_micros());
75 self.push_event(GpuEncodeEvent::finished(
76 GpuEncodeStage::Encode,
77 descriptor,
78 elapsed,
79 ));
80 }
81
82 pub fn record_encode_failed(
83 &mut self,
84 descriptor: &GpuUploadDescriptor,
85 elapsed: Duration,
86 message: impl Into<String>,
87 ) {
88 let message = message.into();
89 self.encode_failures = self.encode_failures.saturating_add(1);
90 self.encode_time_us = self.encode_time_us.saturating_add(elapsed.as_micros());
91 self.last_error = Some(message.clone());
92 self.push_event(GpuEncodeEvent::failed(
93 GpuEncodeStage::Encode,
94 descriptor,
95 elapsed,
96 message,
97 ));
98 }
99
100 fn push_event(&mut self, event: GpuEncodeEvent) {
101 if self.recent_events.len() == Self::MAX_RECENT_EVENTS {
102 self.recent_events.remove(0);
103 }
104 self.recent_events.push(event);
105 }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub struct GpuUploadDescriptor {
110 pub backend: GpuBackend,
111 pub width: u32,
112 pub height: u32,
113 pub estimated_bytes: u64,
114}
115
116impl GpuUploadDescriptor {
117 pub fn from_frame(frame: &GpuVideoInput<'_>) -> Self {
118 let (width, height) = frame.dimensions();
119 Self {
120 backend: frame.backend(),
121 width,
122 height,
123 estimated_bytes: frame.estimated_rgba_bytes(),
124 }
125 }
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum GpuEncodeStage {
130 Upload,
131 Encode,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct GpuEncodeEvent {
136 pub stage: GpuEncodeStage,
137 pub outcome: GpuEncodeOutcome,
138 pub backend: GpuBackend,
139 pub width: u32,
140 pub height: u32,
141 pub estimated_bytes: u64,
142 pub elapsed_us: Option<u128>,
143 pub message: Option<String>,
144}
145
146impl GpuEncodeEvent {
147 fn started(stage: GpuEncodeStage, descriptor: &GpuUploadDescriptor) -> Self {
148 Self::new(stage, GpuEncodeOutcome::Started, descriptor, None, None)
149 }
150
151 fn finished(
152 stage: GpuEncodeStage,
153 descriptor: &GpuUploadDescriptor,
154 elapsed: Duration,
155 ) -> Self {
156 Self::new(
157 stage,
158 GpuEncodeOutcome::Finished,
159 descriptor,
160 Some(elapsed),
161 None,
162 )
163 }
164
165 fn failed(
166 stage: GpuEncodeStage,
167 descriptor: &GpuUploadDescriptor,
168 elapsed: Duration,
169 message: String,
170 ) -> Self {
171 Self::new(
172 stage,
173 GpuEncodeOutcome::Failed,
174 descriptor,
175 Some(elapsed),
176 Some(message),
177 )
178 }
179
180 fn new(
181 stage: GpuEncodeStage,
182 outcome: GpuEncodeOutcome,
183 descriptor: &GpuUploadDescriptor,
184 elapsed: Option<Duration>,
185 message: Option<String>,
186 ) -> Self {
187 Self {
188 stage,
189 outcome,
190 backend: descriptor.backend,
191 width: descriptor.width,
192 height: descriptor.height,
193 estimated_bytes: descriptor.estimated_bytes,
194 elapsed_us: elapsed.map(|duration| duration.as_micros()),
195 message,
196 }
197 }
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub enum GpuEncodeOutcome {
202 Started,
203 Finished,
204 Failed,
205}