profile-inspect 0.1.3

Analyze V8 CPU and heap profiles from Node.js/Chrome DevTools
Documentation
use std::fs::File;
use std::io::BufWriter;
use std::path::PathBuf;

use bpaf::Bpaf;

use profile_inspect::analysis::HeapAnalyzer;
use profile_inspect::classify::FrameClassifier;
use profile_inspect::output::{OutputFormat, get_formatter};
use profile_inspect::parser::HeapProfileParser;

use super::common::{AnalysisOptions, OutputOptions};
use super::common::{analysis_options, output_options};

/// Analyze a heap profile
#[derive(Debug, Clone, Bpaf)]
#[bpaf(command("heap"))]
pub struct HeapCommand {
    #[bpaf(external(output_options))]
    pub output_options: OutputOptions,

    #[bpaf(external(analysis_options))]
    pub analysis_options: AnalysisOptions,

    /// Path to .heapprofile file
    #[bpaf(positional("INPUT"))]
    pub input: PathBuf,
}

impl HeapCommand {
    pub fn run(self) -> Result<(), Box<dyn std::error::Error>> {
        // Parse profile
        let classifier = FrameClassifier::default();
        let parser = HeapProfileParser::new(classifier);
        let profile = parser.parse_file(&self.input)?;

        // Build analyzer
        let analyzer = HeapAnalyzer::new()
            .include_internals(self.analysis_options.include_internals)
            .min_percent(self.analysis_options.min_percent)
            .top_n(self.analysis_options.top);

        let analysis = analyzer.analyze(&profile);

        // Output to each format
        for fmt_str in &self.output_options.formats() {
            let format = OutputFormat::from_str(fmt_str)
                .ok_or_else(|| format!("Unknown format: {fmt_str}"))?;

            let formatter = get_formatter(format);

            // Determine output path
            let output_path = if let Some(dir) = &self.output_options.output {
                std::fs::create_dir_all(dir)?;
                // Use heap-specific filename
                let filename = match format {
                    OutputFormat::Markdown => "heap-analysis.md",
                    OutputFormat::Text => "heap-analysis.txt",
                    OutputFormat::Json => "heap-analysis.json",
                    OutputFormat::Speedscope => "heap.speedscope.json",
                    OutputFormat::Collapsed => "heap.collapsed.txt",
                };
                Some(dir.join(filename))
            } else {
                None
            };

            // Write output
            if let Some(path) = output_path {
                let file = File::create(&path)?;
                let mut writer = BufWriter::new(file);
                formatter.write_heap_analysis(&profile, &analysis, &mut writer)?;
                eprintln!("Wrote: {}", path.display());
            } else {
                let stdout = std::io::stdout();
                let mut writer = stdout.lock();
                formatter.write_heap_analysis(&profile, &analysis, &mut writer)?;
            }
        }

        Ok(())
    }
}