Skip to content

Commit c5e965e

Browse files
authored
Merge pull request #2210 from Kobzol/granular-test-case-checking
Make missing test case check more granular
2 parents 7929852 + 8b5b9f7 commit c5e965e

File tree

13 files changed

+339
-69
lines changed

13 files changed

+339
-69
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

collector/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ anyhow = { workspace = true }
1111
chrono = { workspace = true, features = ["serde"] }
1212
clap = { workspace = true, features = ["derive"] }
1313
env_logger = { workspace = true }
14+
hashbrown = { workspace = true }
1415
log = { workspace = true }
1516
reqwest = { workspace = true, features = ["blocking", "json"] }
1617
serde = { workspace = true, features = ["derive"] }

collector/src/bin/collector.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ fn profile_compile(
225225
toolchain,
226226
Some(1),
227227
targets,
228+
// We always want to profile everything
229+
&hashbrown::HashSet::new(),
228230
));
229231
eprintln!("Finished benchmark {benchmark_id}");
230232

@@ -1804,11 +1806,8 @@ async fn bench_compile(
18041806
print_intro: &dyn Fn(),
18051807
measure: F,
18061808
) {
1807-
let is_fresh = collector.start_compile_step(conn, benchmark_name).await;
1808-
if !is_fresh {
1809-
eprintln!("skipping {} -- already benchmarked", benchmark_name);
1810-
return;
1811-
}
1809+
collector.start_compile_step(conn, benchmark_name).await;
1810+
18121811
let mut tx = conn.transaction().await;
18131812
let (supports_stable, category) = category.db_representation();
18141813
tx.conn()
@@ -1819,7 +1818,7 @@ async fn bench_compile(
18191818
tx.conn(),
18201819
benchmark_name,
18211820
&shared.artifact_id,
1822-
collector.artifact_row_id,
1821+
collector,
18231822
config.is_self_profile,
18241823
);
18251824
let result = measure(&mut processor).await;
@@ -1866,6 +1865,7 @@ async fn bench_compile(
18661865
&shared.toolchain,
18671866
config.iterations,
18681867
&config.targets,
1868+
&collector.measured_compile_test_cases,
18691869
))
18701870
.await
18711871
.with_context(|| anyhow::anyhow!("Cannot compile {}", benchmark.name))

collector/src/compile/benchmark/codegen_backend.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,12 @@ impl CodegenBackend {
1010
vec![CodegenBackend::Llvm, CodegenBackend::Cranelift]
1111
}
1212
}
13+
14+
impl From<CodegenBackend> for database::CodegenBackend {
15+
fn from(value: CodegenBackend) -> Self {
16+
match value {
17+
CodegenBackend::Llvm => database::CodegenBackend::Llvm,
18+
CodegenBackend::Cranelift => database::CodegenBackend::Cranelift,
19+
}
20+
}
21+
}

collector/src/compile/benchmark/mod.rs

Lines changed: 117 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::compile::execute::{CargoProcess, Processor};
88
use crate::toolchain::Toolchain;
99
use crate::utils::wait_for_future;
1010
use anyhow::{bail, Context};
11+
use database::selector::CompileTestCase;
1112
use log::debug;
1213
use std::collections::{HashMap, HashSet};
1314
use std::fmt::{Display, Formatter};
@@ -249,6 +250,7 @@ impl Benchmark {
249250
toolchain: &Toolchain,
250251
iterations: Option<usize>,
251252
targets: &[Target],
253+
already_computed: &hashbrown::HashSet<CompileTestCase>,
252254
) -> anyhow::Result<()> {
253255
if self.config.disabled {
254256
eprintln!("Skipping {}: disabled", self.name);
@@ -279,19 +281,61 @@ impl Benchmark {
279281
return Ok(());
280282
}
281283

282-
eprintln!("Preparing {}", self.name);
283-
let mut target_dirs: Vec<((CodegenBackend, Profile, Target), TempDir)> = vec![];
284+
struct BenchmarkDir {
285+
dir: TempDir,
286+
scenarios: Vec<Scenario>,
287+
profile: Profile,
288+
backend: CodegenBackend,
289+
target: Target,
290+
}
291+
292+
// Materialize the test cases that we want to benchmark
293+
// We need to handle scenarios a bit specially, because they share the target directory
294+
let mut benchmark_dirs: Vec<BenchmarkDir> = vec![];
295+
284296
for backend in backends {
285297
for profile in &profiles {
286298
for target in targets {
287-
target_dirs.push((
288-
(*backend, *profile, *target),
289-
self.make_temp_dir(&self.path)?,
290-
));
299+
// Do we have any scenarios left to compute?
300+
let remaining_scenarios = scenarios
301+
.iter()
302+
.filter(|scenario| {
303+
self.should_run_scenario(
304+
scenario,
305+
profile,
306+
backend,
307+
target,
308+
already_computed,
309+
)
310+
})
311+
.copied()
312+
.collect::<Vec<Scenario>>();
313+
if remaining_scenarios.is_empty() {
314+
continue;
315+
}
316+
317+
let temp_dir = self.make_temp_dir(&self.path)?;
318+
benchmark_dirs.push(BenchmarkDir {
319+
dir: temp_dir,
320+
scenarios: remaining_scenarios,
321+
profile: *profile,
322+
backend: *backend,
323+
target: *target,
324+
});
291325
}
292326
}
293327
}
294328

329+
if benchmark_dirs.is_empty() {
330+
eprintln!(
331+
"Skipping {}: all test cases were previously computed",
332+
self.name
333+
);
334+
return Ok(());
335+
}
336+
337+
eprintln!("Preparing {}", self.name);
338+
295339
// In parallel (but with a limit to the number of CPUs), prepare all
296340
// profiles. This is done in parallel vs. sequentially because:
297341
// * We don't record any measurements during this phase, so the
@@ -325,18 +369,18 @@ impl Benchmark {
325369
.get(),
326370
)
327371
.context("jobserver::new")?;
328-
let mut threads = Vec::with_capacity(target_dirs.len());
329-
for ((backend, profile, target), prep_dir) in &target_dirs {
372+
let mut threads = Vec::with_capacity(benchmark_dirs.len());
373+
for benchmark_dir in &benchmark_dirs {
330374
let server = server.clone();
331375
let thread = s.spawn::<_, anyhow::Result<()>>(move || {
332376
wait_for_future(async move {
333377
let server = server.clone();
334378
self.mk_cargo_process(
335379
toolchain,
336-
prep_dir.path(),
337-
*profile,
338-
*backend,
339-
*target,
380+
benchmark_dir.dir.path(),
381+
benchmark_dir.profile,
382+
benchmark_dir.backend,
383+
benchmark_dir.target,
340384
)
341385
.jobserver(server)
342386
.run_rustc(false)
@@ -371,10 +415,11 @@ impl Benchmark {
371415
let mut timing_dirs: Vec<ManuallyDrop<TempDir>> = vec![];
372416

373417
let benchmark_start = std::time::Instant::now();
374-
for ((backend, profile, target), prep_dir) in &target_dirs {
375-
let backend = *backend;
376-
let profile = *profile;
377-
let target = *target;
418+
for benchmark_dir in &benchmark_dirs {
419+
let backend = benchmark_dir.backend;
420+
let profile = benchmark_dir.profile;
421+
let target = benchmark_dir.target;
422+
let scenarios = &benchmark_dir.scenarios;
378423
eprintln!(
379424
"Running {}: {:?} + {:?} + {:?} + {:?}",
380425
self.name, profile, scenarios, backend, target,
@@ -394,7 +439,7 @@ impl Benchmark {
394439
}
395440
log::debug!("Benchmark iteration {}/{}", i + 1, iterations);
396441
// Don't delete the directory on error.
397-
let timing_dir = ManuallyDrop::new(self.make_temp_dir(prep_dir.path())?);
442+
let timing_dir = ManuallyDrop::new(self.make_temp_dir(benchmark_dir.dir.path())?);
398443
let cwd = timing_dir.path();
399444

400445
// A full non-incremental build.
@@ -407,7 +452,7 @@ impl Benchmark {
407452

408453
// Rustdoc does not support incremental compilation
409454
if !profile.is_doc() {
410-
// An incremental from scratch (slowest incremental case).
455+
// An incremental build from scratch (slowest incremental case).
411456
// This is required for any subsequent incremental builds.
412457
if scenarios.iter().any(|s| s.is_incr()) {
413458
self.mk_cargo_process(toolchain, cwd, profile, backend, target)
@@ -464,6 +509,60 @@ impl Benchmark {
464509

465510
Ok(())
466511
}
512+
513+
/// Return true if the given `scenario` should be computed.
514+
fn should_run_scenario(
515+
&self,
516+
scenario: &Scenario,
517+
profile: &Profile,
518+
backend: &CodegenBackend,
519+
target: &Target,
520+
already_computed: &hashbrown::HashSet<CompileTestCase>,
521+
) -> bool {
522+
// Keep this in sync with the logic in `Benchmark::measure`.
523+
if scenario.is_incr() && profile.is_doc() {
524+
return false;
525+
}
526+
527+
let benchmark = database::Benchmark::from(self.name.0.as_str());
528+
let profile: database::Profile = (*profile).into();
529+
let backend: database::CodegenBackend = (*backend).into();
530+
let target: database::Target = (*target).into();
531+
532+
match scenario {
533+
// For these scenarios, we can simply check if they were benchmarked or not
534+
Scenario::Full | Scenario::IncrFull | Scenario::IncrUnchanged => {
535+
let test_case = CompileTestCase {
536+
benchmark,
537+
profile,
538+
backend,
539+
target,
540+
scenario: match scenario {
541+
Scenario::Full => database::Scenario::Empty,
542+
Scenario::IncrFull => database::Scenario::IncrementalEmpty,
543+
Scenario::IncrUnchanged => database::Scenario::IncrementalFresh,
544+
Scenario::IncrPatched => unreachable!(),
545+
},
546+
};
547+
!already_computed.contains(&test_case)
548+
}
549+
// For incr-patched, it is a bit more complicated.
550+
// If there is at least a single uncomputed `IncrPatched`, we need to rerun
551+
// all of them, because they stack on top of one another.
552+
// Note that we don't need to explicitly include `IncrFull` if `IncrPatched`
553+
// is selected, as the benchmark code will always run `IncrFull` before `IncrPatched`.
554+
Scenario::IncrPatched => self.patches.iter().any(|patch| {
555+
let test_case = CompileTestCase {
556+
benchmark,
557+
profile,
558+
scenario: database::Scenario::IncrementalPatch(patch.name),
559+
backend,
560+
target,
561+
};
562+
!already_computed.contains(&test_case)
563+
}),
564+
}
565+
}
467566
}
468567

469568
/// Directory containing compile-time benchmarks.

collector/src/compile/benchmark/profile.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,16 @@ impl Profile {
4141
}
4242
}
4343
}
44+
45+
impl From<Profile> for database::Profile {
46+
fn from(value: Profile) -> Self {
47+
match value {
48+
Profile::Check => database::Profile::Check,
49+
Profile::Debug => database::Profile::Debug,
50+
Profile::Doc => database::Profile::Doc,
51+
Profile::DocJson => database::Profile::DocJson,
52+
Profile::Opt => database::Profile::Opt,
53+
Profile::Clippy => database::Profile::Clippy,
54+
}
55+
}
56+
}

collector/src/compile/benchmark/target.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,20 @@ impl Target {
1919
pub fn all() -> Vec<Self> {
2020
vec![Self::X86_64UnknownLinuxGnu]
2121
}
22+
}
2223

23-
pub fn from_db_target(target: &database::Target) -> Target {
24-
match target {
24+
impl From<database::Target> for Target {
25+
fn from(value: database::Target) -> Self {
26+
match value {
2527
database::Target::X86_64UnknownLinuxGnu => Self::X86_64UnknownLinuxGnu,
2628
}
2729
}
2830
}
31+
32+
impl From<Target> for database::Target {
33+
fn from(value: Target) -> Self {
34+
match value {
35+
Target::X86_64UnknownLinuxGnu => database::Target::X86_64UnknownLinuxGnu,
36+
}
37+
}
38+
}

collector/src/compile/execute/bencher.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::compile::execute::{
1010
};
1111
use crate::toolchain::Toolchain;
1212
use crate::utils::git::get_rustc_perf_commit;
13+
use crate::CollectorCtx;
1314
use anyhow::Context;
1415
use database::CollectionId;
1516
use futures::stream::FuturesUnordered;
@@ -42,7 +43,7 @@ pub struct BenchProcessor<'a> {
4243
benchmark: &'a BenchmarkName,
4344
conn: &'a mut dyn database::Connection,
4445
artifact: &'a database::ArtifactId,
45-
artifact_row_id: database::ArtifactIdNumber,
46+
collector_ctx: &'a CollectorCtx,
4647
is_first_collection: bool,
4748
is_self_profile: bool,
4849
tries: u8,
@@ -54,7 +55,7 @@ impl<'a> BenchProcessor<'a> {
5455
conn: &'a mut dyn database::Connection,
5556
benchmark: &'a BenchmarkName,
5657
artifact: &'a database::ArtifactId,
57-
artifact_row_id: database::ArtifactIdNumber,
58+
collector_ctx: &'a CollectorCtx,
5859
is_self_profile: bool,
5960
) -> Self {
6061
// Check we have `perf` or (`xperf.exe` and `tracelog.exe`) available.
@@ -78,7 +79,7 @@ impl<'a> BenchProcessor<'a> {
7879
conn,
7980
benchmark,
8081
artifact,
81-
artifact_row_id,
82+
collector_ctx,
8283
is_first_collection: true,
8384
is_self_profile,
8485
tries: 0,
@@ -108,7 +109,7 @@ impl<'a> BenchProcessor<'a> {
108109
for (stat, value) in stats.iter() {
109110
buf.push(self.conn.record_statistic(
110111
collection,
111-
self.artifact_row_id,
112+
self.collector_ctx.artifact_row_id,
112113
self.benchmark.0.as_str(),
113114
profile,
114115
scenario,
@@ -123,7 +124,13 @@ impl<'a> BenchProcessor<'a> {
123124
}
124125

125126
pub async fn measure_rustc(&mut self, toolchain: &Toolchain) -> anyhow::Result<()> {
126-
rustc::measure(self.conn, toolchain, self.artifact, self.artifact_row_id).await
127+
rustc::measure(
128+
self.conn,
129+
toolchain,
130+
self.artifact,
131+
self.collector_ctx.artifact_row_id,
132+
)
133+
.await
127134
}
128135
}
129136

@@ -252,7 +259,7 @@ impl Processor for BenchProcessor<'_> {
252259
.map(|profile| {
253260
self.conn.record_raw_self_profile(
254261
profile.collection,
255-
self.artifact_row_id,
262+
self.collector_ctx.artifact_row_id,
256263
self.benchmark.0.as_str(),
257264
profile.profile,
258265
profile.scenario,
@@ -270,7 +277,7 @@ impl Processor for BenchProcessor<'_> {
270277

271278
// FIXME: Record codegen backend in the self profile name
272279
let prefix = PathBuf::from("self-profile")
273-
.join(self.artifact_row_id.0.to_string())
280+
.join(self.collector_ctx.artifact_row_id.0.to_string())
274281
.join(self.benchmark.0.as_str())
275282
.join(profile.profile.to_string())
276283
.join(profile.scenario.to_id());

0 commit comments

Comments
 (0)