Skip to content

Commit

Permalink
H-3882: Create flamegraphs locally when analyzing benchmarks (#6078)
Browse files Browse the repository at this point in the history
  • Loading branch information
TimDiekmann authored Jan 7, 2025
1 parent e635226 commit 4cfb773
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 42 deletions.
25 changes: 20 additions & 5 deletions libs/@local/repo-chores/rust/bin/cli/subcommand/benches/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ use std::{
path::PathBuf,
};

use bytes::Bytes;
use clap::Parser;
use error_stack::{Report, ResultExt as _};
use hash_repo_chores::benches::{
analyze::{AnalyzeError, BenchmarkAnalysis, criterion},
report::Benchmark,
};
use inferno::flamegraph;

use crate::subcommand::benches::{criterion_directory, current_commit, target_directory};

Expand Down Expand Up @@ -68,12 +70,25 @@ pub(super) fn run(args: Args) -> Result<(), Box<dyn Error + Send + Sync>> {
BenchmarkAnalysis::from_benchmark(benchmark, &args.baseline, &artifacts_path)
.change_context(AnalyzeError::ReadInput)
.and_then(|analysis| {
if args.enforce_flame_graph && analysis.folded_stacks.is_none() {
Err(Report::new(AnalyzeError::FlameGraphMissing)
.attach_printable(analysis.measurement.info.title))
} else {
Ok(analysis)
if let Some(folded_stacks) = &analysis.folded_stacks {
let flamegraph = folded_stacks
.create_flame_graph(flamegraph::Options::default())
.change_context(AnalyzeError::FlameGraphCreation)?;
BufWriter::new(
File::options()
.create(true)
.truncate(true)
.write(true)
.open(analysis.path.join("flamegraph.svg"))
.change_context(AnalyzeError::FlameGraphCreation)?,
)
.write_all(Bytes::from(flamegraph).as_ref())
.change_context(AnalyzeError::FlameGraphCreation)?;
} else if args.enforce_flame_graph {
return Err(Report::new(AnalyzeError::FlameGraphMissing)
.attach_printable(analysis.measurement.info.title));
}
Ok(analysis)
})
})
})
Expand Down
25 changes: 21 additions & 4 deletions libs/@local/repo-chores/rust/src/benches/analyze/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::path::Path;
use std::path::PathBuf;

use error_stack::{Report, ResultExt as _};

use crate::benches::{
analyze::tracing::FoldedStacks,
generate_path,
report::{Benchmark, ChangeEstimates, Measurement},
};

Expand All @@ -12,6 +13,7 @@ pub mod tracing;

#[derive(Debug)]
pub struct BenchmarkAnalysis {
pub path: PathBuf,
pub measurement: Measurement,
pub change: Option<ChangeEstimates>,
pub folded_stacks: Option<FoldedStacks>,
Expand All @@ -30,15 +32,28 @@ impl BenchmarkAnalysis {
pub fn from_benchmark(
mut benchmark: Benchmark,
baseline: &str,
artifact_output: impl AsRef<Path>,
artifact_path: impl Into<PathBuf>,
) -> Result<Self, Report<AnalyzeError>> {
let artifact_path = artifact_path.into();
let measurement = benchmark
.measurements
.remove(baseline)
.ok_or(AnalyzeError::BaselineMissing)?;
let folded_stacks = FoldedStacks::from_measurement(artifact_output, &measurement)
.change_context(AnalyzeError::ReadInput)?;

let path = artifact_path.join(generate_path(
&measurement.info.group_id,
measurement.info.function_id.as_deref(),
measurement.info.value_str.as_deref(),
));
let tracing_file = path.join("tracing.folded");

let folded_stacks = tracing_file
.exists()
.then(|| FoldedStacks::from_file(tracing_file).change_context(AnalyzeError::ReadInput))
.transpose()?;

Ok(Self {
path,
measurement,
change: benchmark.change,
folded_stacks,
Expand All @@ -56,4 +71,6 @@ pub enum AnalyzeError {
BaselineMissing,
#[error("Flame graph is missing.")]
FlameGraphMissing,
#[error("Flame graph creation failed.")]
FlameGraphCreation,
}
34 changes: 1 addition & 33 deletions libs/@local/repo-chores/rust/src/benches/analyze/tracing.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
use core::error::Error;
use std::{
fs::File,
io,
io::{BufRead as _, BufReader},
io::{self, BufRead as _, BufReader},
path::Path,
};

use bytes::Bytes;
use error_stack::Report;
use inferno::flamegraph;

use crate::benches::{generate_path, report::Measurement};
#[derive(Debug)]
pub struct FoldedStacks {
data: Vec<String>,
Expand All @@ -23,36 +21,6 @@ impl From<FoldedStacks> for Bytes {
}

impl FoldedStacks {
/// Reads the folded stacks from the given measurement.
///
/// The folded stacks are expected to be stored in a file named `tracing.folded` in the
/// directory of the measurement in the given `artifact_output` directory.
///
/// Returns `None` if the file does not exist.
///
/// # Errors
///
/// Returns an error if reading from the file fails.
pub fn from_measurement(
artifact_output: impl AsRef<Path>,
measurement: &Measurement,
) -> Result<Option<Self>, Report<io::Error>> {
let path = artifact_output
.as_ref()
.join(generate_path(
&measurement.info.group_id,
measurement.info.function_id.as_deref(),
measurement.info.value_str.as_deref(),
))
.join("tracing.folded");

if path.exists() {
Ok(Some(Self::from_file(path)?))
} else {
Ok(None)
}
}

/// Reads the folded stacks from the given file.
///
/// # Errors
Expand Down

0 comments on commit 4cfb773

Please sign in to comment.