Skip to content

Commit 3c84c57

Browse files
committed
Add command for profiling and comparing toolchains
Current implementation allows using an arbitrary profiler, but the intended use case is profiling with cachegrind. When cachegrind is selected, the command will automatically compare results with `cg_diff` and post process output with `cg_annotate`.
1 parent 61466f7 commit 3c84c57

File tree

1 file changed

+207
-18
lines changed

1 file changed

+207
-18
lines changed

collector/src/main.rs

Lines changed: 207 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::fs;
1111
use std::io::{stderr, Write};
1212
use std::path::{Path, PathBuf};
1313
use std::process;
14-
use std::process::Command;
14+
use std::process::{Command, Stdio};
1515
use std::{str, time::Instant};
1616
use tokio::runtime::Runtime;
1717

@@ -486,6 +486,117 @@ fn get_local_toolchain(
486486
Ok((rustc, rustdoc, cargo))
487487
}
488488

489+
fn generate_cachegrind_diffs(
490+
id1: &str,
491+
id2: &str,
492+
out_dir: &Path,
493+
benchmarks: &[Benchmark],
494+
build_kinds: &[BuildKind],
495+
scenario_kinds: &[ScenarioKind],
496+
errors: &mut BenchmarkErrors,
497+
) {
498+
for benchmark in benchmarks {
499+
for &build_kind in build_kinds {
500+
for &scenario_kind in scenario_kinds {
501+
if let ScenarioKind::IncrPatched = scenario_kind {
502+
continue;
503+
}
504+
let filename = |prefix, id| {
505+
format!(
506+
"{}-{}-{}-{:?}-{:?}",
507+
prefix, id, benchmark.name, build_kind, scenario_kind
508+
)
509+
};
510+
let id_diff = format!("{}-{}", id1, id2);
511+
let cgout1 = out_dir.join(filename("cgout", id1));
512+
let cgout2 = out_dir.join(filename("cgout", id2));
513+
let cgdiff = out_dir.join(filename("cgdiff", &id_diff));
514+
let cgann = out_dir.join(filename("cgann", &id_diff));
515+
516+
if let Err(e) = cg_diff(&cgout1, &cgout2, &cgdiff) {
517+
errors.incr();
518+
eprintln!("collector error: {:?}", e);
519+
continue;
520+
}
521+
if let Err(e) = cg_annotate(&cgdiff, &cgann) {
522+
errors.incr();
523+
eprintln!("collector error: {:?}", e);
524+
continue;
525+
}
526+
}
527+
}
528+
}
529+
}
530+
531+
/// Compares two Cachegrind output files using cg_diff and writes result to path.
532+
fn cg_diff(cgout1: &Path, cgout2: &Path, path: &Path) -> anyhow::Result<()> {
533+
let output = Command::new("cg_diff")
534+
.arg("--mod-filename=s#/rustc/[^/]*/##")
535+
.arg("--mod-funcname=s/[.]llvm[.].*//")
536+
.arg(cgout1)
537+
.arg(cgout2)
538+
.stderr(Stdio::inherit())
539+
.output()
540+
.context("failed to run `cg_diff`")?;
541+
542+
if !output.status.success() {
543+
anyhow::bail!("failed to generate cachegrind diff");
544+
}
545+
546+
fs::write(path, output.stdout).context("failed to write `cg_diff` output")?;
547+
548+
Ok(())
549+
}
550+
551+
/// Post process Cachegrind output file and writes resutl to path.
552+
fn cg_annotate(cgout: &Path, path: &Path) -> anyhow::Result<()> {
553+
let output = Command::new("cg_annotate")
554+
.arg("--show-percs=no")
555+
.arg(cgout)
556+
.stderr(Stdio::inherit())
557+
.output()
558+
.context("failed to run `cg_annotate`")?;
559+
560+
if !output.status.success() {
561+
anyhow::bail!("failed to annotate cachegrind output");
562+
}
563+
564+
fs::write(path, output.stdout).context("failed to write `cg_annotate` output")?;
565+
566+
Ok(())
567+
}
568+
569+
fn profile(
570+
compiler: Compiler,
571+
id: &str,
572+
profiler: Profiler,
573+
out_dir: &Path,
574+
benchmarks: &[Benchmark],
575+
build_kinds: &[BuildKind],
576+
scenario_kinds: &[ScenarioKind],
577+
errors: &mut BenchmarkErrors,
578+
) {
579+
eprintln!("Profiling {} with {:?}", id, profiler);
580+
for (i, benchmark) in benchmarks.iter().enumerate() {
581+
eprintln!("{}", n_benchmarks_remaining(benchmarks.len() - i));
582+
let mut processor = execute::ProfileProcessor::new(profiler, out_dir, id);
583+
let result = benchmark.measure(
584+
&mut processor,
585+
&build_kinds,
586+
&scenario_kinds,
587+
compiler,
588+
Some(1),
589+
);
590+
if let Err(ref s) = result {
591+
errors.incr();
592+
eprintln!(
593+
"collector error: Failed to profile '{}' with {:?}, recorded: {:?}",
594+
benchmark.name, profiler, s
595+
);
596+
}
597+
}
598+
}
599+
489600
fn main() {
490601
match main_result() {
491602
Ok(code) => process::exit(code),
@@ -585,6 +696,35 @@ fn main_result() -> anyhow::Result<i32> {
585696
(@arg RUSTDOC: --rustdoc +takes_value "The path to the local rustdoc to benchmark")
586697
)
587698

699+
(@subcommand diff_local =>
700+
(about: "Profiles and compares two toolchains with one of several profilers")
701+
702+
// Mandatory arguments
703+
(@arg PROFILER: +required +takes_value
704+
"One of: 'self-profile', 'time-passes', 'perf-record',\n\
705+
'oprofile', 'cachegrind', 'callgrind', 'dhat', 'massif',\n\
706+
'eprintln', 'llvm-lines'")
707+
(@arg RUSTC_BEFORE: +required +takes_value "The path to the local rustc to benchmark")
708+
(@arg RUSTC_AFTER: +required +takes_value "The path to the local rustc to benchmark")
709+
710+
// Options
711+
(@arg BUILDS: --builds +takes_value
712+
"One or more (comma-separated) of: 'Check', \n\
713+
'Debug', 'Doc', 'Opt', 'All'")
714+
(@arg CARGO: --cargo +takes_value "The path to the local Cargo to use")
715+
(@arg EXCLUDE: --exclude +takes_value
716+
"Exclude all benchmarks matching anything in\n\
717+
this comma-separated list of patterns")
718+
(@arg INCLUDE: --include +takes_value
719+
"Include only benchmarks matching something in\n\
720+
this comma-separated list of patterns")
721+
(@arg OUT_DIR: --("out-dir") +takes_value "Output directory")
722+
(@arg RUNS: --runs +takes_value
723+
"One or more (comma-separated) of: 'Full',\n\
724+
'IncrFull', 'IncrUnchanged', 'IncrPatched', 'All'")
725+
(@arg RUSTDOC: --rustdoc +takes_value "The path to the local rustdoc to benchmark")
726+
)
727+
588728
(@subcommand install_next =>
589729
(about: "Installs the next commit for perf.rust-lang.org")
590730

@@ -796,38 +936,87 @@ fn main_result() -> anyhow::Result<i32> {
796936
let rustdoc = sub_m.value_of("RUSTDOC");
797937

798938
let (rustc, rustdoc, cargo) = get_local_toolchain(&build_kinds, rustc, rustdoc, cargo)?;
799-
800939
let compiler = Compiler {
801940
rustc: &rustc,
802941
rustdoc: rustdoc.as_deref(),
803942
cargo: &cargo,
804943
triple: &target_triple,
805944
is_nightly: true,
806945
};
807-
808946
let benchmarks = get_benchmarks(&benchmark_dir, include, exclude)?;
947+
let mut errors = BenchmarkErrors::new();
948+
profile(
949+
compiler,
950+
id,
951+
profiler,
952+
&out_dir,
953+
&benchmarks,
954+
&build_kinds,
955+
&scenario_kinds,
956+
&mut errors,
957+
);
958+
errors.fail_if_nonzero()?;
959+
Ok(0)
960+
}
961+
962+
("diff_local", Some(sub_m)) => {
963+
// Mandatory arguments
964+
let profiler = Profiler::from_name(sub_m.value_of("PROFILER").unwrap())?;
965+
let rustc1 = sub_m.value_of("RUSTC_BEFORE").unwrap();
966+
let rustc2 = sub_m.value_of("RUSTC_AFTER").unwrap();
967+
968+
// Options
969+
let build_kinds = build_kinds_from_arg(&sub_m.value_of("BUILDS"))?;
970+
let cargo = sub_m.value_of("CARGO");
971+
let exclude = sub_m.value_of("EXCLUDE");
972+
let include = sub_m.value_of("INCLUDE");
973+
let out_dir = PathBuf::from(sub_m.value_of_os("OUT_DIR").unwrap_or(default_out_dir));
974+
let scenario_kinds = scenario_kinds_from_arg(sub_m.value_of("RUNS"))?;
975+
let rustdoc = sub_m.value_of("RUSTDOC");
809976

810-
eprintln!("Profiling with {:?}", profiler);
977+
let id1 = rustc1.strip_prefix('+').unwrap_or("before");
978+
let id2 = rustc2.strip_prefix('+').unwrap_or("after");
979+
let mut toolchains = Vec::new();
980+
for (id, rustc) in [(id1, rustc1), (id2, rustc2)] {
981+
let (rustc, rustdoc, cargo) =
982+
get_local_toolchain(&build_kinds, rustc, rustdoc, cargo)?;
983+
toolchains.push((id.to_owned(), rustc, rustdoc, cargo));
984+
}
811985

986+
let benchmarks = get_benchmarks(&benchmark_dir, include, exclude)?;
812987
let mut errors = BenchmarkErrors::new();
813-
for (i, benchmark) in benchmarks.iter().enumerate() {
814-
eprintln!("{}", n_benchmarks_remaining(benchmarks.len() - i));
815-
let mut processor = execute::ProfileProcessor::new(profiler, &out_dir, &id);
816-
let result = benchmark.measure(
817-
&mut processor,
988+
for (id, rustc, rustdoc, cargo) in &toolchains {
989+
let compiler = Compiler {
990+
rustc: &rustc,
991+
rustdoc: rustdoc.as_deref(),
992+
cargo: &cargo,
993+
triple: &target_triple,
994+
is_nightly: true,
995+
};
996+
profile(
997+
compiler,
998+
id,
999+
profiler,
1000+
&out_dir,
1001+
&benchmarks,
8181002
&build_kinds,
8191003
&scenario_kinds,
820-
compiler,
821-
Some(1),
1004+
&mut errors,
8221005
);
823-
if let Err(ref s) = result {
824-
errors.incr();
825-
eprintln!(
826-
"collector error: Failed to profile '{}' with {:?}, recorded: {:?}",
827-
benchmark.name, profiler, s
828-
);
829-
}
8301006
}
1007+
1008+
if let Profiler::Cachegrind = profiler {
1009+
generate_cachegrind_diffs(
1010+
id1,
1011+
id2,
1012+
&out_dir,
1013+
&benchmarks,
1014+
&build_kinds,
1015+
&scenario_kinds,
1016+
&mut errors,
1017+
);
1018+
}
1019+
8311020
errors.fail_if_nonzero()?;
8321021
Ok(0)
8331022
}

0 commit comments

Comments
 (0)