Skip to main content

euv_core/reactive/profiler/
impl.rs

1use super::*;
2
3impl ProfilerHandle {
4    /// Records a fresh measurement around the given closure.
5    ///
6    /// Captures the start timestamp, runs `f`, captures the
7    /// end timestamp, and pushes a `ProfileEntry { label,
8    /// elapsed_ms, timestamp_ms }` into the entries signal.
9    /// The `elapsed_ms` is `>= 0.0` by construction (start is
10    /// always captured before `f` runs).
11    ///
12    /// # Arguments
13    ///
14    /// - `&str` - The label for this measurement.
15    /// - `F: FnOnce() -> R` - The closure to measure. Can
16    ///   return any type — the return value is forwarded to
17    ///   the caller unchanged.
18    ///
19    /// # Returns
20    ///
21    /// - `R` - Whatever `f` returned.
22    pub fn measure<F, R>(&self, label: &str, f: F) -> R
23    where
24        F: FnOnce() -> R,
25    {
26        let started_ms: f64 = now_ms();
27        let result: R = f();
28        let ended_ms: f64 = now_ms();
29        let entry: ProfileEntry =
30            ProfileEntry::new(label.to_string(), ended_ms - started_ms, ended_ms);
31        // Push the entry onto the existing entries vector.
32        // Read-modify-write via `.get()` is necessary because
33        // `Signal<T>::set` requires `T: Clone` (which
34        // `Vec<ProfileEntry>` is) but does NOT take `&mut T`
35        // — the entire new value is supplied as the argument.
36        let mut current: Vec<ProfileEntry> = self.get_entries().get();
37        current.push(entry);
38        self.get_entries().set(current);
39        result
40    }
41
42    /// Starts a measurement that will end later.
43    ///
44    /// Use this when the measured region is not a single
45    /// closure — e.g. you want to bracket an async operation
46    /// or a callback fired from event handling. The returned
47    /// `ProfilerMark` knows the start timestamp and the
48    /// entries signal; pass it to `end()` when the work
49    /// completes.
50    ///
51    /// # Arguments
52    ///
53    /// - `&str` - The label for this measurement.
54    ///
55    /// # Returns
56    ///
57    /// - `ProfilerMark` - An RAII-ish guard. Call `mark.end()`
58    ///   to push the `ProfileEntry`; drop without `end()` to
59    ///   discard the measurement.
60    pub fn begin(&self, label: &str) -> ProfilerMark {
61        ProfilerMark::new(label.to_string(), now_ms(), self.get_entries().clone())
62    }
63
64    /// Returns a clone of the entries signal so subscribers
65    /// outside this handle can react to new measurements.
66    ///
67    /// `Signal<T>` is `Copy`-by-pointer (the inner state is
68    /// heap-allocated and the handle stores a raw address),
69    /// so cloning is cheap. Subscribers call `.get()` inside
70    /// their render closure to subscribe to new entries.
71    pub fn entries(&self) -> Signal<Vec<ProfileEntry>> {
72        self.get_entries().clone()
73    }
74
75    /// Empties the entries vector. Useful between benchmarks
76    /// ("measure just this call, not the previous ones too")
77    /// and in tests ("start from a clean slate").
78    pub fn clear(&self) {
79        self.get_entries().set(Vec::new());
80    }
81}
82
83impl ProfilerMark {
84    /// Closes the measurement started by `begin()` and pushes
85    /// the resulting `ProfileEntry` into the entries signal.
86    ///
87    /// After calling `end()`, the marker is consumed and
88    /// cannot be reused. Calling `end()` twice is a no-op on
89    /// the second call — the marker is moved into the first
90    /// call, so the borrow checker prevents a second
91    /// invocation in well-typed code.
92    pub fn end(self) {
93        let ended_ms: f64 = now_ms();
94        let entry: ProfileEntry = ProfileEntry::new(
95            self.get_label().clone(),
96            ended_ms - self.get_started_ms(),
97            ended_ms,
98        );
99        let mut current: Vec<ProfileEntry> = self.get_entries().get();
100        current.push(entry);
101        self.get_entries().set(current);
102    }
103}