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