From 22b9da95be89d617e9c10a419ee6831631bd29fa Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 21 Aug 2026 16:04:32 +0200 Subject: [PATCH 1/3] Add a `from_token` method which is nice for testing I use this to create a `GraphQLClient` locally to test against Github's api --- site/src/github/client.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/site/src/github/client.rs b/site/src/github/client.rs index 817233722..ec8d43728 100644 --- a/site/src/github/client.rs +++ b/site/src/github/client.rs @@ -91,6 +91,7 @@ const GRAPHQL_API_BASE: &str = "https://api.github.com/graphql"; /// A client for interacting with the GraphQL GitHub API. pub struct GraphQLClient { inner: reqwest::Client, + repo: &'static str, } impl GraphQLClient { @@ -100,9 +101,12 @@ impl GraphQLClient { .config .keys .github_api_token - .clone() + .as_ref() .expect("needs github API token"); + Self::from_token(token, "rust") + } + fn from_token(token: &str, repo: &'static str) -> Self { let mut headers = header::HeaderMap::new(); headers.insert(USER_AGENT, header::HeaderValue::from_static(BOT_USER_AGENT)); headers.insert( @@ -114,7 +118,10 @@ impl GraphQLClient { .default_headers(headers) .build() .unwrap(); - Self { inner: client } + Self { + inner: client, + repo, + } } pub async fn get_comments(&self, pull_request: u32) -> anyhow::Result> { @@ -157,7 +164,6 @@ impl GraphQLClient { } let owner = "rust-lang"; - let repo = "rust"; let mut comments = Vec::new(); let mut cursor = None; @@ -167,7 +173,7 @@ impl GraphQLClient { QUERY, serde_json::json!({ "owner": owner, - "repo": repo, + "repo": self.repo, "pr": pull_request, "cursor": cursor, }), From 9d4064c69327900533022dacfa3653659f4f9c3e Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 21 Aug 2026 16:26:02 +0200 Subject: [PATCH 2/3] Add `PerfRunSource::TriageBuild` --- site/src/github.rs | 1 + site/src/github/comparison_summary.rs | 10 +++++-- site/src/github/triage.rs | 42 +++++++++++++++++++++++++++ site/src/request_handlers.rs | 2 +- site/src/request_handlers/github.rs | 12 ++++---- 5 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 site/src/github/triage.rs diff --git a/site/src/github.rs b/site/src/github.rs index 225223f88..0a6327579 100644 --- a/site/src/github.rs +++ b/site/src/github.rs @@ -1,5 +1,6 @@ pub mod client; pub mod comparison_summary; +pub mod triage; use crate::job_queue::build_queue; use crate::load::{SiteCtxt, TryCommit}; diff --git a/site/src/github/comparison_summary.rs b/site/src/github/comparison_summary.rs index 2c3f8a761..a0b08c301 100644 --- a/site/src/github/comparison_summary.rs +++ b/site/src/github/comparison_summary.rs @@ -6,6 +6,7 @@ use crate::load::SiteCtxt; use database::{metric::Metric, QueuedCommit}; +use crate::github::triage::{is_triage_run, TriageBuild}; use crate::github::{COMMENT_MARK_TEMPORARY, RUST_REPO_GITHUB_API_URL}; use humansize::BINARY; use std::fmt::Write; @@ -18,11 +19,11 @@ pub async fn post_comparison_comment( commit: QueuedCommit, is_master_commit: bool, ) -> anyhow::Result<()> { - let client = super::client::Client::from_ctxt(ctxt, RUST_REPO_GITHUB_API_URL.to_owned()); + let mut client = super::client::Client::from_ctxt(ctxt, RUST_REPO_GITHUB_API_URL.to_owned()); let pr = commit.pr; // Scan comments to hide outdated ones and gather context - let graph_client = super::client::GraphQLClient::from_ctxt(ctxt); + let mut graph_client = super::client::GraphQLClient::from_ctxt(ctxt); for comment in graph_client.get_comments(pr).await? { // If this bot is the author of the comment, the comment is not yet minimized and it is // a temporary comment, minimize it. @@ -37,6 +38,8 @@ pub async fn post_comparison_comment( let source = if is_master_commit { PerfRunSource::MasterCommit + } else if let Some(triage_run) = is_triage_run(&commit, &mut client, &mut graph_client).await? { + PerfRunSource::TriageBuild(triage_run) } else { PerfRunSource::TryBuild }; @@ -88,6 +91,8 @@ enum PerfRunSource { MasterCommit, // Manual try build on a PR TryBuild, + // A try build on a PR as part of a `@rust-timer triage` command + TriageBuild(TriageBuild), } // Should the metric be shown by default in the summary? @@ -161,6 +166,7 @@ async fn summarize_run( let next_steps = match source { PerfRunSource::TryBuild => try_run_body(is_regression, deserves_attention), PerfRunSource::MasterCommit => master_run_body(is_regression), + PerfRunSource::TriageBuild(_) => todo!(), }; writeln!(&mut message, "{next_steps}\n").unwrap(); diff --git a/site/src/github/triage.rs b/site/src/github/triage.rs new file mode 100644 index 000000000..02884303d --- /dev/null +++ b/site/src/github/triage.rs @@ -0,0 +1,42 @@ +use crate::github::client::{Client, GraphQLClient, ResponseComment}; +use crate::request_handlers::parse_unrolled_build_message; +use database::QueuedCommit; + +pub struct TriageBuild { + rollup_pr_number: u32, + triage_comment: ResponseComment, +} + +pub const TRIAGE_MARKER: &str = ""; + +/// Returns `Some` if this commit is part of a triage run. +pub async fn is_triage_run( + commit: &QueuedCommit, + client: &mut Client, + graph_qlclient: &mut GraphQLClient, +) -> anyhow::Result> { + // Find the rollup PR + let commit_title = client.get_commit(&commit.sha).await?; + let Ok(unrolled_build) = parse_unrolled_build_message(&commit_title.commit.message) else { + // The commit title parsed successfully during the `@rust-timer triage` command, so if it does not parse here it's not a triage run + return Ok(None); + }; + + // Find the triage run on the rollup PR + let rollup_comments = graph_qlclient + .get_comments(unrolled_build.rollup_pr_number) + .await?; + let Some(triage_comment) = rollup_comments.into_iter().rev().find(|c| { + c.author.login == "rust-timer" + && c.body.contains(TRIAGE_MARKER) + && c.body.contains(&commit.sha) + }) else { + // This was a try job on the unrolled build that did not originate from a triage command + return Ok(None); + }; + + Ok(Some(TriageBuild { + rollup_pr_number: unrolled_build.rollup_pr_number, + triage_comment, + })) +} diff --git a/site/src/request_handlers.rs b/site/src/request_handlers.rs index a0e95e3b9..4cd5dcbb8 100644 --- a/site/src/request_handlers.rs +++ b/site/src/request_handlers.rs @@ -6,7 +6,7 @@ mod status_page; mod toolchain; pub use dashboard::handle_dashboard; -pub use github::handle_github_webhook; +pub use github::{handle_github_webhook, parse_unrolled_build_message}; pub use graph::{ handle_compile_detail_graphs, handle_compile_detail_sections, handle_graphs, handle_runtime_detail_graphs, diff --git a/site/src/request_handlers/github.rs b/site/src/request_handlers/github.rs index 9065cced6..a879a2879 100644 --- a/site/src/request_handlers/github.rs +++ b/site/src/request_handlers/github.rs @@ -6,6 +6,7 @@ use crate::load::SiteCtxt; use std::fmt::Write; use crate::github::client::Client; +use crate::github::triage::TRIAGE_MARKER; use database::{ parse_backends, parse_profiles, parse_targets, BenchmarkRequest, BenchmarkRequestInsertResult, CodegenBackend, Profile, Target, @@ -274,6 +275,8 @@ async fn handle_rust_timer( .await; writeln!(&mut result, "{msg}\n").unwrap(); } + // Add a marker to the comment which should help to find it again later + writeln!(&mut result, "{}", TRIAGE_MARKER).unwrap(); main_client.post_comment(issue.number, result).await; } Err(e) => { @@ -285,13 +288,12 @@ async fn handle_rust_timer( } #[derive(Debug)] -struct UnrolledBuildMessage { - member_pr_number: u32, - #[expect(unused, reason = "Will be used in follow-up PR")] - rollup_pr_number: u32, +pub struct UnrolledBuildMessage { + pub member_pr_number: u32, + pub rollup_pr_number: u32, } -fn parse_unrolled_build_message(commit_message: &str) -> Result { +pub fn parse_unrolled_build_message(commit_message: &str) -> Result { let first_line = commit_message.lines().next().unwrap_or(""); // The first line of the commit message will look like From 8bf3d42be4c891690395b98dada639fafcc9d9ec Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 21 Aug 2026 16:53:59 +0200 Subject: [PATCH 3/3] Make comment of triage build link back to the triage invocation --- site/src/github/comparison_summary.rs | 17 +++++++++++++++-- site/src/github/triage.rs | 8 +++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/site/src/github/comparison_summary.rs b/site/src/github/comparison_summary.rs index a0b08c301..566b1d7ec 100644 --- a/site/src/github/comparison_summary.rs +++ b/site/src/github/comparison_summary.rs @@ -163,10 +163,10 @@ async fn summarize_run( ) .unwrap(); - let next_steps = match source { + let next_steps = match &source { PerfRunSource::TryBuild => try_run_body(is_regression, deserves_attention), PerfRunSource::MasterCommit => master_run_body(is_regression), - PerfRunSource::TriageBuild(_) => todo!(), + PerfRunSource::TriageBuild(triage_build) => triage_run_body(triage_build, is_regression), }; writeln!(&mut message, "{next_steps}\n").unwrap(); @@ -372,3 +372,16 @@ Benchmarking means the PR may be perf-sensitive. \ @rustbot label: -S-waiting-on-perf {sign}perf-regression", ) } + +fn triage_run_body(build: &TriageBuild, is_regression: bool) -> String { + // The triage url deliberately not prefixed with "https://github.com/" as to not trigger the + // "mentioned this pull request" github entry on the rollup + format!( + "This PR was benchmarked as part of triage of its containing rollup: [triage URL](/rust-lang/rust/pull/{}#issuecomment-{}).\ + \n\n\ + {}", + build.rollup_pr_number, + build.triage_comment.id, + master_run_body(is_regression) + ) +} diff --git a/site/src/github/triage.rs b/site/src/github/triage.rs index 02884303d..40d852f23 100644 --- a/site/src/github/triage.rs +++ b/site/src/github/triage.rs @@ -3,8 +3,8 @@ use crate::request_handlers::parse_unrolled_build_message; use database::QueuedCommit; pub struct TriageBuild { - rollup_pr_number: u32, - triage_comment: ResponseComment, + pub rollup_pr_number: u32, + pub triage_comment: ResponseComment, } pub const TRIAGE_MARKER: &str = ""; @@ -27,9 +27,7 @@ pub async fn is_triage_run( .get_comments(unrolled_build.rollup_pr_number) .await?; let Some(triage_comment) = rollup_comments.into_iter().rev().find(|c| { - c.author.login == "rust-timer" - && c.body.contains(TRIAGE_MARKER) - && c.body.contains(&commit.sha) + c.viewer_did_author && c.body.contains(TRIAGE_MARKER) && c.body.contains(&commit.sha) }) else { // This was a try job on the unrolled build that did not originate from a triage command return Ok(None);