use std::fs::File;
use std::io::BufWriter;
use std::path::PathBuf;
use bpaf::Bpaf;
use profile_inspect::analysis::CpuAnalyzer;
use profile_inspect::classify::FrameClassifier;
use profile_inspect::output::{OutputFormat, get_formatter};
use profile_inspect::parser::CpuProfileParser;
use super::common::{AnalysisOptions, OutputOptions, parse_categories};
use super::common::{analysis_options, output_options};
#[derive(Debug, Clone, Bpaf)]
#[bpaf(command("cpu"))]
pub struct CpuCommand {
#[bpaf(external(output_options))]
pub output_options: OutputOptions,
#[bpaf(long)]
pub filter: Option<String>,
#[bpaf(external(analysis_options))]
pub analysis_options: AnalysisOptions,
#[bpaf(long)]
pub focus: Option<String>,
#[bpaf(long)]
pub exclude: Option<String>,
#[bpaf(long)]
pub sourcemap_dir: Option<PathBuf>,
#[bpaf(long, argument("NAME"))]
pub package: Option<String>,
#[bpaf(positional("INPUT"))]
pub input: PathBuf,
}
impl CpuCommand {
pub fn run(self) -> Result<(), Box<dyn std::error::Error>> {
let classifier = FrameClassifier::default();
let parser = CpuProfileParser::new(classifier);
let mut profile = parser.parse_file(&self.input)?;
if let Some(ref dir) = self.sourcemap_dir {
eprintln!("Resolving sourcemaps from: {}", dir.display());
let resolved = profile.resolve_sourcemaps(vec![dir.clone()]);
if resolved > 0 {
eprintln!(" Resolved {} frames via sourcemaps", resolved);
} else {
eprintln!(" No frames resolved (sourcemap not found or no matches)");
}
}
let mut analyzer = CpuAnalyzer::new()
.include_internals(self.analysis_options.include_internals)
.min_percent(self.analysis_options.min_percent)
.top_n(self.analysis_options.top);
if let Some(filter_str) = &self.filter {
let categories = parse_categories(filter_str);
analyzer = analyzer.filter_categories(categories);
}
if let Some(pkg) = &self.package {
analyzer = analyzer.filter_package(pkg.clone());
}
if let Some(pattern) = &self.focus {
let regex =
regex::Regex::new(pattern).map_err(|e| format!("Invalid focus pattern: {e}"))?;
analyzer = analyzer.focus(regex);
}
if let Some(pattern) = &self.exclude {
let regex =
regex::Regex::new(pattern).map_err(|e| format!("Invalid exclude pattern: {e}"))?;
analyzer = analyzer.exclude(regex);
}
let analysis = analyzer.analyze(&profile);
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);
let output_path = if let Some(dir) = &self.output_options.output {
std::fs::create_dir_all(dir)?;
Some(dir.join(format.default_filename()))
} else {
None
};
if let Some(path) = output_path {
let file = File::create(&path)?;
let mut writer = BufWriter::new(file);
formatter.write_cpu_analysis(&profile, &analysis, &mut writer)?;
eprintln!("Wrote: {}", path.display());
} else {
let stdout = std::io::stdout();
let mut writer = stdout.lock();
formatter.write_cpu_analysis(&profile, &analysis, &mut writer)?;
}
}
Ok(())
}
}