Skip to main content

flashraw_core/
profile.rs

1//! Lightweight stage profiling and a portable clock.
2//!
3//! `std::time::Instant` is unavailable on `wasm32-unknown-unknown`; the WASM
4//! bindings install a `performance.now()`-backed clock via [`set_clock`].
5
6use std::sync::Mutex;
7use std::sync::atomic::{AtomicUsize, Ordering};
8
9/// Decode pipeline stages reported by `--profile` and `DecodeStatistics`.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12#[non_exhaustive]
13pub enum Stage {
14    /// Container structure (TIFF/BMFF/vendor headers).
15    ContainerParse,
16    /// EXIF, maker notes, camera database lookup.
17    Metadata,
18    /// Huffman / arithmetic / wavelet entropy decoding.
19    EntropyDecode,
20    /// Unpacking of bit-packed uncompressed samples.
21    Unpack,
22    /// Predictor / delta reconstruction when separable from entropy decoding.
23    Predictor,
24    /// Post-decode calibration applied in the decoder (linearisation curves, ...).
25    Calibration,
26    /// Optional processing after decoding (black subtraction, demosaic, colour).
27    Postprocess,
28    /// Host → GPU transfer.
29    GpuUpload,
30    /// GPU compute dispatch.
31    GpuCompute,
32    /// GPU → host transfer.
33    GpuDownload,
34    /// Embedded preview extraction.
35    Preview,
36}
37
38impl Stage {
39    /// Human-readable stage name (e.g. `"Entropy decode"`).
40    pub fn label(self) -> &'static str {
41        match self {
42            Stage::ContainerParse => "Container parse",
43            Stage::Metadata => "Metadata",
44            Stage::EntropyDecode => "Entropy decode",
45            Stage::Unpack => "Pixel unpack",
46            Stage::Predictor => "Pixel reconstruction",
47            Stage::Calibration => "Calibration",
48            Stage::Postprocess => "Post-process",
49            Stage::GpuUpload => "GPU upload",
50            Stage::GpuCompute => "GPU compute",
51            Stage::GpuDownload => "GPU download",
52            Stage::Preview => "Preview",
53        }
54    }
55}
56
57static CLOCK: AtomicUsize = AtomicUsize::new(0);
58
59/// Install a millisecond clock (used on targets without `Instant`).
60pub fn set_clock(f: fn() -> f64) {
61    CLOCK.store(f as usize, Ordering::Relaxed);
62}
63
64/// Monotonic milliseconds from an arbitrary origin.
65pub fn now_ms() -> f64 {
66    let p = CLOCK.load(Ordering::Relaxed);
67    if p != 0 {
68        // SAFETY: the only non-zero values ever stored are `fn() -> f64` pointers
69        // written by `set_clock`; function pointers round-trip through usize.
70        let f: fn() -> f64 = unsafe { std::mem::transmute::<usize, fn() -> f64>(p) };
71        return f();
72    }
73    default_clock()
74}
75
76#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
77fn default_clock() -> f64 {
78    use std::sync::OnceLock;
79    use std::time::Instant;
80    static START: OnceLock<Instant> = OnceLock::new();
81    START.get_or_init(Instant::now).elapsed().as_secs_f64() * 1000.0
82}
83
84#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
85fn default_clock() -> f64 {
86    0.0
87}
88
89/// Collects per-stage wall-clock durations. Cheap enough to be always on: a handful
90/// of entries per decode, recorded from whichever thread runs the stage.
91#[derive(Debug, Default)]
92pub struct Profiler {
93    entries: Mutex<Vec<(Stage, f64)>>,
94}
95
96impl Profiler {
97    /// An empty profiler.
98    pub fn new() -> Self {
99        Self::default()
100    }
101    /// Add `ms` to `stage`.
102    pub fn record(&self, stage: Stage, ms: f64) {
103        if let Ok(mut e) = self.entries.lock() {
104            if let Some(x) = e.iter_mut().find(|(s, _)| *s == stage) {
105                x.1 += ms;
106            } else {
107                e.push((stage, ms));
108            }
109        }
110    }
111    /// Time `f` under `stage`.
112    pub fn time<T>(&self, stage: Stage, f: impl FnOnce() -> T) -> T {
113        let t = now_ms();
114        let r = f();
115        self.record(stage, now_ms() - t);
116        r
117    }
118    /// Snapshot of `(stage, milliseconds)` sorted by pipeline order.
119    pub fn stages(&self) -> Vec<(Stage, f64)> {
120        let mut v = self.entries.lock().map(|e| e.clone()).unwrap_or_default();
121        v.sort_by_key(|(s, _)| *s);
122        v
123    }
124}