use std::time::Duration;
use std::time::Instant;
use crate::batch::GpuBatchPlan;
use crate::config::{ExecutionMode, TelemetrySink};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TelemetryPhase {
Plan,
Encode,
Gpu,
Readback,
Decode,
CpuFallback,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct TelemetryReport {
pub label: String,
pub execution_mode: Option<ExecutionMode>,
pub bytes_in: usize,
pub bytes_out: usize,
pub batch_count: usize,
pub plan_time: Duration,
pub encode_time: Duration,
pub gpu_time: Duration,
pub readback_time: Duration,
pub decode_time: Duration,
pub cpu_fallback_time: Duration,
}
impl TelemetryReport {
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
..Self::default()
}
}
pub fn add_elapsed(&mut self, phase: TelemetryPhase, elapsed: Duration) {
match phase {
TelemetryPhase::Plan => self.plan_time += elapsed,
TelemetryPhase::Encode => self.encode_time += elapsed,
TelemetryPhase::Gpu => self.gpu_time += elapsed,
TelemetryPhase::Readback => self.readback_time += elapsed,
TelemetryPhase::Decode => self.decode_time += elapsed,
TelemetryPhase::CpuFallback => self.cpu_fallback_time += elapsed,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GpuPhase {
Pack,
Gpu,
Readback,
Post,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct GpuTelemetry {
pub pack_ms: f64,
pub gpu_ms: f64,
pub readback_ms: f64,
pub post_ms: f64,
pub bytes_in: usize,
pub bytes_out: usize,
pub batch_count: usize,
}
impl GpuTelemetry {
pub fn record_plan(&mut self, plan: &GpuBatchPlan) {
self.bytes_in = self.bytes_in.saturating_add(plan.bytes_in());
self.bytes_out = self.bytes_out.saturating_add(plan.bytes_out());
self.batch_count = self.batch_count.saturating_add(plan.batch_count());
}
pub fn add_elapsed_ms(&mut self, phase: GpuPhase, elapsed_ms: f64) {
match phase {
GpuPhase::Pack => self.pack_ms += elapsed_ms,
GpuPhase::Gpu => self.gpu_ms += elapsed_ms,
GpuPhase::Readback => self.readback_ms += elapsed_ms,
GpuPhase::Post => self.post_ms += elapsed_ms,
}
}
pub fn merge(&mut self, other: &Self) {
self.pack_ms += other.pack_ms;
self.gpu_ms += other.gpu_ms;
self.readback_ms += other.readback_ms;
self.post_ms += other.post_ms;
self.bytes_in = self.bytes_in.saturating_add(other.bytes_in);
self.bytes_out = self.bytes_out.saturating_add(other.bytes_out);
self.batch_count = self.batch_count.saturating_add(other.batch_count);
}
pub fn log_line(&self, label: impl AsRef<str>) -> String {
format!(
"{} pack_ms={:.3} gpu_ms={:.3} readback_ms={:.3} post_ms={:.3} bytes_in={} bytes_out={} batch_count={}",
label.as_ref(),
self.pack_ms,
self.gpu_ms,
self.readback_ms,
self.post_ms,
self.bytes_in,
self.bytes_out,
self.batch_count
)
}
}
pub fn measure_phase<T>(
telemetry: &mut GpuTelemetry,
phase: GpuPhase,
work: impl FnOnce() -> T,
) -> T {
let started = Instant::now();
let result = work();
telemetry.add_elapsed_ms(phase, started.elapsed().as_secs_f64() * 1000.0);
result
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct TelemetryCollector {
reports: Vec<TelemetryReport>,
}
impl TelemetryCollector {
pub fn record(&mut self, report: TelemetryReport, sink: &TelemetrySink) {
match sink {
TelemetrySink::Log => {}
TelemetrySink::Callback(callback) => callback(&report),
TelemetrySink::Buffer(sender) => {
let _ = sender.send(report.clone());
}
}
self.reports.push(report);
}
pub fn latest(&self) -> Option<&TelemetryReport> {
self.reports.last()
}
pub fn reports(&self) -> &[TelemetryReport] {
&self.reports
}
pub fn clear(&mut self) {
self.reports.clear();
}
}