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