Skip to main content

lumen_ffmpeg/encode/
telemetry.rs

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}