Skip to content

Commit f521c0a

Browse files
authored
Merge pull request #2161 from Kobzol/check-test-cases-with-measurements
Check test cases with measurements
2 parents b34ad29 + d424fa1 commit f521c0a

File tree

9 files changed

+315
-64
lines changed

9 files changed

+315
-64
lines changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
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/mod.rs

Lines changed: 127 additions & 17 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};
@@ -243,6 +244,7 @@ impl Benchmark {
243244
toolchain: &Toolchain,
244245
iterations: Option<usize>,
245246
targets: &[Target],
247+
already_computed: &hashbrown::HashSet<CompileTestCase>,
246248
) -> anyhow::Result<()> {
247249
if self.config.disabled {
248250
eprintln!("Skipping {}: disabled", self.name);
@@ -273,19 +275,65 @@ impl Benchmark {
273275
return Ok(());
274276
}
275277

276-
eprintln!("Preparing {}", self.name);
277-
let mut target_dirs: Vec<((CodegenBackend, Profile, Target), TempDir)> = vec![];
278+
struct BenchmarkDir {
279+
dir: TempDir,
280+
scenarios: Vec<Scenario>,
281+
profile: Profile,
282+
backend: CodegenBackend,
283+
target: Target,
284+
}
285+
286+
// Materialize the test cases that we want to benchmark
287+
// We need to handle scenarios a bit specially, because they share the target directory
288+
let mut benchmark_dirs: Vec<BenchmarkDir> = vec![];
289+
278290
for backend in backends {
279291
for profile in &profiles {
280292
for target in targets {
281-
target_dirs.push((
282-
(*backend, *profile, *target),
283-
self.make_temp_dir(&self.path)?,
284-
));
293+
// Do we have any scenarios left to compute?
294+
let remaining_scenarios = scenarios
295+
.iter()
296+
.filter(|scenario| {
297+
self.should_run_scenario(
298+
scenario,
299+
profile,
300+
backend,
301+
target,
302+
already_computed,
303+
)
304+
})
305+
.copied()
306+
.collect::<Vec<Scenario>>();
307+
if remaining_scenarios.is_empty() {
308+
continue;
309+
}
310+
311+
let temp_dir = self.make_temp_dir(&self.path)?;
312+
benchmark_dirs.push(BenchmarkDir {
313+
dir: temp_dir,
314+
scenarios: remaining_scenarios,
315+
profile: *profile,
316+
backend: *backend,
317+
target: *target,
318+
});
285319
}
286320
}
287321
}
288322

323+
if benchmark_dirs.is_empty() {
324+
eprintln!(
325+
"Skipping {}: all test cases were previously computed",
326+
self.name
327+
);
328+
return Ok(());
329+
}
330+
331+
eprintln!(
332+
"Preparing {} (test cases: {})",
333+
self.name,
334+
benchmark_dirs.len()
335+
);
336+
289337
// In parallel (but with a limit to the number of CPUs), prepare all
290338
// profiles. This is done in parallel vs. sequentially because:
291339
// * We don't record any measurements during this phase, so the
@@ -319,18 +367,18 @@ impl Benchmark {
319367
.get(),
320368
)
321369
.context("jobserver::new")?;
322-
let mut threads = Vec::with_capacity(target_dirs.len());
323-
for ((backend, profile, target), prep_dir) in &target_dirs {
370+
let mut threads = Vec::with_capacity(benchmark_dirs.len());
371+
for benchmark_dir in &benchmark_dirs {
324372
let server = server.clone();
325373
let thread = s.spawn::<_, anyhow::Result<()>>(move || {
326374
wait_for_future(async move {
327375
let server = server.clone();
328376
self.mk_cargo_process(
329377
toolchain,
330-
prep_dir.path(),
331-
*profile,
332-
*backend,
333-
*target,
378+
benchmark_dir.dir.path(),
379+
benchmark_dir.profile,
380+
benchmark_dir.backend,
381+
benchmark_dir.target,
334382
)
335383
.jobserver(server)
336384
.run_rustc(false)
@@ -365,10 +413,11 @@ impl Benchmark {
365413
let mut timing_dirs: Vec<ManuallyDrop<TempDir>> = vec![];
366414

367415
let benchmark_start = std::time::Instant::now();
368-
for ((backend, profile, target), prep_dir) in &target_dirs {
369-
let backend = *backend;
370-
let profile = *profile;
371-
let target = *target;
416+
for benchmark_dir in &benchmark_dirs {
417+
let backend = benchmark_dir.backend;
418+
let profile = benchmark_dir.profile;
419+
let target = benchmark_dir.target;
420+
let scenarios = &benchmark_dir.scenarios;
372421
eprintln!(
373422
"Running {}: {:?} + {:?} + {:?} + {:?}",
374423
self.name, profile, scenarios, backend, target,
@@ -388,7 +437,7 @@ impl Benchmark {
388437
}
389438
log::debug!("Benchmark iteration {}/{}", i + 1, iterations);
390439
// Don't delete the directory on error.
391-
let timing_dir = ManuallyDrop::new(self.make_temp_dir(prep_dir.path())?);
440+
let timing_dir = ManuallyDrop::new(self.make_temp_dir(benchmark_dir.dir.path())?);
392441
let cwd = timing_dir.path();
393442

394443
// A full non-incremental build.
@@ -458,6 +507,67 @@ impl Benchmark {
458507

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

463573
/// Directory containing compile-time benchmarks.

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());

collector/src/lib.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ pub mod utils;
1717

1818
use crate::compile::benchmark::{Benchmark, BenchmarkName};
1919
use crate::runtime::{BenchmarkGroup, BenchmarkSuite};
20+
use database::selector::CompileTestCase;
2021
use database::{ArtifactId, ArtifactIdNumber, Connection};
22+
use hashbrown::HashSet;
2123
use process::Stdio;
2224
use std::time::{Duration, Instant};
2325

@@ -330,23 +332,30 @@ impl CollectorStepBuilder {
330332
tx.commit().await.unwrap();
331333
artifact_row_id
332334
};
333-
CollectorCtx { artifact_row_id }
335+
// Find out which tests cases were already computed
336+
let measured_compile_test_cases = conn
337+
.get_compile_test_cases_with_measurements(&artifact_row_id)
338+
.await
339+
.expect("cannot fetch measured compile test cases from DB");
340+
341+
CollectorCtx {
342+
artifact_row_id,
343+
measured_compile_test_cases,
344+
}
334345
}
335346
}
336347

337348
/// Represents an in-progress run for a given artifact.
338349
pub struct CollectorCtx {
339350
pub artifact_row_id: ArtifactIdNumber,
351+
/// Which tests cases were already computed **before** this collection began?
352+
pub measured_compile_test_cases: HashSet<CompileTestCase>,
340353
}
341354

342355
impl CollectorCtx {
343-
pub async fn start_compile_step(
344-
&self,
345-
conn: &dyn Connection,
346-
benchmark_name: &BenchmarkName,
347-
) -> bool {
356+
pub async fn start_compile_step(&self, conn: &dyn Connection, benchmark_name: &BenchmarkName) {
348357
conn.collector_start_step(self.artifact_row_id, &benchmark_name.0)
349-
.await
358+
.await;
350359
}
351360

352361
pub async fn end_compile_step(&self, conn: &dyn Connection, benchmark_name: &BenchmarkName) {

0 commit comments

Comments
 (0)