Skip to content

Commit eb9b40a

Browse files
authored
Merge pull request #256 from clubby789/perf-search
Search perf build artifacts on rollup PRs
2 parents 21bc8ce + b4a41e4 commit eb9b40a

File tree

2 files changed

+119
-0
lines changed

2 files changed

+119
-0
lines changed

src/github.rs

+28
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ struct GithubAuthor {
2626
email: String,
2727
name: String,
2828
}
29+
#[derive(Serialize, Deserialize, Debug)]
30+
pub(crate) struct GithubCommentAuthor {
31+
pub(crate) login: String,
32+
}
33+
#[derive(Serialize, Deserialize, Debug)]
34+
pub(crate) struct GithubComment {
35+
pub(crate) user: GithubCommentAuthor,
36+
pub(crate) body: String,
37+
}
2938

3039
impl GithubCommitElem {
3140
fn date(&self) -> anyhow::Result<GitDate> {
@@ -78,6 +87,25 @@ pub(crate) fn get_commit(sha: &str) -> anyhow::Result<Commit> {
7887
elem.merge_base_commit.git_commit()
7988
}
8089

90+
pub(crate) fn get_pr_comments(pr: &str) -> anyhow::Result<Vec<GithubComment>> {
91+
let url = format!("https://api.github.com/repos/rust-lang/rust/issues/{pr}/comments");
92+
let client = Client::builder().default_headers(headers()?).build()?;
93+
let response: Response = client.get(&url).send()?;
94+
let status = response.status();
95+
if !status.is_success() {
96+
bail!(
97+
"error: url <{}> response {}: {}",
98+
url,
99+
status,
100+
response.text().unwrap_or_else(|_| format!("<empty>"))
101+
);
102+
}
103+
let comments: Vec<GithubComment> = response
104+
.json()
105+
.with_context(|| "failed to decode GitHub JSON response")?;
106+
Ok(comments)
107+
}
108+
81109
#[derive(Copy, Clone, Debug)]
82110
pub(crate) struct CommitsQuery<'a> {
83111
pub since_date: &'a str,

src/main.rs

+91
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use anyhow::{bail, Context};
1717
use chrono::{Date, Duration, NaiveDate, Utc};
1818
use clap::{ArgAction, Parser, ValueEnum};
1919
use colored::Colorize;
20+
use github::get_pr_comments;
2021
use log::debug;
2122
use reqwest::blocking::Client;
2223

@@ -26,6 +27,7 @@ mod least_satisfying;
2627
mod repo_access;
2728
mod toolchains;
2829

30+
use crate::github::get_commit;
2931
use crate::least_satisfying::{least_satisfying, Satisfies};
3032
use crate::repo_access::{AccessViaGithub, AccessViaLocalGit, RustRepositoryAccessor};
3133
use crate::toolchains::{
@@ -558,11 +560,30 @@ impl Config {
558560
Ok(())
559561
}
560562

563+
fn do_perf_search(&self, result: &BisectionResult) {
564+
let toolchain = &result.searched[result.found];
565+
match self.search_perf_builds(toolchain) {
566+
Ok(result) => {
567+
let url = format!(
568+
"https://github.com/rust-lang-ci/rust/commit/{}",
569+
result.searched[result.found]
570+
)
571+
.red()
572+
.bold();
573+
eprintln!("Regression in {url}");
574+
}
575+
Err(e) => {
576+
eprintln!("ERROR: {e}");
577+
}
578+
}
579+
}
580+
561581
// bisection entry point
562582
fn bisect(&self) -> anyhow::Result<()> {
563583
if self.is_commit {
564584
let bisection_result = self.bisect_ci()?;
565585
self.print_results(&bisection_result);
586+
self.do_perf_search(&bisection_result);
566587
} else {
567588
let nightly_bisection_result = self.bisect_nightlies()?;
568589
self.print_results(&nightly_bisection_result);
@@ -583,6 +604,7 @@ impl Config {
583604
let ci_bisection_result = self.bisect_ci_via(&working_commit, &bad_commit)?;
584605

585606
self.print_results(&ci_bisection_result);
607+
self.do_perf_search(&ci_bisection_result);
586608
print_final_report(self, &nightly_bisection_result, &ci_bisection_result);
587609
}
588610
}
@@ -1154,6 +1176,75 @@ impl Config {
11541176
dl_spec,
11551177
})
11561178
}
1179+
1180+
fn search_perf_builds(&self, toolchain: &Toolchain) -> anyhow::Result<BisectionResult> {
1181+
eprintln!("Attempting to search unrolled perf builds");
1182+
let Toolchain {spec: ToolchainSpec::Ci { commit, .. }, ..} = toolchain else {
1183+
bail!("not a ci commit");
1184+
};
1185+
let summary = get_commit(commit)?.summary;
1186+
if !summary.starts_with("Auto merge of #") && !summary.contains("Rollup of") {
1187+
bail!("not a rollup pr");
1188+
}
1189+
let pr = summary.split(' ').nth(3).unwrap();
1190+
// remove '#'
1191+
let pr = pr.chars().skip(1).collect::<String>();
1192+
let comments = get_pr_comments(&pr)?;
1193+
let perf_comment = comments
1194+
.iter()
1195+
.filter(|c| c.user.login == "rust-timer")
1196+
.find(|c| c.body.contains("Perf builds for each rolled up PR"))
1197+
.context("couldn't find perf build comment")?;
1198+
let builds = perf_comment
1199+
.body
1200+
.lines()
1201+
// lines of table with PR builds
1202+
.filter(|l| l.starts_with("|#"))
1203+
// get the commit link
1204+
.filter_map(|l| l.split('|').nth(2))
1205+
// get the commit sha
1206+
.map(|l| l.split_once('[').unwrap().1.rsplit_once(']').unwrap().0)
1207+
.collect::<Vec<_>>();
1208+
let short_sha = builds
1209+
.iter()
1210+
.map(|sha| sha.chars().take(8).collect())
1211+
.collect::<Vec<String>>();
1212+
eprintln!("Found commits {short_sha:?}");
1213+
self.linear_in_commits(&builds)
1214+
}
1215+
1216+
fn linear_in_commits(&self, commits: &[&str]) -> anyhow::Result<BisectionResult> {
1217+
let dl_spec = DownloadParams::for_ci(self);
1218+
1219+
let toolchains = commits
1220+
.into_iter()
1221+
.map(|commit| {
1222+
let mut t = Toolchain {
1223+
spec: ToolchainSpec::Ci {
1224+
commit: commit.to_string(),
1225+
alt: self.args.alt,
1226+
},
1227+
host: self.args.host.clone(),
1228+
std_targets: vec![self.args.host.clone(), self.target.clone()],
1229+
};
1230+
t.std_targets.sort();
1231+
t.std_targets.dedup();
1232+
t
1233+
})
1234+
.collect::<Vec<_>>();
1235+
1236+
let Some(found) = toolchains.iter().position(|t| {
1237+
self.install_and_test(t, &dl_spec).unwrap_or(Satisfies::Unknown) == Satisfies::Yes
1238+
}) else {
1239+
bail!("none of the toolchains satisfied the predicate");
1240+
};
1241+
1242+
Ok(BisectionResult {
1243+
searched: toolchains,
1244+
found,
1245+
dl_spec,
1246+
})
1247+
}
11571248
}
11581249

11591250
#[derive(Clone)]

0 commit comments

Comments
 (0)