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 ///
72 /// Public because `ui/tests/profiler/fn.rs`,
73 /// `ui/tests/hook_traits/fn.rs`, `ui/tests/hook_i18n/fn.rs`
74 /// and `example/src/page/hooks_protect` read it through
75 /// `get_entries()`.
76 pub entries: Signal<Vec<ProfileEntry>>,
77}
78/// A `begin()` marker — RAII guard that records the start
79/// timestamp and the label so the matching `end()` call can
80/// compute the elapsed time.
81///
82/// Created by `ProfilerHandle::begin(label)`. Consume with
83/// `end()` to push a `ProfileEntry` into the entries signal.
84/// Dropping the marker without calling `end()` discards the
85/// measurement silently (we don't have a place to push a
86/// half-finished entry, and panicking on drop is hostile).
87#[derive(Data, New)]
88pub struct ProfilerMark {
89 /// The label this marker was created with. Copied out of
90 /// the `&str` at construction time so the marker does not
91 /// outlive any borrowed string.
92 ///
93 /// `get_label()` is only called by `impl.rs` while building
94 /// the `ProfileEntry`, so the three generated accessors are
95 /// all `pub(crate)`.
96 #[get(pub(crate))]
97 #[get_mut(pub(crate))]
98 #[set(pub(crate))]
99 pub(crate) label: String,
100 /// Wall-clock timestamp captured at `begin()`. Subtracted
101 /// from the `end()` timestamp to compute `elapsed_ms`.
102 ///
103 /// `get_started_ms()` is only called by `impl.rs`, so the
104 /// three generated accessors are all `pub(crate)`.
105 #[get(type(copy), pub(crate))]
106 #[get_mut(pub(crate))]
107 #[set(pub(crate))]
108 pub(crate) started_ms: f64,
109 /// Back-reference to the entries signal. Cloned (cheap —
110 /// `Signal<T>` is `Copy`-by-pointer) so `end()` can push
111 /// without re-borrowing the handle.
112 ///
113 /// Only `impl.rs` pushes through it, so the three
114 /// generated accessors are all `pub(crate)`.
115 #[get(pub(crate))]
116 #[get_mut(pub(crate))]
117 #[set(pub(crate))]
118 pub(crate) entries: Signal<Vec<ProfileEntry>>,
119}