Skip to main content

keyhog_profile/
detail.rs

1//! The one switch that decides whether performance measurement runs.
2//!
3//! Every crate in the workspace asks this module, and only this module, whether
4//! to measure. Before this existed the scanner kept two private process-wide
5//! atomics (`DETAILED_ENABLED` and `PERF_TRACE_ENABLED`), the CLI kept two
6//! config booleans (`scanner.profile` and `scanner.perf_trace`), and each
7//! subsystem decided for itself which one to read. They were always written
8//! from the same source, so they were one decision wearing four hats.
9//!
10//! ```
11//! use keyhog_profile::{detail, set_detail, Detail};
12//!
13//! set_detail(Detail::Diagnostic);
14//! assert!(detail().is_diagnostic());
15//! assert!(detail().records_stages());
16//!
17//! set_detail(Detail::Off);
18//! assert!(!detail().records_stages());
19//! ```
20//!
21//! # Levels are ordered
22//!
23//! `Off < Stages < Diagnostic`. A caller that wants stage timing asks
24//! [`Detail::records_stages`]. A caller that wants the expensive per-pattern and
25//! per-backend decomposition asks [`Detail::is_diagnostic`]. Nothing else is a
26//! valid question, because nothing else is a level.
27//!
28//! # Cost when off
29//!
30//! [`detail`] is one relaxed load of a `u8` and no clock read. The hot-path
31//! pattern is `if detail().is_diagnostic()` guarding the timed region, so a
32//! disabled build takes a predictable never-taken branch and never constructs an
33//! `Instant`.
34//!
35//! # Relationship to the runtime switch
36//!
37//! [`set_detail`] also drives [`crate::set_enabled`], so turning measurement on
38//! is a single call. [`crate::enabled`] stays the per-thread question ("is a
39//! profile runtime current here"), which is what a span guard needs.
40//! [`detail`] is the process-wide question ("was measurement requested at all"),
41//! which is what a caller needs before paying to construct a measurement.
42//! A [`crate::Session`] raises [`crate::enabled`] without raising [`detail`], so
43//! an operator `--profile` run records stages without paying diagnostic cost.
44
45use std::sync::atomic::{AtomicU8, Ordering::Relaxed};
46
47/// How much performance measurement this process performs.
48#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Hash)]
49#[repr(u8)]
50pub enum Detail {
51    /// Measure nothing. No clock is read on any hot path.
52    #[default]
53    Off = 0,
54    /// Record fixed stage spans, typed counters, and the causal record.
55    Stages = 1,
56    /// Everything in [`Detail::Stages`], plus the per-pattern, per-decoder and
57    /// per-backend decomposition that costs measurable hot-path time.
58    Diagnostic = 2,
59}
60
61impl Detail {
62    /// Stable text label used by human reports and config echoes.
63    pub const fn as_str(self) -> &'static str {
64        match self {
65            Self::Off => "off",
66            Self::Stages => "stages",
67            Self::Diagnostic => "diagnostic",
68        }
69    }
70
71    /// True when stage spans and typed counters should be recorded.
72    #[inline]
73    pub const fn records_stages(self) -> bool {
74        (self as u8) >= (Self::Stages as u8)
75    }
76
77    /// True when the expensive per-pattern and per-backend decomposition
78    /// should be recorded.
79    #[inline]
80    pub const fn is_diagnostic(self) -> bool {
81        (self as u8) >= (Self::Diagnostic as u8)
82    }
83}
84
85static DETAIL: AtomicU8 = AtomicU8::new(Detail::Off as u8);
86
87/// Return the measurement level requested for this process.
88///
89/// One relaxed atomic load. Safe to call on any hot path.
90#[inline]
91pub fn detail() -> Detail {
92    match DETAIL.load(Relaxed) {
93        1 => Detail::Stages,
94        2 => Detail::Diagnostic,
95        _ => Detail::Off,
96    }
97}
98
99/// Set the measurement level for this process and enable or disable the
100/// calling thread's standalone profiling runtime to match.
101///
102/// This is the only supported way for a caller outside this crate to turn
103/// measurement on.
104pub fn set_detail(detail: Detail) {
105    DETAIL.store(detail as u8, Relaxed);
106    crate::runtime::set_enabled(detail.records_stages());
107}
108
109#[cfg(test)]
110mod tests {
111    use super::{detail, set_detail, Detail};
112
113    /// The levels answer their two questions consistently, so a caller never
114    /// has to compare discriminants by hand.
115    #[test]
116    fn level_predicates_are_ordered() {
117        assert!(!Detail::Off.records_stages());
118        assert!(!Detail::Off.is_diagnostic());
119        assert!(Detail::Stages.records_stages());
120        assert!(!Detail::Stages.is_diagnostic());
121        assert!(Detail::Diagnostic.records_stages());
122        assert!(Detail::Diagnostic.is_diagnostic());
123        assert!(Detail::Off < Detail::Stages);
124        assert!(Detail::Stages < Detail::Diagnostic);
125    }
126
127    /// Setting the level also arms the runtime, so one call turns measurement
128    /// on. This is the property that lets every other crate delete its private
129    /// enable flag.
130    #[test]
131    fn setting_detail_arms_the_runtime() {
132        set_detail(Detail::Diagnostic);
133        assert_eq!(detail(), Detail::Diagnostic);
134        assert!(crate::enabled());
135
136        set_detail(Detail::Stages);
137        assert_eq!(detail(), Detail::Stages);
138        assert!(crate::enabled());
139
140        set_detail(Detail::Off);
141        assert_eq!(detail(), Detail::Off);
142        assert!(!crate::enabled());
143    }
144}