Skip to content

Commit 46f20a0

Browse files
committed
Move Bencher to a separate bencher module
1 parent 1b42e2f commit 46f20a0

File tree

3 files changed

+277
-264
lines changed

3 files changed

+277
-264
lines changed

collector/src/bin/collector.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@ use std::str::FromStr;
2323
use std::{str, time::Instant};
2424
use tokio::runtime::Runtime;
2525

26-
use collector::execute::{
27-
profiler::{ProfileProcessor, Profiler},
28-
BenchProcessor,
29-
};
26+
use collector::execute::bencher::BenchProcessor;
27+
use collector::execute::profiler::{ProfileProcessor, Profiler};
3028
use collector::toolchain::{get_local_toolchain, Compiler, Sysroot};
3129

3230
fn n_normal_benchmarks_remaining(n: usize) -> String {

collector/src/execute/bencher.rs

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
use crate::benchmark::profile::Profile;
2+
use crate::benchmark::scenario::Scenario;
3+
use crate::benchmark::BenchmarkName;
4+
use crate::execute;
5+
use crate::execute::{
6+
rustc, DeserializeStatError, PerfTool, ProcessOutputData, Processor, Retry, SelfProfile,
7+
SelfProfileFiles, Stats, Upload,
8+
};
9+
use crate::toolchain::Compiler;
10+
use anyhow::Context;
11+
use futures::stream::FuturesUnordered;
12+
use futures::StreamExt;
13+
use std::path::PathBuf;
14+
use std::process::Command;
15+
use std::{env, process};
16+
use tokio::runtime::Runtime;
17+
18+
// Tools usable with the benchmarking subcommands.
19+
#[derive(Clone, Copy, Debug, PartialEq)]
20+
pub enum Bencher {
21+
PerfStat,
22+
PerfStatSelfProfile,
23+
XperfStat,
24+
XperfStatSelfProfile,
25+
}
26+
27+
pub struct BenchProcessor<'a> {
28+
rt: &'a mut Runtime,
29+
benchmark: &'a BenchmarkName,
30+
conn: &'a mut dyn database::Connection,
31+
artifact: &'a database::ArtifactId,
32+
artifact_row_id: database::ArtifactIdNumber,
33+
upload: Option<Upload>,
34+
is_first_collection: bool,
35+
is_self_profile: bool,
36+
tries: u8,
37+
}
38+
39+
impl<'a> BenchProcessor<'a> {
40+
pub fn new(
41+
rt: &'a mut Runtime,
42+
conn: &'a mut dyn database::Connection,
43+
benchmark: &'a BenchmarkName,
44+
artifact: &'a database::ArtifactId,
45+
artifact_row_id: database::ArtifactIdNumber,
46+
is_self_profile: bool,
47+
) -> Self {
48+
// Check we have `perf` or (`xperf.exe` and `tracelog.exe`) available.
49+
if cfg!(unix) {
50+
let has_perf = Command::new("perf").output().is_ok();
51+
assert!(has_perf);
52+
} else {
53+
let has_xperf = Command::new(env::var("XPERF").unwrap_or("xperf.exe".to_string()))
54+
.output()
55+
.is_ok();
56+
assert!(has_xperf);
57+
58+
let has_tracelog =
59+
Command::new(env::var("TRACELOG").unwrap_or("tracelog.exe".to_string()))
60+
.output()
61+
.is_ok();
62+
assert!(has_tracelog);
63+
}
64+
65+
BenchProcessor {
66+
rt,
67+
upload: None,
68+
conn,
69+
benchmark,
70+
artifact,
71+
artifact_row_id,
72+
is_first_collection: true,
73+
is_self_profile,
74+
tries: 0,
75+
}
76+
}
77+
78+
fn insert_stats(
79+
&mut self,
80+
scenario: database::Scenario,
81+
profile: Profile,
82+
stats: (Stats, Option<SelfProfile>, Option<SelfProfileFiles>),
83+
) {
84+
let version = String::from_utf8(
85+
Command::new("git")
86+
.arg("rev-parse")
87+
.arg("HEAD")
88+
.output()
89+
.context("git rev-parse HEAD")
90+
.unwrap()
91+
.stdout,
92+
)
93+
.context("utf8")
94+
.unwrap();
95+
96+
let collection = self.rt.block_on(self.conn.collection_id(&version));
97+
let profile = match profile {
98+
Profile::Check => database::Profile::Check,
99+
Profile::Debug => database::Profile::Debug,
100+
Profile::Doc => database::Profile::Doc,
101+
Profile::Opt => database::Profile::Opt,
102+
};
103+
104+
if let Some(files) = stats.2 {
105+
if env::var_os("RUSTC_PERF_UPLOAD_TO_S3").is_some() {
106+
// We can afford to have the uploads run concurrently with
107+
// rustc. Generally speaking, they take up almost no CPU time
108+
// (just copying data into the network). Plus, during
109+
// self-profile data timing noise doesn't matter as much. (We'll
110+
// be migrating to instructions soon, hopefully, where the
111+
// upload will cause even less noise). We may also opt at some
112+
// point to defer these uploads entirely to the *end* or
113+
// something like that. For now though this works quite well.
114+
if let Some(u) = self.upload.take() {
115+
u.wait();
116+
}
117+
let prefix = PathBuf::from("self-profile")
118+
.join(self.artifact_row_id.0.to_string())
119+
.join(self.benchmark.0.as_str())
120+
.join(profile.to_string())
121+
.join(scenario.to_id());
122+
self.upload = Some(Upload::new(prefix, collection, files));
123+
self.rt.block_on(self.conn.record_raw_self_profile(
124+
collection,
125+
self.artifact_row_id,
126+
self.benchmark.0.as_str(),
127+
profile,
128+
scenario,
129+
));
130+
}
131+
}
132+
133+
let mut buf = FuturesUnordered::new();
134+
for (stat, value) in stats.0.iter() {
135+
buf.push(self.conn.record_statistic(
136+
collection,
137+
self.artifact_row_id,
138+
self.benchmark.0.as_str(),
139+
profile,
140+
scenario,
141+
stat,
142+
value,
143+
));
144+
}
145+
146+
if let Some(sp) = &stats.1 {
147+
let conn = &*self.conn;
148+
let artifact_row_id = self.artifact_row_id;
149+
let benchmark = self.benchmark.0.as_str();
150+
for qd in &sp.query_data {
151+
buf.push(conn.record_self_profile_query(
152+
collection,
153+
artifact_row_id,
154+
benchmark,
155+
profile,
156+
scenario,
157+
qd.label.as_str(),
158+
database::QueryDatum {
159+
self_time: qd.self_time,
160+
blocked_time: qd.blocked_time,
161+
incremental_load_time: qd.incremental_load_time,
162+
number_of_cache_hits: qd.number_of_cache_hits,
163+
invocation_count: qd.invocation_count,
164+
},
165+
));
166+
}
167+
}
168+
169+
self.rt
170+
.block_on(async move { while let Some(()) = buf.next().await {} });
171+
}
172+
173+
pub fn measure_rustc(&mut self, compiler: Compiler<'_>) -> anyhow::Result<()> {
174+
rustc::measure(
175+
self.rt,
176+
self.conn,
177+
compiler,
178+
self.artifact,
179+
self.artifact_row_id,
180+
)
181+
}
182+
}
183+
184+
impl<'a> Processor for BenchProcessor<'a> {
185+
fn perf_tool(&self) -> PerfTool {
186+
if self.is_first_collection && self.is_self_profile {
187+
if cfg!(unix) {
188+
PerfTool::BenchTool(Bencher::PerfStatSelfProfile)
189+
} else {
190+
PerfTool::BenchTool(Bencher::XperfStatSelfProfile)
191+
}
192+
} else {
193+
if cfg!(unix) {
194+
PerfTool::BenchTool(Bencher::PerfStat)
195+
} else {
196+
PerfTool::BenchTool(Bencher::XperfStat)
197+
}
198+
}
199+
}
200+
201+
fn start_first_collection(&mut self) {
202+
self.is_first_collection = true;
203+
}
204+
205+
fn finished_first_collection(&mut self) -> bool {
206+
let original = self.perf_tool();
207+
self.is_first_collection = false;
208+
// We need to run again if we're going to use a different perf tool
209+
self.perf_tool() != original
210+
}
211+
212+
fn process_output(
213+
&mut self,
214+
data: &ProcessOutputData<'_>,
215+
output: process::Output,
216+
) -> anyhow::Result<Retry> {
217+
match execute::process_stat_output(output) {
218+
Ok(mut res) => {
219+
if let Some(ref profile) = res.1 {
220+
execute::store_artifact_sizes_into_stats(&mut res.0, profile);
221+
}
222+
if let Profile::Doc = data.profile {
223+
let doc_dir = data.cwd.join("target/doc");
224+
if doc_dir.is_dir() {
225+
execute::store_documentation_size_into_stats(&mut res.0, &doc_dir);
226+
}
227+
}
228+
229+
match data.scenario {
230+
Scenario::Full => {
231+
self.insert_stats(database::Scenario::Empty, data.profile, res);
232+
}
233+
Scenario::IncrFull => {
234+
self.insert_stats(database::Scenario::IncrementalEmpty, data.profile, res);
235+
}
236+
Scenario::IncrUnchanged => {
237+
self.insert_stats(database::Scenario::IncrementalFresh, data.profile, res);
238+
}
239+
Scenario::IncrPatched => {
240+
let patch = data.patch.unwrap();
241+
self.insert_stats(
242+
database::Scenario::IncrementalPatch(patch.name),
243+
data.profile,
244+
res,
245+
);
246+
}
247+
}
248+
Ok(Retry::No)
249+
}
250+
Err(DeserializeStatError::NoOutput(output)) => {
251+
if self.tries < 5 {
252+
log::warn!(
253+
"failed to deserialize stats, retrying (try {}); output: {:?}",
254+
self.tries,
255+
output
256+
);
257+
self.tries += 1;
258+
Ok(Retry::Yes)
259+
} else {
260+
panic!("failed to collect statistics after 5 tries");
261+
}
262+
}
263+
Err(
264+
e
265+
@ (DeserializeStatError::ParseError { .. } | DeserializeStatError::XperfError(..)),
266+
) => {
267+
panic!("process_perf_stat_output failed: {:?}", e);
268+
}
269+
}
270+
}
271+
}

0 commit comments

Comments
 (0)