Skip to main content

profile_inspect/output/
mod.rs

1mod collapsed;
2mod json;
3mod markdown;
4mod speedscope;
5mod text;
6
7pub use collapsed::*;
8pub use json::*;
9pub use markdown::*;
10pub use speedscope::*;
11pub use text::*;
12
13use std::io::Write;
14
15use thiserror::Error;
16
17use crate::analysis::{CpuAnalysis, HeapAnalysis};
18use crate::ir::ProfileIR;
19
20/// Output format errors
21#[derive(Debug, Error)]
22pub enum OutputError {
23    #[error("I/O error: {0}")]
24    Io(#[from] std::io::Error),
25
26    #[error("JSON serialization error: {0}")]
27    Json(#[from] serde_json::Error),
28}
29
30/// Format time in microseconds to human-readable string.
31/// - < 1000ms: "X.XX ms"
32/// - 1000ms to 60s: "X.XX s"
33/// - >= 60s: "X.XX min"
34pub fn format_time_us(time_us: u64) -> String {
35    let ms = time_us as f64 / 1000.0;
36    format_time_ms(ms)
37}
38
39/// Format time in milliseconds to human-readable string.
40pub fn format_time_ms(ms: f64) -> String {
41    if ms < 1000.0 {
42        format!("{ms:.2} ms")
43    } else if ms < 60_000.0 {
44        format!("{:.2} s", ms / 1000.0)
45    } else {
46        format!("{:.2} min", ms / 60_000.0)
47    }
48}
49
50/// Available output formats
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum OutputFormat {
53    /// Markdown (default, LLM-optimized)
54    Markdown,
55    /// Plain text with ASCII tables
56    Text,
57    /// JSON summary for CI
58    Json,
59    /// Speedscope JSON for visualization
60    Speedscope,
61    /// Collapsed stacks for flamegraph tools
62    Collapsed,
63}
64
65impl OutputFormat {
66    /// Parse format from string
67    pub fn from_str(s: &str) -> Option<Self> {
68        match s.to_lowercase().as_str() {
69            "markdown" | "md" => Some(Self::Markdown),
70            "text" | "txt" => Some(Self::Text),
71            "json" => Some(Self::Json),
72            "speedscope" => Some(Self::Speedscope),
73            "collapsed" | "folded" => Some(Self::Collapsed),
74            _ => None,
75        }
76    }
77
78    /// Get the default file extension for this format
79    pub fn extension(&self) -> &'static str {
80        match self {
81            Self::Markdown => "md",
82            Self::Text => "txt",
83            Self::Json => "json",
84            Self::Speedscope => "speedscope.json",
85            Self::Collapsed => "collapsed.txt",
86        }
87    }
88
89    /// Get the default filename for this format
90    pub fn default_filename(&self) -> &'static str {
91        match self {
92            Self::Markdown => "profile-analysis.md",
93            Self::Text => "profile-analysis.txt",
94            Self::Json => "profile-analysis.json",
95            Self::Speedscope => "profile.speedscope.json",
96            Self::Collapsed => "profile.collapsed.txt",
97        }
98    }
99}
100
101/// Trait for output formatters
102pub trait Formatter {
103    /// Write CPU analysis to output
104    fn write_cpu_analysis(
105        &self,
106        profile: &ProfileIR,
107        analysis: &CpuAnalysis,
108        writer: &mut dyn Write,
109    ) -> Result<(), OutputError>;
110
111    /// Write heap analysis to output
112    fn write_heap_analysis(
113        &self,
114        profile: &ProfileIR,
115        analysis: &HeapAnalysis,
116        writer: &mut dyn Write,
117    ) -> Result<(), OutputError>;
118}
119
120/// Get a formatter for the given output format
121pub fn get_formatter(format: OutputFormat) -> Box<dyn Formatter> {
122    match format {
123        OutputFormat::Markdown => Box::new(MarkdownFormatter),
124        OutputFormat::Text => Box::new(TextFormatter),
125        OutputFormat::Json => Box::new(JsonFormatter),
126        OutputFormat::Speedscope => Box::new(SpeedscopeFormatter),
127        OutputFormat::Collapsed => Box::new(CollapsedFormatter),
128    }
129}