Skip to main content

hotpath_meta/
shared.rs

1use std::str::FromStr;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4pub enum Section {
5    FunctionsTiming,
6    FunctionsAlloc,
7    FunctionsCpu,
8    Channels,
9    Streams,
10    Futures,
11    RwLocks,
12    Mutexes,
13    Sql,
14    Http,
15    Server,
16    Io,
17    Threads,
18    Debug,
19}
20
21impl Section {
22    pub fn all() -> Vec<Section> {
23        vec![
24            Section::FunctionsTiming,
25            Section::FunctionsAlloc,
26            Section::FunctionsCpu,
27            Section::Channels,
28            Section::Streams,
29            Section::Futures,
30            Section::RwLocks,
31            Section::Mutexes,
32            Section::Sql,
33            Section::Http,
34            Section::Server,
35            Section::Io,
36            Section::Threads,
37            Section::Debug,
38        ]
39    }
40
41    pub fn short_name(&self) -> &'static str {
42        match self {
43            Section::FunctionsTiming => "timing",
44            Section::FunctionsAlloc => "alloc",
45            Section::FunctionsCpu => "cpu",
46            Section::Channels => "channels",
47            Section::Streams => "streams",
48            Section::Futures => "futures",
49            Section::RwLocks => "rw_locks",
50            Section::Mutexes => "mutexes",
51            Section::Sql => "sql",
52            Section::Http => "http",
53            Section::Server => "server",
54            Section::Io => "io",
55            Section::Threads => "threads",
56            Section::Debug => "debug",
57        }
58    }
59
60    #[cfg(feature = "hotpath-meta")]
61    pub(crate) fn from_name(s: &str) -> Option<Section> {
62        match s.trim() {
63            "functions-timing" => Some(Section::FunctionsTiming),
64            "functions-alloc" => Some(Section::FunctionsAlloc),
65            "functions-cpu" => Some(Section::FunctionsCpu),
66            "channels" => Some(Section::Channels),
67            "streams" => Some(Section::Streams),
68            "futures" => Some(Section::Futures),
69            "rw_locks" => Some(Section::RwLocks),
70            "mutexes" => Some(Section::Mutexes),
71            "sql" => Some(Section::Sql),
72            "http" => Some(Section::Http),
73            "server" => Some(Section::Server),
74            "io" => Some(Section::Io),
75            "threads" => Some(Section::Threads),
76            "debug" => Some(Section::Debug),
77            _ => None,
78        }
79    }
80}
81
82/// How the set of report sections is determined.
83#[cfg(feature = "hotpath-meta")]
84#[derive(Clone, Debug, PartialEq, Eq)]
85pub(crate) enum SectionsMode {
86    /// Base sections (functions, threads) plus every data-driven section
87    /// (channels, streams, futures, rw_locks, mutexes, sql, http, server, io, debug)
88    /// that has data at shutdown. `include` forces sections in, `exclude`
89    /// hides them.
90    Auto {
91        include: Vec<Section>,
92        exclude: Vec<Section>,
93    },
94    /// Exactly these sections, nothing else.
95    Explicit(Vec<Section>),
96}
97
98#[cfg(feature = "hotpath-meta")]
99impl Default for SectionsMode {
100    fn default() -> Self {
101        SectionsMode::Auto {
102            include: Vec::new(),
103            exclude: Vec::new(),
104        }
105    }
106}
107
108#[cfg(feature = "hotpath-meta")]
109impl SectionsMode {
110    /// Parses a report spec: `all`, `auto`, section names, and `-section`
111    /// exclusions, comma-separated. A spec containing `auto` or any `-section`
112    /// token resolves to [`SectionsMode::Auto`]; a plain list of names is
113    /// [`SectionsMode::Explicit`]; `all` is every section minus exclusions.
114    pub(crate) fn parse(spec: &str) -> SectionsMode {
115        let mut auto = false;
116        let mut all = false;
117        let mut include = Vec::new();
118        let mut exclude = Vec::new();
119
120        for part in spec.split(',') {
121            let part = part.trim();
122            match part {
123                "all" => all = true,
124                "auto" => auto = true,
125                _ => {
126                    let (target, name) = match part.strip_prefix('-') {
127                        Some(rest) => (&mut exclude, rest.trim()),
128                        None => (&mut include, part),
129                    };
130                    if let Some(s) = Section::from_name(name) {
131                        if !target.contains(&s) {
132                            target.push(s);
133                        }
134                    } else {
135                        eprintln!("[hotpath-meta] Unknown report section: '{}'", part);
136                    }
137                }
138            }
139        }
140
141        if all {
142            SectionsMode::Explicit(
143                Section::all()
144                    .into_iter()
145                    .filter(|s| !exclude.contains(s))
146                    .collect(),
147            )
148        } else if auto || !exclude.is_empty() {
149            SectionsMode::Auto { include, exclude }
150        } else {
151            SectionsMode::Explicit(include)
152        }
153    }
154
155    pub(crate) fn from_env() -> Option<SectionsMode> {
156        std::env::var("HOTPATH_META_REPORT")
157            .ok()
158            .map(|val| SectionsMode::parse(&val))
159    }
160
161    /// Whether the section is requested by name (explicit list or a forced
162    /// auto include). Auto mode does not imply it - used for build-time
163    /// side effects that should stay lazy under auto.
164    pub(crate) fn explicitly_contains(&self, section: Section) -> bool {
165        match self {
166            SectionsMode::Explicit(list) => list.contains(&section),
167            SectionsMode::Auto { include, .. } => include.contains(&section),
168        }
169    }
170
171    /// Whether the section is part of the report set decidable before any
172    /// data exists: listed explicitly, or in auto mode and not excluded.
173    #[cfg(feature = "hotpath-cpu-meta")]
174    pub(crate) fn contains_or_auto(&self, section: Section) -> bool {
175        match self {
176            SectionsMode::Explicit(list) => list.contains(&section),
177            SectionsMode::Auto { include, exclude } => {
178                include.contains(&section) || !exclude.contains(&section)
179            }
180        }
181    }
182}
183
184/// Output format for the profiling report printed when the program exits.
185///
186/// # Variants
187///
188/// * `Table` - Human-readable table format (default)
189/// * `Json` - JSON format
190/// * `JsonPretty` - Pretty-printed JSON format
191/// * `None` - Suppress all profiling output (metrics server and MCP server still function)
192///
193/// # Parsing
194///
195/// Can be parsed from strings via `HOTPATH_META_OUTPUT_FORMAT` environment variable:
196/// - `"table"` → `Format::Table`
197/// - `"json"` → `Format::Json`
198/// - `"json-pretty"` → `Format::JsonPretty`
199/// - `"none"` → `Format::None`
200#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
201pub enum Format {
202    #[default]
203    Table,
204    Json,
205    JsonPretty,
206    None,
207}
208
209impl FromStr for Format {
210    type Err = String;
211
212    fn from_str(s: &str) -> Result<Self, Self::Err> {
213        match s.to_lowercase().as_str() {
214            "table" => Ok(Format::Table),
215            "json" => Ok(Format::Json),
216            "json-pretty" | "jsonpretty" => Ok(Format::JsonPretty),
217            "none" => Ok(Format::None),
218            _ => Err(format!(
219                "unknown format '{}', expected: table, json, json-pretty, none",
220                s
221            )),
222        }
223    }
224}
225
226impl Format {
227    /// Returns the format from `HOTPATH_META_OUTPUT_FORMAT` env var, or default if not set.
228    /// Panics if the env var contains an invalid value.
229    #[cfg(feature = "hotpath-meta")]
230    pub(crate) fn from_env() -> Self {
231        match std::env::var("HOTPATH_META_OUTPUT_FORMAT") {
232            Ok(v) => v
233                .parse()
234                .unwrap_or_else(|e| panic!("HOTPATH_META_OUTPUT_FORMAT: {}", e)),
235            Err(_) => Format::default(),
236        }
237    }
238}
239
240pub trait IntoF64 {
241    fn into_f64(self) -> f64;
242}
243
244impl IntoF64 for f64 {
245    fn into_f64(self) -> f64 {
246        self
247    }
248}
249
250impl IntoF64 for f32 {
251    fn into_f64(self) -> f64 {
252        self as f64
253    }
254}
255
256impl IntoF64 for i8 {
257    fn into_f64(self) -> f64 {
258        self as f64
259    }
260}
261
262impl IntoF64 for i16 {
263    fn into_f64(self) -> f64 {
264        self as f64
265    }
266}
267
268impl IntoF64 for i32 {
269    fn into_f64(self) -> f64 {
270        self as f64
271    }
272}
273
274impl IntoF64 for i64 {
275    fn into_f64(self) -> f64 {
276        self as f64
277    }
278}
279
280impl IntoF64 for u8 {
281    fn into_f64(self) -> f64 {
282        self as f64
283    }
284}
285
286impl IntoF64 for u16 {
287    fn into_f64(self) -> f64 {
288        self as f64
289    }
290}
291
292impl IntoF64 for u32 {
293    fn into_f64(self) -> f64 {
294        self as f64
295    }
296}
297
298impl IntoF64 for u64 {
299    fn into_f64(self) -> f64 {
300        self as f64
301    }
302}
303
304impl IntoF64 for isize {
305    fn into_f64(self) -> f64 {
306        self as f64
307    }
308}
309
310impl IntoF64 for usize {
311    fn into_f64(self) -> f64 {
312        self as f64
313    }
314}
315
316#[doc(hidden)]
317pub fn env_flag(name: &str) -> bool {
318    std::env::var(name)
319        .map(|v| v.eq_ignore_ascii_case("true") || v == "1")
320        .unwrap_or(false)
321}
322
323#[cfg(feature = "hotpath-meta")]
324pub(crate) fn resolve_timeout_duration(
325    default_duration: std::time::Duration,
326    env_var: &str,
327) -> Option<std::time::Duration> {
328    let effective_duration = std::env::var(env_var)
329        .ok()
330        .and_then(|value| value.parse::<u64>().ok())
331        .map(std::time::Duration::from_millis)
332        .unwrap_or(default_duration);
333
334    if effective_duration.is_zero() {
335        None
336    } else {
337        Some(effective_duration)
338    }
339}