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    pub fn from_name(s: &str) -> Option<Section> {
61        match s.trim() {
62            "functions-timing" => Some(Section::FunctionsTiming),
63            "functions-alloc" => Some(Section::FunctionsAlloc),
64            "functions-cpu" => Some(Section::FunctionsCpu),
65            "channels" => Some(Section::Channels),
66            "streams" => Some(Section::Streams),
67            "futures" => Some(Section::Futures),
68            "rw_locks" => Some(Section::RwLocks),
69            "mutexes" => Some(Section::Mutexes),
70            "sql" => Some(Section::Sql),
71            "http" => Some(Section::Http),
72            "server" => Some(Section::Server),
73            "io" => Some(Section::Io),
74            "threads" => Some(Section::Threads),
75            "debug" => Some(Section::Debug),
76            _ => None,
77        }
78    }
79}
80
81/// How the set of report sections is determined.
82#[cfg(feature = "hotpath-meta")]
83#[derive(Clone, Debug, PartialEq, Eq)]
84pub enum SectionsMode {
85    /// Base sections (functions, threads) plus every data-driven section
86    /// (channels, streams, futures, rw_locks, mutexes, sql, http, server, io, debug)
87    /// that has data at shutdown. `include` forces sections in, `exclude`
88    /// hides them.
89    Auto {
90        include: Vec<Section>,
91        exclude: Vec<Section>,
92    },
93    /// Exactly these sections, nothing else.
94    Explicit(Vec<Section>),
95}
96
97#[cfg(feature = "hotpath-meta")]
98impl Default for SectionsMode {
99    fn default() -> Self {
100        SectionsMode::Auto {
101            include: Vec::new(),
102            exclude: Vec::new(),
103        }
104    }
105}
106
107#[cfg(feature = "hotpath-meta")]
108impl SectionsMode {
109    /// Parses a report spec: `all`, `auto`, section names, and `-section`
110    /// exclusions, comma-separated. A spec containing `auto` or any `-section`
111    /// token resolves to [`SectionsMode::Auto`]; a plain list of names is
112    /// [`SectionsMode::Explicit`]; `all` is every section minus exclusions.
113    pub fn parse(spec: &str) -> SectionsMode {
114        let mut auto = false;
115        let mut all = false;
116        let mut include = Vec::new();
117        let mut exclude = Vec::new();
118
119        for part in spec.split(',') {
120            let part = part.trim();
121            match part {
122                "all" => all = true,
123                "auto" => auto = true,
124                _ => {
125                    let (target, name) = match part.strip_prefix('-') {
126                        Some(rest) => (&mut exclude, rest.trim()),
127                        None => (&mut include, part),
128                    };
129                    if let Some(s) = Section::from_name(name) {
130                        if !target.contains(&s) {
131                            target.push(s);
132                        }
133                    } else {
134                        eprintln!("[hotpath-meta] Unknown report section: '{}'", part);
135                    }
136                }
137            }
138        }
139
140        if all {
141            SectionsMode::Explicit(
142                Section::all()
143                    .into_iter()
144                    .filter(|s| !exclude.contains(s))
145                    .collect(),
146            )
147        } else if auto || !exclude.is_empty() {
148            SectionsMode::Auto { include, exclude }
149        } else {
150            SectionsMode::Explicit(include)
151        }
152    }
153
154    pub fn from_env() -> Option<SectionsMode> {
155        std::env::var("HOTPATH_META_REPORT")
156            .ok()
157            .map(|val| SectionsMode::parse(&val))
158    }
159
160    /// Whether the section is requested by name (explicit list or a forced
161    /// auto include). Auto mode does not imply it - used for build-time
162    /// side effects that should stay lazy under auto.
163    pub fn explicitly_contains(&self, section: Section) -> bool {
164        match self {
165            SectionsMode::Explicit(list) => list.contains(&section),
166            SectionsMode::Auto { include, .. } => include.contains(&section),
167        }
168    }
169
170    /// Whether the section is part of the report set decidable before any
171    /// data exists: listed explicitly, or in auto mode and not excluded.
172    #[cfg(feature = "hotpath-cpu-meta")]
173    pub fn contains_or_auto(&self, section: Section) -> bool {
174        match self {
175            SectionsMode::Explicit(list) => list.contains(&section),
176            SectionsMode::Auto { include, exclude } => {
177                include.contains(&section) || !exclude.contains(&section)
178            }
179        }
180    }
181}
182
183/// Output format for profiling reports.
184///
185/// This enum specifies how profiling results should be displayed when the program exits.
186///
187/// # Variants
188///
189/// * `Table` - Human-readable table format (default)
190/// * `Json` - JSON format
191/// * `JsonPretty` - Pretty-printed JSON format
192/// * `None` - Suppress all profiling output (metrics server and MCP server still function)
193///
194/// # Parsing
195///
196/// Can be parsed from strings via `HOTPATH_META_OUTPUT_FORMAT` environment variable:
197/// - `"table"` → `Format::Table`
198/// - `"json"` → `Format::Json`
199/// - `"json-pretty"` → `Format::JsonPretty`
200/// - `"none"` → `Format::None`
201#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
202pub enum Format {
203    #[default]
204    Table,
205    Json,
206    JsonPretty,
207    None,
208}
209
210impl FromStr for Format {
211    type Err = String;
212
213    fn from_str(s: &str) -> Result<Self, Self::Err> {
214        match s.to_lowercase().as_str() {
215            "table" => Ok(Format::Table),
216            "json" => Ok(Format::Json),
217            "json-pretty" | "jsonpretty" => Ok(Format::JsonPretty),
218            "none" => Ok(Format::None),
219            _ => Err(format!(
220                "unknown format '{}', expected: table, json, json-pretty, none",
221                s
222            )),
223        }
224    }
225}
226
227impl Format {
228    /// Returns the format from `HOTPATH_META_OUTPUT_FORMAT` env var, or default if not set.
229    /// Panics if the env var contains an invalid value.
230    pub 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}