Skip to content

Commit 814271d

Browse files
authored
Merge pull request #2031 from oli-obk/push-wtzmtssxzmxz
Make it easy to add diffing of output of arbitrary profilers (demonstrate for dep-graph)
2 parents e22e086 + 0892746 commit 814271d

File tree

7 files changed

+149
-82
lines changed

7 files changed

+149
-82
lines changed

collector/src/bin/collector.rs

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -132,21 +132,16 @@ fn check_measureme_installed() -> Result<(), String> {
132132
}
133133
}
134134

135-
fn check_installed(name: &str) -> anyhow::Result<()> {
136-
if !is_installed(name) {
137-
anyhow::bail!("`{}` is not installed but must be", name);
138-
}
139-
Ok(())
140-
}
141-
142-
fn generate_cachegrind_diffs(
135+
#[allow(clippy::too_many_arguments)]
136+
fn generate_diffs(
143137
id1: &str,
144138
id2: &str,
145139
out_dir: &Path,
146140
benchmarks: &[Benchmark],
147141
profiles: &[Profile],
148142
scenarios: &[Scenario],
149143
errors: &mut BenchmarkErrors,
144+
profiler: &Profiler,
150145
) -> Vec<PathBuf> {
151146
let mut annotated_diffs = Vec::new();
152147
for benchmark in benchmarks {
@@ -166,22 +161,28 @@ fn generate_cachegrind_diffs(
166161
}) {
167162
let filename = |prefix, id| {
168163
format!(
169-
"{}-{}-{}-{:?}-{}",
170-
prefix, id, benchmark.name, profile, scenario
164+
"{}-{}-{}-{:?}-{}{}",
165+
prefix,
166+
id,
167+
benchmark.name,
168+
profile,
169+
scenario,
170+
profiler.postfix()
171171
)
172172
};
173173
let id_diff = format!("{}-{}", id1, id2);
174-
let cgout1 = out_dir.join(filename("cgout", id1));
175-
let cgout2 = out_dir.join(filename("cgout", id2));
176-
let cgann_diff = out_dir.join(filename("cgann-diff", &id_diff));
174+
let prefix = profiler.prefix();
175+
let left = out_dir.join(filename(prefix, id1));
176+
let right = out_dir.join(filename(prefix, id2));
177+
let output = out_dir.join(filename(&format!("{prefix}-diff"), &id_diff));
177178

178-
if let Err(e) = cachegrind_diff(&cgout1, &cgout2, &cgann_diff) {
179+
if let Err(e) = profiler.diff(&left, &right, &output) {
179180
errors.incr();
180181
eprintln!("collector error: {:?}", e);
181182
continue;
182183
}
183184

184-
annotated_diffs.push(cgann_diff);
185+
annotated_diffs.push(output);
185186
}
186187
}
187188
}
@@ -1145,32 +1146,25 @@ fn main_result() -> anyhow::Result<i32> {
11451146
let id1 = get_toolchain_and_profile(local.rustc.as_str(), "1")?;
11461147
let id2 = get_toolchain_and_profile(rustc2.as_str(), "2")?;
11471148

1148-
if profiler == Profiler::Cachegrind {
1149-
check_installed("valgrind")?;
1150-
check_installed("cg_annotate")?;
1151-
1152-
let diffs = generate_cachegrind_diffs(
1153-
&id1,
1154-
&id2,
1155-
&out_dir,
1156-
&benchmarks,
1157-
profiles,
1158-
scenarios,
1159-
&mut errors,
1160-
);
1161-
match diffs.len().cmp(&1) {
1162-
Ordering::Equal => {
1163-
let short = out_dir.join("cgann-diff-latest");
1164-
std::fs::copy(&diffs[0], &short).expect("copy to short path");
1165-
eprintln!("Original diff at: {}", diffs[0].to_string_lossy());
1166-
eprintln!("Short path: {}", short.to_string_lossy());
1167-
}
1168-
_ => {
1169-
eprintln!("Diffs:");
1170-
for diff in diffs {
1171-
eprintln!("{}", diff.to_string_lossy());
1172-
}
1173-
}
1149+
let diffs = generate_diffs(
1150+
&id1,
1151+
&id2,
1152+
&out_dir,
1153+
&benchmarks,
1154+
profiles,
1155+
scenarios,
1156+
&mut errors,
1157+
&profiler,
1158+
);
1159+
if let [diff] = &diffs[..] {
1160+
let short = out_dir.join(format!("{}-diff-latest", profiler.prefix()));
1161+
std::fs::copy(diff, &short).expect("copy to short path");
1162+
eprintln!("Original diff at: {}", diff.to_string_lossy());
1163+
eprintln!("Short path: {}", short.to_string_lossy());
1164+
} else {
1165+
eprintln!("Diffs:");
1166+
for diff in diffs {
1167+
eprintln!("{}", diff.to_string_lossy());
11741168
}
11751169
}
11761170
} else {

collector/src/compare.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ pub async fn compare_artifacts(
8080
None => select_artifact_id("base", &aids)?.to_string(),
8181
};
8282
aids.retain(|id| id != &base);
83-
let modified = if aids.len() == 1 {
84-
let new_modified = aids[0].clone();
83+
let modified = if let [new_modified] = &aids[..] {
84+
let new_modified = new_modified.clone();
8585
println!(
8686
"Only 1 artifact remains, automatically selecting: {}",
8787
new_modified

collector/src/compile/execute/profiler.rs

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use crate::compile::execute::{PerfTool, ProcessOutputData, Processor, Retry};
2-
use crate::utils;
3-
use crate::utils::cachegrind::cachegrind_annotate;
2+
use crate::utils::cachegrind::{cachegrind_annotate, cachegrind_diff};
3+
use crate::utils::diff::run_diff;
4+
use crate::utils::{self};
45
use anyhow::Context;
56
use std::collections::HashMap;
7+
use std::fs::File;
68
use std::future::Future;
79
use std::io::BufRead;
810
use std::io::Write;
@@ -50,6 +52,42 @@ impl Profiler {
5052
| Profiler::DepGraph
5153
)
5254
}
55+
56+
/// A file prefix added to all files of this profiler.
57+
pub fn prefix(&self) -> &'static str {
58+
use Profiler::*;
59+
match self {
60+
Cachegrind => "cgout",
61+
DepGraph => "dep-graph",
62+
63+
SelfProfile | PerfRecord | Oprofile | Samply | Callgrind | Dhat | DhatCopy | Massif
64+
| Bytehound | Eprintln | LlvmLines | MonoItems | LlvmIr => "",
65+
}
66+
}
67+
68+
/// A postfix added to the file that gets diffed.
69+
pub fn postfix(&self) -> &'static str {
70+
use Profiler::*;
71+
match self {
72+
Cachegrind => "",
73+
DepGraph => ".txt",
74+
75+
SelfProfile | PerfRecord | Oprofile | Samply | Callgrind | Dhat | DhatCopy | Massif
76+
| Bytehound | Eprintln | LlvmLines | MonoItems | LlvmIr => "",
77+
}
78+
}
79+
80+
/// Actually perform the diff
81+
pub fn diff(&self, left: &Path, right: &Path, output: &Path) -> anyhow::Result<()> {
82+
use Profiler::*;
83+
match self {
84+
Cachegrind => cachegrind_diff(left, right, output),
85+
DepGraph => run_diff(left, right, output),
86+
87+
SelfProfile | PerfRecord | Oprofile | Samply | Callgrind | Dhat | DhatCopy | Massif
88+
| Bytehound | Eprintln | LlvmLines | MonoItems | LlvmIr => Ok(()),
89+
}
90+
}
5391
}
5492

5593
pub struct ProfileProcessor<'a> {
@@ -143,10 +181,8 @@ impl<'a> Processor for ProfileProcessor<'a> {
143181
// Run `summarize`.
144182
let mut summarize_cmd = Command::new("summarize");
145183
summarize_cmd.arg("summarize").arg(&zsp_files_prefix);
146-
fs::write(
147-
summarize_file,
148-
summarize_cmd.output().context("summarize")?.stdout,
149-
)?;
184+
summarize_cmd.stdout(File::create(summarize_file)?);
185+
summarize_cmd.status().context("summarize")?;
150186

151187
// Run `flamegraph`.
152188
let mut flamegraph_cmd = Command::new("flamegraph");
@@ -198,17 +234,19 @@ impl<'a> Processor for ProfileProcessor<'a> {
198234
.arg("--debug-info")
199235
.arg("--threshold")
200236
.arg("0.5")
201-
.arg(&session_dir_arg);
202-
fs::write(oprep_file, op_report_cmd.output()?.stdout)?;
237+
.arg(&session_dir_arg)
238+
.stdout(File::create(oprep_file)?);
239+
op_report_cmd.status()?;
203240

204241
let mut op_annotate_cmd = Command::new("opannotate");
205242
// Other possibly useful args: --assembly
206243
op_annotate_cmd
207244
.arg("--source")
208245
.arg("--threshold")
209246
.arg("0.5")
210-
.arg(&session_dir_arg);
211-
fs::write(opann_file, op_annotate_cmd.output()?.stdout)?;
247+
.arg(&session_dir_arg)
248+
.stdout(File::create(opann_file)?);
249+
op_annotate_cmd.status()?;
212250
}
213251

214252
// Samply produces (via rustc-fake) a data file called
@@ -248,8 +286,9 @@ impl<'a> Processor for ProfileProcessor<'a> {
248286
clg_annotate_cmd
249287
.arg("--auto=yes")
250288
.arg("--show-percs=yes")
251-
.arg(&clgout_file);
252-
fs::write(clgann_file, clg_annotate_cmd.output()?.stdout)?;
289+
.arg(&clgout_file)
290+
.stdout(File::create(clgann_file)?);
291+
clg_annotate_cmd.status()?;
253292
}
254293

255294
// DHAT produces (via rustc-fake) a data file called `dhout`. We
@@ -371,13 +410,13 @@ impl<'a> Processor for ProfileProcessor<'a> {
371410
Profiler::DepGraph => {
372411
let tmp_file = filepath(data.cwd, "dep_graph.txt");
373412
let output =
374-
filepath(self.output_dir, &out_file("dep-graph")).with_extension("txt");
413+
filepath(self.output_dir, &format!("{}.txt", out_file("dep-graph")));
375414

376415
fs::copy(tmp_file, output)?;
377416

378417
let tmp_file = filepath(data.cwd, "dep_graph.dot");
379418
let output =
380-
filepath(self.output_dir, &out_file("dep-graph")).with_extension("dot");
419+
filepath(self.output_dir, &format!("{}.dot", out_file("dep-graph")));
381420

382421
// May not exist if not incremental, but then that's OK.
383422
fs::copy(tmp_file, output)?;

collector/src/toolchain.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -418,9 +418,10 @@ pub fn get_local_toolchain(
418418
.output()
419419
.context("failed to run `rustup which rustc`")?;
420420

421-
if !output.status.success() {
422-
anyhow::bail!("did not manage to obtain toolchain {}", toolchain);
423-
}
421+
anyhow::ensure!(
422+
output.status.success(),
423+
"did not manage to obtain toolchain {toolchain}"
424+
);
424425

425426
let s = String::from_utf8(output.stdout)
426427
.context("failed to convert `rustup which rustc` output to utf8")?;

collector/src/utils/cachegrind.rs

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
use crate::utils::is_installed;
21
use crate::utils::mangling::demangle_file;
32
use anyhow::Context;
3+
use std::fs::File;
44
use std::io::{BufRead, Write};
55
use std::path::Path;
66
use std::process::{Command, Stdio};
77
use std::{fs, io};
88

9+
use super::check_installed;
10+
911
/// Annotate and demangle the output of Cachegrind using the `cg_annotate` tool.
1012
pub fn cachegrind_annotate(
1113
input: &Path,
@@ -61,13 +63,15 @@ pub fn cachegrind_annotate(
6163
cg_annotate_cmd
6264
.arg("--auto=yes")
6365
.arg("--show-percs=yes")
64-
.arg(cgout_output);
65-
fs::write(cgann_output, cg_annotate_cmd.output()?.stdout)?;
66+
.arg(cgout_output)
67+
.stdout(File::create(cgann_output)?);
68+
cg_annotate_cmd.status()?;
6669
Ok(())
6770
}
6871

6972
/// Creates a diff between two `cgout` files, and annotates the diff.
7073
pub fn cachegrind_diff(cgout_a: &Path, cgout_b: &Path, output: &Path) -> anyhow::Result<()> {
74+
check_installed("valgrind")?;
7175
let cgout_diff = tempfile::NamedTempFile::new()?.into_temp_path();
7276

7377
run_cg_diff(cgout_a, cgout_b, &cgout_diff).context("Cannot run cg_diff")?;
@@ -78,41 +82,34 @@ pub fn cachegrind_diff(cgout_a: &Path, cgout_b: &Path, output: &Path) -> anyhow:
7882

7983
/// Compares two Cachegrind output files using `cg_diff` and writes the result to `path`.
8084
fn run_cg_diff(cgout1: &Path, cgout2: &Path, path: &Path) -> anyhow::Result<()> {
81-
if !is_installed("cg_diff") {
82-
anyhow::bail!("`cg_diff` not installed.");
83-
}
84-
let output = Command::new("cg_diff")
85+
check_installed("cg_diff")?;
86+
let status = Command::new("cg_diff")
8587
.arg(r"--mod-filename=s/\/rustc\/[^\/]*\///")
8688
.arg("--mod-funcname=s/[.]llvm[.].*//")
8789
.arg(cgout1)
8890
.arg(cgout2)
8991
.stderr(Stdio::inherit())
90-
.output()
92+
.stdout(File::create(path)?)
93+
.status()
9194
.context("failed to run `cg_diff`")?;
9295

93-
if !output.status.success() {
94-
anyhow::bail!("failed to generate cachegrind diff");
95-
}
96-
97-
fs::write(path, output.stdout).context("failed to write `cg_diff` output")?;
96+
anyhow::ensure!(status.success(), "failed to generate cachegrind diff");
9897

9998
Ok(())
10099
}
101100

102101
/// Postprocess Cachegrind output file and writes the result to `path`.
103102
fn annotate_diff(cgout: &Path, path: &Path) -> anyhow::Result<()> {
104-
let output = Command::new("cg_annotate")
103+
check_installed("cg_annotate")?;
104+
let status = Command::new("cg_annotate")
105105
.arg("--show-percs=no")
106106
.arg(cgout)
107107
.stderr(Stdio::inherit())
108-
.output()
108+
.stdout(File::create(path)?)
109+
.status()
109110
.context("failed to run `cg_annotate`")?;
110111

111-
if !output.status.success() {
112-
anyhow::bail!("failed to annotate cachegrind output");
113-
}
114-
115-
fs::write(path, output.stdout).context("failed to write `cg_annotate` output")?;
112+
anyhow::ensure!(status.success(), "failed to annotate cachegrind output");
116113

117114
Ok(())
118115
}

collector/src/utils/diff.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use std::{
2+
fs::File,
3+
path::Path,
4+
process::{Command, Stdio},
5+
};
6+
7+
use anyhow::Context as _;
8+
9+
use super::check_installed;
10+
11+
/// Compares two text files using `diff` and writes the result to `path`.
12+
pub fn run_diff(left: &Path, right: &Path, out_file: &Path) -> anyhow::Result<()> {
13+
check_installed("diff")?;
14+
Command::new("diff")
15+
.arg(left)
16+
.arg(right)
17+
.stderr(Stdio::inherit())
18+
.stdout(File::create(out_file)?)
19+
.status()
20+
.context("failed to run `diff`")?;
21+
22+
Ok(())
23+
}

collector/src/utils/mod.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use std::future::Future;
2-
use std::process::Command;
2+
use std::process::{Command, Stdio};
33

44
pub mod cachegrind;
5+
pub mod diff;
56
pub mod fs;
67
pub mod git;
78
pub mod mangling;
@@ -17,5 +18,17 @@ pub fn wait_for_future<F: Future<Output = R>, R>(f: F) -> R {
1718

1819
/// Checks if the given binary can be executed.
1920
pub fn is_installed(name: &str) -> bool {
20-
Command::new(name).output().is_ok()
21+
Command::new(name)
22+
.stdout(Stdio::null())
23+
.stderr(Stdio::null())
24+
.status()
25+
.is_ok()
26+
}
27+
28+
/// Checks if the given binary can be executed and bails otherwise.
29+
pub fn check_installed(name: &str) -> anyhow::Result<()> {
30+
if !is_installed(name) {
31+
anyhow::bail!("`{}` is not installed but must be", name);
32+
}
33+
Ok(())
2134
}

0 commit comments

Comments
 (0)