Skip to main content

euv_ui/hook/profiler/
struct.rs

1use super::*;
2
3/// A single recorded measurement.
4///
5/// Pushed onto the `ProfilerHandle`'s entries signal every time
6/// the user calls `ProfilerHandle::measure(label, f)` (or
7/// manually `begin` / `end`). Cheap to `Clone` (the inner
8/// strings are small and `f64` is `Copy`).
9///
10/// Field-level semantics:
11///
12/// - `label`: free-form identifier — typically the call site name
13///   (`"render-list"`, `"fetch-posts"`). Empty strings are
14///   allowed but render as an empty chip in the UI, which makes
15///   misconfigured measurements obvious in a profiler readout.
16/// - `elapsed_ms`: wall-clock time between `begin()` and the
17///   matching `end()` (or the duration of the measured closure),
18///   in milliseconds. Always `>= 0.0` — `begin` is captured
19///   before any user code runs, so the subtraction cannot
20///   underflow.
21/// - `timestamp_ms`: the wall-clock `now_ms()` value at the
22///   instant the entry was recorded (NOT the start of the
23///   measurement). This lets the UI sort / filter entries by
24///   when they were committed, not by when the user started
25///   the timer — which matters when entries are kept around
26///   for "last N measurements" readouts.
27#[derive(Clone, Data, Debug, New, PartialEq)]
28pub struct ProfileEntry {
29    /// The free-form label passed to `measure` / `begin`.
30    pub label: String,
31    /// Duration of the measured operation, in milliseconds.
32    #[get(type(copy))]
33    pub elapsed_ms: f64,
34    /// Wall-clock time at which the entry was recorded.
35    #[get(type(copy))]
36    pub timestamp_ms: f64,
37}
38
39/// A handle to the profiler registered against the current
40/// hook context.
41///
42/// The handle owns the entries signal; calling
43/// `ProfilerHandle::entries()` returns that signal so any
44/// reactive read (`Signal::get()`) inside a closure subscribes
45/// the enclosing render to new entries. The matching
46/// measurement API is `ProfilerHandle::measure(label, f)`
47/// (push-on-exit) or `ProfilerHandle::begin(label)` /
48/// `ProfilerHandle::end()` (split-timer API for code paths
49/// that don't fit inside a single closure).
50///
51/// # Lifecycle
52///
53/// The handle is obtained via `App::use_profiler()` (or
54/// directly via `HookContext::profiler()`), which slots it into
55/// the current hook context. On every render at the same hook
56/// index, the same handle is returned — so measurements
57/// recorded from a previous render remain visible in
58/// `entries()`.
59///
60/// On hook-context teardown (component unmount, match-arm
61/// switch, or explicit `clear()`), the handle is dropped and
62/// its entries signal goes with it. If you need to keep
63/// measurements alive past the lifetime of the component,
64/// clone the entries vector out before the context is cleared.
65#[derive(Clone, Data, New)]
66pub struct ProfilerHandle {
67    /// The reactive log of measurements. Every measurement
68    /// pushes a fresh `ProfileEntry` into this vector via
69    /// `.set(...)` — the `set` triggers the reactive update
70    /// path, so any subscriber re-renders.
71    pub(crate) entries: Signal<Vec<ProfileEntry>>,
72}
73
74/// `ProfilerHandle` is `Copy` because `Signal<Vec<ProfileEntry>>`
75/// is itself `Copy` (the registry hands out cheap `usize`
76/// addresses; the vector lives in the global signal store).
77impl Copy for ProfilerHandle {}
78
79/// A `begin()` marker — RAII guard that records the start
80/// timestamp and the label so the matching `end()` call can
81/// compute the elapsed time.
82///
83/// Created by `ProfilerHandle::begin(label)`. Consume with
84/// `end()` to push a `ProfileEntry` into the entries signal.
85/// Dropping the marker without calling `end()` discards the
86/// measurement silently (we don't have a place to push a
87/// half-finished entry, and panicking on drop is hostile).
88#[derive(Data, New)]
89pub struct ProfilerMark {
90    /// The label this marker was created with. Copied out of
91    /// the `&str` at construction time so the marker does not
92    /// outlive any borrowed string.
93    pub(crate) label: String,
94    /// Wall-clock timestamp captured at `begin()`. Subtracted
95    /// from the `end()` timestamp to compute `elapsed_ms`.
96    pub(crate) started_ms: f64,
97    /// Back-reference to the entries signal. Cloned (cheap —
98    /// `Signal<T>` is `Copy`-by-pointer) so `end()` can push
99    /// without re-borrowing the handle.
100    pub(crate) entries: Signal<Vec<ProfileEntry>>,
101}