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 profiling reports.
185///
186/// This enum specifies how profiling results should be displayed when the program exits.
187///
188/// # Variants
189///
190/// * `Table` - Human-readable table format (default)
191/// * `Json` - JSON format
192/// * `JsonPretty` - Pretty-printed JSON format
193/// * `None` - Suppress all profiling output (metrics server and MCP server still function)
194///
195/// # Parsing
196///
197/// Can be parsed from strings via `HOTPATH_META_OUTPUT_FORMAT` environment variable:
198/// - `"table"` → `Format::Table`
199/// - `"json"` → `Format::Json`
200/// - `"json-pretty"` → `Format::JsonPretty`
201/// - `"none"` → `Format::None`
202#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
203pub enum Format {
204    #[default]
205    Table,
206    Json,
207    JsonPretty,
208    None,
209}
210
211impl FromStr for Format {
212    type Err = String;
213
214    fn from_str(s: &str) -> Result<Self, Self::Err> {
215        match s.to_lowercase().as_str() {
216            "table" => Ok(Format::Table),
217            "json" => Ok(Format::Json),
218            "json-pretty" | "jsonpretty" => Ok(Format::JsonPretty),
219            "none" => Ok(Format::None),
220            _ => Err(format!(
221                "unknown format '{}', expected: table, json, json-pretty, none",
222                s
223            )),
224        }
225    }
226}
227
228impl Format {
229    /// Returns the format from `HOTPATH_META_OUTPUT_FORMAT` env var, or default if not set.
230    /// Panics if the env var contains an invalid value.
231    #[cfg(feature = "hotpath-meta")]
232    pub(crate) fn from_env() -> Self {
233        match std::env::var("HOTPATH_META_OUTPUT_FORMAT") {
234            Ok(v) => v
235                .parse()
236                .unwrap_or_else(|e| panic!("HOTPATH_META_OUTPUT_FORMAT: {}", e)),
237            Err(_) => Format::default(),
238        }
239    }
240}
241
242pub trait IntoF64 {
243    fn into_f64(self) -> f64;
244}
245
246impl IntoF64 for f64 {
247    fn into_f64(self) -> f64 {
248        self
249    }
250}
251
252impl IntoF64 for f32 {
253    fn into_f64(self) -> f64 {
254        self as f64
255    }
256}
257
258impl IntoF64 for i8 {
259    fn into_f64(self) -> f64 {
260        self as f64
261    }
262}
263
264impl IntoF64 for i16 {
265    fn into_f64(self) -> f64 {
266        self as f64
267    }
268}
269
270impl IntoF64 for i32 {
271    fn into_f64(self) -> f64 {
272        self as f64
273    }
274}
275
276impl IntoF64 for i64 {
277    fn into_f64(self) -> f64 {
278        self as f64
279    }
280}
281
282impl IntoF64 for u8 {
283    fn into_f64(self) -> f64 {
284        self as f64
285    }
286}
287
288impl IntoF64 for u16 {
289    fn into_f64(self) -> f64 {
290        self as f64
291    }
292}
293
294impl IntoF64 for u32 {
295    fn into_f64(self) -> f64 {
296        self as f64
297    }
298}
299
300impl IntoF64 for u64 {
301    fn into_f64(self) -> f64 {
302        self as f64
303    }
304}
305
306impl IntoF64 for isize {
307    fn into_f64(self) -> f64 {
308        self as f64
309    }
310}
311
312impl IntoF64 for usize {
313    fn into_f64(self) -> f64 {
314        self as f64
315    }
316}
317
318#[doc(hidden)]
319pub fn env_flag(name: &str) -> bool {
320    std::env::var(name)
321        .map(|v| v.eq_ignore_ascii_case("true") || v == "1")
322        .unwrap_or(false)
323}
324
325#[cfg(feature = "hotpath-meta")]
326pub(crate) fn resolve_timeout_duration(
327    default_duration: std::time::Duration,
328    env_var: &str,
329) -> Option<std::time::Duration> {
330    let effective_duration = std::env::var(env_var)
331        .ok()
332        .and_then(|value| value.parse::<u64>().ok())
333        .map(std::time::Duration::from_millis)
334        .unwrap_or(default_duration);
335
336    if effective_duration.is_zero() {
337        None
338    } else {
339        Some(effective_duration)
340    }
341}