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}