>(&job.benchmarks)
.map(|v| v.join(", "))
.unwrap_or_else(|_| job.benchmarks.clone());
- let comment_url = format!("{}#issuecomment-{}", job.pr_url, job.comment_id);
- let footer = github::issues_footer(config.runner_repo_url.as_deref());
let k8s_detail = if k8s_message.is_empty() {
String::new()
} else {
@@ -248,16 +258,27 @@ async fn post_deadline_exceeded_comment(
"\n\nKubernetes message
\n\n```\n{k8s_message}\n```\n\n "
)
};
- let body = format!(
- "Benchmark for [this request]({comment_url}) hit the {deadline}s job deadline before finishing.\n\n\
- Benchmarks requested: `{benchmarks}`{k8s_detail}{footer}",
+ let section = format!(
+ "\u{23F1}\u{FE0F} **Benchmark hit the {deadline}s job deadline**\n\n\
+ Benchmarks requested: `{benchmarks}`{k8s_detail}",
deadline = config.active_deadline_secs,
);
- if let Err(e) = gh.post_comment(&job.repo, job.pr_number, &body).await {
+ if let Err(e) = health::update_trigger_comment(
+ pool,
+ gh,
+ locks,
+ config.runner_repo_url.as_deref(),
+ job.id,
+ &job.repo,
+ job.comment_id,
+ §ion,
+ )
+ .await
+ {
warn!(
comment_id = job.comment_id,
error = %e,
- "failed to post deadline-exceeded comment"
+ "failed to update trigger comment for deadline-exceeded"
);
}
}
diff --git a/controller/src/lib.rs b/controller/src/lib.rs
index 7ab29c6..fd15501 100644
--- a/controller/src/lib.rs
+++ b/controller/src/lib.rs
@@ -1,4 +1,5 @@
pub mod benchmarks;
+pub mod comment_render;
pub mod config;
pub mod db;
pub mod github;
diff --git a/controller/src/runner/bench_arrow.rs b/controller/src/runner/bench_arrow.rs
index 0c11a39..cb410e6 100644
--- a/controller/src/runner/bench_arrow.rs
+++ b/controller/src/runner/bench_arrow.rs
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use tracing::{info, warn};
-use crate::github;
+use crate::runner::bench_standard::format_machine_details;
use crate::runner::config::RunnerConfig;
use crate::runner::git;
use crate::runner::monitor;
@@ -59,7 +59,7 @@ pub async fn run(config: &RunnerConfig, poster: &CommentPoster) -> Result<()> {
// Pre-install stable toolchain to avoid rustup race in parallel builds
git::rustup_stable().await?;
- // Post "running" comment
+ // Post "running" section
let uname = shell::uname().await;
let instance_type = shell::node_instance_type().await;
let pod_resources = shell::pod_resources();
@@ -75,27 +75,20 @@ pub async fn run(config: &RunnerConfig, poster: &CommentPoster) -> Result<()> {
} else {
format!("{} (merge-base)", &base_sha[..7.min(base_sha.len())])
};
- let footer = github::issues_footer(config.runner_repo_url.as_deref());
- let running_body = format!(
- "\u{1f916} Arrow criterion benchmark running (GKE) | [trigger]({})\n\
- **Instance:** `{instance_type}` ({pod_resources}) | `{uname}`\n\
- CPU Details (lscpu)
\n\n\
- ```\n\
- {lscpu}\n\
- ```\n\n\
- \n\n\
+ let machine_details = format_machine_details(&instance_type, &pod_resources, &uname, &lscpu);
+ let running_section = format!(
+ "\u{1f916} **Arrow criterion benchmark running (GKE)**\n\n\
Comparing {changed_display} ({changed_sha}) to {baseline_label} \
[diff](https://github.com/{repo}/compare/{base_sha}..{changed_sha})\n\
BENCH_NAME={bench_name}\n\
BENCH_COMMAND={bench_command_display}\n\
- BENCH_FILTER={bench_filter}\n\
- Results will be posted here when complete{footer}",
- config.comment_url,
+ BENCH_FILTER={bench_filter}\n\n\
+ {machine_details}",
repo = config.repo,
);
- let pr_number = config.pr_number()?;
+ let comment_id = config.comment_id_i64()?;
poster
- .post_comment(&config.repo, pr_number, &running_body)
+ .update_section(&config.repo, comment_id, &running_section)
.await?;
// Compile both in parallel
@@ -178,7 +171,7 @@ pub async fn run(config: &RunnerConfig, poster: &CommentPoster) -> Result<()> {
};
// Compare and post results
- let result_body = if baseline_available {
+ let result_section = if baseline_available {
// Copy baselines into one target dir for critcmp
copy_criterion_baselines(&base_dir, &branch_dir).await;
@@ -191,15 +184,7 @@ pub async fn run(config: &RunnerConfig, poster: &CommentPoster) -> Result<()> {
monitor::format_resource_comment("base (merge-base)", &base_stats.unwrap()),
monitor::format_resource_comment("branch", &branch_stats),
);
- format_result_comment(
- &config.comment_url,
- &report,
- &resource_section,
- &instance_type,
- &pod_resources,
- &lscpu,
- &footer,
- )
+ format_result_section(&report, &resource_section, &machine_details)
} else {
let report = shell::run_command("critcmp", &[bench_branch_name.as_str()], &branch_dir)
.await
@@ -207,18 +192,10 @@ pub async fn run(config: &RunnerConfig, poster: &CommentPoster) -> Result<()> {
let resource_section =
monitor::format_resource_comment("branch", &branch_stats).to_string();
- format_branch_only_result_comment(
- &config.comment_url,
- &report,
- &resource_section,
- &instance_type,
- &pod_resources,
- &lscpu,
- &footer,
- )
+ format_branch_only_result_section(&report, &resource_section, &machine_details)
};
poster
- .post_comment(&config.repo, pr_number, &result_body)
+ .update_section(&config.repo, comment_id, &result_section)
.await?;
Ok(())
@@ -252,24 +229,10 @@ async fn copy_criterion_baselines(base_dir: &Path, branch_dir: &Path) {
}
}
-/// Format the result comment body.
-fn format_result_comment(
- comment_url: &str,
- report: &str,
- resource_section: &str,
- instance_type: &str,
- pod_resources: &str,
- lscpu: &str,
- footer: &str,
-) -> String {
+/// Format the per-job "completed" section for a baseline-comparison run.
+fn format_result_section(report: &str, resource_section: &str, machine_details: &str) -> String {
format!(
- "\u{1f916} Arrow criterion benchmark completed (GKE) | [trigger]({comment_url})\n\n\
- **Instance:** `{instance_type}` ({pod_resources})\n\n\
- CPU Details (lscpu)
\n\n\
- ```\n\
- {lscpu}\n\
- ```\n\n\
- \n\n\
+ "\u{1f916} **Arrow criterion benchmark completed (GKE)**\n\n\
Details
\n\
\n\n\
```\n\
@@ -279,29 +242,19 @@ fn format_result_comment(
\n\n\
Resource Usage
\n\n\
{resource_section}\
- \n\
- {footer}"
+ \n\n\
+ {machine_details}"
)
}
-/// Format the result comment body for branch-only runs (no baseline comparison).
-fn format_branch_only_result_comment(
- comment_url: &str,
+/// Format the per-job "completed" section for branch-only runs.
+fn format_branch_only_result_section(
report: &str,
resource_section: &str,
- instance_type: &str,
- pod_resources: &str,
- lscpu: &str,
- footer: &str,
+ machine_details: &str,
) -> String {
format!(
- "\u{1f916} Arrow criterion benchmark completed (GKE) | [trigger]({comment_url})\n\n\
- **Instance:** `{instance_type}` ({pod_resources})\n\n\
- CPU Details (lscpu)
\n\n\
- ```\n\
- {lscpu}\n\
- ```\n\n\
- \n\n\
+ "\u{1f916} **Arrow criterion benchmark completed (GKE)**\n\n\
**New benchmark — branch-only results (no baseline comparison)**\n\n\
Details
\n\
\n\n\
@@ -312,8 +265,8 @@ fn format_branch_only_result_comment(
\n\n\
Resource Usage
\n\n\
{resource_section}\
- \n\
- {footer}"
+ \n\n\
+ {machine_details}"
)
}
@@ -341,44 +294,41 @@ mod tests {
}
#[test]
- fn result_comment_format() {
- let comment = format_result_comment(
- "https://example.com/comment",
- "test report\n",
- "resources\n",
+ fn result_section_format() {
+ let machine = format_machine_details(
"c4a-standard-48",
"12 vCPU / 65 GiB",
+ "uname",
"lscpu output",
- "",
);
- assert!(comment.contains("Arrow criterion benchmark completed"));
- assert!(comment.contains("[trigger](https://example.com/comment)"));
- assert!(comment.contains("test report"));
- assert!(comment.contains("Resource Usage"));
- assert!(comment.contains("c4a-standard-48"));
- assert!(comment.contains("12 vCPU / 65 GiB"));
- assert!(comment.contains("lscpu output"));
+ let section = format_result_section("test report\n", "resources\n", &machine);
+ assert!(section.contains("Arrow criterion benchmark completed"));
+ assert!(section.contains("test report"));
+ assert!(section.contains("Resource Usage"));
+ assert!(section.contains("c4a-standard-48"));
+ assert!(section.contains("12 vCPU / 65 GiB"));
+ assert!(section.contains("lscpu output"));
+ assert!(!section.contains("[trigger]"));
}
#[test]
- fn branch_only_result_comment_format() {
- let comment = format_branch_only_result_comment(
- "https://example.com/comment",
- "branch report\n",
- "branch resources\n",
+ fn branch_only_result_section_format() {
+ let machine = format_machine_details(
"c4a-standard-48",
"12 vCPU / 65 GiB",
+ "uname",
"lscpu output",
- "",
);
- assert!(comment.contains("Arrow criterion benchmark completed"));
- assert!(comment.contains("New benchmark — branch-only results"));
- assert!(comment.contains("[trigger](https://example.com/comment)"));
- assert!(comment.contains("branch report"));
- assert!(comment.contains("Resource Usage"));
- assert!(comment.contains("branch resources"));
- assert!(comment.contains("c4a-standard-48"));
- assert!(comment.contains("12 vCPU / 65 GiB"));
- assert!(comment.contains("lscpu output"));
+ let section =
+ format_branch_only_result_section("branch report\n", "branch resources\n", &machine);
+ assert!(section.contains("Arrow criterion benchmark completed"));
+ assert!(section.contains("New benchmark — branch-only results"));
+ assert!(section.contains("branch report"));
+ assert!(section.contains("Resource Usage"));
+ assert!(section.contains("branch resources"));
+ assert!(section.contains("c4a-standard-48"));
+ assert!(section.contains("12 vCPU / 65 GiB"));
+ assert!(section.contains("lscpu output"));
+ assert!(!section.contains("[trigger]"));
}
}
diff --git a/controller/src/runner/bench_criterion.rs b/controller/src/runner/bench_criterion.rs
index 95397e1..114e8ac 100644
--- a/controller/src/runner/bench_criterion.rs
+++ b/controller/src/runner/bench_criterion.rs
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use tracing::{info, warn};
-use crate::github;
+use crate::runner::bench_standard::format_machine_details;
use crate::runner::config::RunnerConfig;
use crate::runner::git;
use crate::runner::monitor;
@@ -58,7 +58,7 @@ pub async fn run(config: &RunnerConfig, poster: &CommentPoster) -> Result<()> {
// Set up required benchmark data (e.g. sql_planner needs clickbench_partitioned)
setup_benchmark_data(bench_name, &branch_dir, &base_dir).await;
- // Post "running" comment
+ // Post "running" section
let uname = shell::uname().await;
let instance_type = shell::node_instance_type().await;
let pod_resources = shell::pod_resources();
@@ -72,27 +72,20 @@ pub async fn run(config: &RunnerConfig, poster: &CommentPoster) -> Result<()> {
} else {
format!("{} (merge-base)", &base_sha[..7.min(base_sha.len())])
};
- let footer = github::issues_footer(config.runner_repo_url.as_deref());
- let running_body = format!(
- "\u{1f916} Criterion benchmark running (GKE) | [trigger]({})\n\
- **Instance:** `{instance_type}` ({pod_resources}) | `{uname}`\n\
- CPU Details (lscpu)
\n\n\
- ```\n\
- {lscpu}\n\
- ```\n\n\
- \n\n\
+ let machine_details = format_machine_details(&instance_type, &pod_resources, &uname, &lscpu);
+ let running_section = format!(
+ "\u{1f916} **Criterion benchmark running (GKE)**\n\n\
Comparing {changed_display} ({changed_sha}) to {baseline_label} \
[diff](https://github.com/{repo}/compare/{base_sha}..{changed_sha})\n\
BENCH_NAME={bench_name}\n\
BENCH_COMMAND={bench_command_display}\n\
- BENCH_FILTER={bench_filter}\n\
- Results will be posted here when complete{footer}",
- config.comment_url,
+ BENCH_FILTER={bench_filter}\n\n\
+ {machine_details}",
repo = config.repo,
);
- let pr_number = config.pr_number()?;
+ let comment_id = config.comment_id_i64()?;
poster
- .post_comment(&config.repo, pr_number, &running_body)
+ .update_section(&config.repo, comment_id, &running_section)
.await?;
// Compile both in parallel
@@ -175,7 +168,7 @@ pub async fn run(config: &RunnerConfig, poster: &CommentPoster) -> Result<()> {
};
// Compare and post results
- let result_body = if baseline_available {
+ let result_section = if baseline_available {
// Copy baselines into one target dir for critcmp
copy_criterion_baselines(&base_dir, &branch_dir).await;
@@ -188,15 +181,7 @@ pub async fn run(config: &RunnerConfig, poster: &CommentPoster) -> Result<()> {
monitor::format_resource_comment("base (merge-base)", &base_stats.unwrap()),
monitor::format_resource_comment("branch", &branch_stats),
);
- format_result_comment(
- &config.comment_url,
- &report,
- &resource_section,
- &instance_type,
- &pod_resources,
- &lscpu,
- &footer,
- )
+ format_result_section(&report, &resource_section, &machine_details)
} else {
let report = shell::run_command("critcmp", &[bench_branch_name.as_str()], &branch_dir)
.await
@@ -204,18 +189,10 @@ pub async fn run(config: &RunnerConfig, poster: &CommentPoster) -> Result<()> {
let resource_section =
monitor::format_resource_comment("branch", &branch_stats).to_string();
- format_branch_only_result_comment(
- &config.comment_url,
- &report,
- &resource_section,
- &instance_type,
- &pod_resources,
- &lscpu,
- &footer,
- )
+ format_branch_only_result_section(&report, &resource_section, &machine_details)
};
poster
- .post_comment(&config.repo, pr_number, &result_body)
+ .update_section(&config.repo, comment_id, &result_section)
.await?;
Ok(())
@@ -249,24 +226,10 @@ async fn copy_criterion_baselines(base_dir: &Path, branch_dir: &Path) {
}
}
-/// Format the result comment body.
-fn format_result_comment(
- comment_url: &str,
- report: &str,
- resource_section: &str,
- instance_type: &str,
- pod_resources: &str,
- lscpu: &str,
- footer: &str,
-) -> String {
+/// Format the per-job "completed" section for a baseline-comparison run.
+fn format_result_section(report: &str, resource_section: &str, machine_details: &str) -> String {
format!(
- "\u{1f916} Criterion benchmark completed (GKE) | [trigger]({comment_url})\n\n\
- **Instance:** `{instance_type}` ({pod_resources})\n\n\
- CPU Details (lscpu)
\n\n\
- ```\n\
- {lscpu}\n\
- ```\n\n\
- \n\n\
+ "\u{1f916} **Criterion benchmark completed (GKE)**\n\n\
Details
\n\
\n\n\
```\n\
@@ -276,29 +239,19 @@ fn format_result_comment(
\n\n\
Resource Usage
\n\n\
{resource_section}\
- \n\
- {footer}"
+ \n\n\
+ {machine_details}"
)
}
-/// Format the result comment body for branch-only runs (no baseline comparison).
-fn format_branch_only_result_comment(
- comment_url: &str,
+/// Format the per-job "completed" section for branch-only runs.
+fn format_branch_only_result_section(
report: &str,
resource_section: &str,
- instance_type: &str,
- pod_resources: &str,
- lscpu: &str,
- footer: &str,
+ machine_details: &str,
) -> String {
format!(
- "\u{1f916} Criterion benchmark completed (GKE) | [trigger]({comment_url})\n\n\
- **Instance:** `{instance_type}` ({pod_resources})\n\n\
- CPU Details (lscpu)
\n\n\
- ```\n\
- {lscpu}\n\
- ```\n\n\
- \n\n\
+ "\u{1f916} **Criterion benchmark completed (GKE)**\n\n\
**New benchmark — branch-only results (no baseline comparison)**\n\n\
Details
\n\
\n\n\
@@ -309,8 +262,8 @@ fn format_branch_only_result_comment(
\n\n\
Resource Usage
\n\n\
{resource_section}\
- \n\
- {footer}"
+ \n\n\
+ {machine_details}"
)
}
@@ -397,45 +350,42 @@ mod tests {
}
#[test]
- fn result_comment_format() {
- let comment = format_result_comment(
- "https://example.com/comment",
- "test report\n",
- "resources\n",
+ fn result_section_format() {
+ let machine = format_machine_details(
"c4a-standard-48",
"12 vCPU / 65 GiB",
+ "uname",
"lscpu output",
- "",
);
- assert!(comment.contains("Criterion benchmark completed"));
- assert!(comment.contains("[trigger](https://example.com/comment)"));
- assert!(comment.contains("test report"));
- assert!(comment.contains(""));
- assert!(comment.contains("Resource Usage"));
- assert!(comment.contains("c4a-standard-48"));
- assert!(comment.contains("12 vCPU / 65 GiB"));
- assert!(comment.contains("lscpu output"));
+ let section = format_result_section("test report\n", "resources\n", &machine);
+ assert!(section.contains("Criterion benchmark completed"));
+ assert!(section.contains("test report"));
+ assert!(section.contains(""));
+ assert!(section.contains("Resource Usage"));
+ assert!(section.contains("c4a-standard-48"));
+ assert!(section.contains("12 vCPU / 65 GiB"));
+ assert!(section.contains("lscpu output"));
+ assert!(!section.contains("[trigger]"));
}
#[test]
- fn branch_only_result_comment_format() {
- let comment = format_branch_only_result_comment(
- "https://example.com/comment",
- "branch report\n",
- "branch resources\n",
+ fn branch_only_result_section_format() {
+ let machine = format_machine_details(
"c4a-standard-48",
"12 vCPU / 65 GiB",
+ "uname",
"lscpu output",
- "",
);
- assert!(comment.contains("Criterion benchmark completed"));
- assert!(comment.contains("New benchmark — branch-only results"));
- assert!(comment.contains("[trigger](https://example.com/comment)"));
- assert!(comment.contains("branch report"));
- assert!(comment.contains("Resource Usage"));
- assert!(comment.contains("branch resources"));
- assert!(comment.contains("c4a-standard-48"));
- assert!(comment.contains("12 vCPU / 65 GiB"));
- assert!(comment.contains("lscpu output"));
+ let section =
+ format_branch_only_result_section("branch report\n", "branch resources\n", &machine);
+ assert!(section.contains("Criterion benchmark completed"));
+ assert!(section.contains("New benchmark — branch-only results"));
+ assert!(section.contains("branch report"));
+ assert!(section.contains("Resource Usage"));
+ assert!(section.contains("branch resources"));
+ assert!(section.contains("c4a-standard-48"));
+ assert!(section.contains("12 vCPU / 65 GiB"));
+ assert!(section.contains("lscpu output"));
+ assert!(!section.contains("[trigger]"));
}
}
diff --git a/controller/src/runner/bench_standard.rs b/controller/src/runner/bench_standard.rs
index d95aafb..92d181e 100644
--- a/controller/src/runner/bench_standard.rs
+++ b/controller/src/runner/bench_standard.rs
@@ -5,7 +5,6 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use tracing::info;
-use crate::github;
use crate::runner::config::RunnerConfig;
use crate::runner::git;
use crate::runner::monitor::{self, ResourceStats};
@@ -71,12 +70,12 @@ pub async fn run(config: &RunnerConfig, poster: &CommentPoster) -> Result<()> {
"/tmp/base_build.log",
);
- // Post "running" comment
+ // Post "running" section
let uname = shell::uname().await;
let instance_type = shell::node_instance_type().await;
let pod_resources = shell::pod_resources();
let lscpu = shell::lscpu().await;
- let pr_number = config.pr_number()?;
+ let comment_id = config.comment_id_i64()?;
// Resolve display names for the comparison
let changed_display = config.changed_ref.as_deref().unwrap_or(&branch_name);
@@ -88,24 +87,17 @@ pub async fn run(config: &RunnerConfig, poster: &CommentPoster) -> Result<()> {
format!("{} (merge-base)", &base_sha[..7.min(base_sha.len())])
};
- let footer = github::issues_footer(config.runner_repo_url.as_deref());
- let running_body = format!(
- "\u{1f916} Benchmark running (GKE) | [trigger]({})\n\
- **Instance:** `{instance_type}` ({pod_resources}) | `{uname}`\n\
- CPU Details (lscpu)
\n\n\
- ```\n\
- {lscpu}\n\
- ```\n\n\
- \n\n\
+ let machine_details = format_machine_details(&instance_type, &pod_resources, &uname, &lscpu);
+ let running_section = format!(
+ "\u{1f916} **Benchmark running (GKE)**\n\n\
Comparing {changed_display} ({changed_sha}) to {baseline_label} \
[diff](https://github.com/{repo}/compare/{base_sha}..{changed_sha}) \
- using: {benchmarks}\n\
- Results will be posted here when complete{footer}",
- config.comment_url,
+ using: {benchmarks}\n\n\
+ {machine_details}",
repo = config.repo,
);
poster
- .post_comment(&config.repo, pr_number, &running_body)
+ .update_section(&config.repo, comment_id, &running_section)
.await?;
// Wait for builds
@@ -197,22 +189,31 @@ pub async fn run(config: &RunnerConfig, poster: &CommentPoster) -> Result<()> {
.context("bench.sh compare")?;
let resource_section = format_resource_section(&base_stats_list, &branch_stats_list);
- let result_body = format_result_comment(
- &config.comment_url,
- &report,
- &resource_section,
- &instance_type,
- &pod_resources,
- &lscpu,
- &footer,
- );
+ let result_section = format_result_section(&report, &resource_section, &machine_details);
poster
- .post_comment(&config.repo, pr_number, &result_body)
+ .update_section(&config.repo, comment_id, &result_section)
.await?;
Ok(())
}
+/// Single collapsed `` block holding instance/pod/uname/lscpu so the
+/// trigger comment stays compact while the run is still in progress.
+pub(crate) fn format_machine_details(
+ instance_type: &str,
+ pod_resources: &str,
+ uname: &str,
+ lscpu: &str,
+) -> String {
+ format!(
+ "Machine info
\n\n\
+ **Instance:** `{instance_type}` ({pod_resources})\n\n\
+ **uname:** `{uname}`\n\n\
+ ```\n{lscpu}\n```\n\n\
+ "
+ )
+}
+
/// Run a single benchmark on one side (base or branch).
///
/// For TPC-H variants we bypass `bench.sh run` and invoke the prebuilt `dfbench`
@@ -416,24 +417,12 @@ fn format_resource_section(
section
}
-/// Format the result comment body.
-fn format_result_comment(
- comment_url: &str,
- report: &str,
- resource_section: &str,
- instance_type: &str,
- pod_resources: &str,
- lscpu: &str,
- footer: &str,
-) -> String {
+/// Format the per-job "completed" section that gets merged into the trigger
+/// comment. The section is bracketed by the controller-side renderer, so this
+/// only contains the parts specific to a single benchmark run.
+fn format_result_section(report: &str, resource_section: &str, machine_details: &str) -> String {
format!(
- "\u{1f916} Benchmark completed (GKE) | [trigger]({comment_url})\n\n\
- **Instance:** `{instance_type}` ({pod_resources})\n\n\
- CPU Details (lscpu)
\n\n\
- ```\n\
- {lscpu}\n\
- ```\n\n\
- \n\n\
+ "\u{1f916} **Benchmark completed (GKE)**\n\n\
Details
\n\
\n\n\
```\n\
@@ -443,8 +432,8 @@ fn format_result_comment(
\n\n\
Resource Usage
\n\n\
{resource_section}\
- \n\
- {footer}"
+ \n\n\
+ {machine_details}"
)
}
@@ -453,25 +442,35 @@ mod tests {
use super::*;
#[test]
- fn result_comment_format() {
- let comment = format_result_comment(
- "https://example.com/comment",
- "test report\n",
- "resources\n",
+ fn result_section_format() {
+ let machine = format_machine_details(
"c4a-standard-48",
"12 vCPU / 65 GiB",
+ "Linux node-1",
"lscpu output",
- "",
);
- assert!(comment.contains("Benchmark completed"));
- assert!(comment.contains("[trigger](https://example.com/comment)"));
- assert!(comment.contains("test report"));
- assert!(comment.contains(""));
- assert!(comment.contains("Resource Usage"));
- assert!(comment.contains("resources"));
- assert!(comment.contains("c4a-standard-48"));
- assert!(comment.contains("12 vCPU / 65 GiB"));
- assert!(comment.contains("lscpu output"));
+ let section = format_result_section("test report\n", "resources\n", &machine);
+ assert!(section.contains("Benchmark completed"));
+ assert!(section.contains("test report"));
+ assert!(section.contains(""));
+ assert!(section.contains("Resource Usage"));
+ assert!(section.contains("resources"));
+ assert!(section.contains("c4a-standard-48"));
+ assert!(section.contains("12 vCPU / 65 GiB"));
+ assert!(section.contains("Linux node-1"));
+ assert!(section.contains("lscpu output"));
+ // No trigger link — we now edit the trigger comment in place.
+ assert!(!section.contains("[trigger]"));
+ }
+
+ #[test]
+ fn machine_details_collapsed() {
+ let m = format_machine_details("c4a-1", "1 vCPU / 1 GiB", "uname-foo", "lscpu-foo");
+ assert!(m.starts_with(""));
+ assert!(m.contains("Machine info"));
+ assert!(m.contains("c4a-1"));
+ assert!(m.contains("uname-foo"));
+ assert!(m.contains("lscpu-foo"));
}
#[test]
diff --git a/controller/src/runner/config.rs b/controller/src/runner/config.rs
index e6e2e56..0d364f6 100644
--- a/controller/src/runner/config.rs
+++ b/controller/src/runner/config.rs
@@ -114,9 +114,10 @@ impl RunnerConfig {
/// Build the [`CommentPoster`] implied by [`Self::poster_mode`].
pub fn build_poster(&self) -> CommentPoster {
match &self.poster_mode {
- PosterMode::Direct { github_token } => {
- CommentPoster::Direct(GitHubClient::new(github_token))
- }
+ PosterMode::Direct { github_token } => CommentPoster::Direct {
+ client: GitHubClient::new(github_token),
+ runner_repo_url: self.runner_repo_url.clone(),
+ },
PosterMode::Proxy {
controller_url,
job_id,
@@ -129,6 +130,13 @@ impl RunnerConfig {
}
}
+ /// The trigger comment id, parsed from `COMMENT_ID`.
+ pub fn comment_id_i64(&self) -> Result {
+ self.comment_id
+ .parse()
+ .with_context(|| format!("invalid COMMENT_ID: {}", self.comment_id))
+ }
+
/// The repo clone URL.
pub fn repo_url(&self) -> String {
format!("https://github.com/{}.git", self.repo)
diff --git a/controller/src/runner/controller_client.rs b/controller/src/runner/controller_client.rs
index 50ccff7..3df3375 100644
--- a/controller/src/runner/controller_client.rs
+++ b/controller/src/runner/controller_client.rs
@@ -28,12 +28,10 @@ impl ControllerClient {
}
}
- /// Post a comment on the PR associated with this runner's job. `repo` and
- /// `pr_number` are accepted for signature parity with
- /// [`crate::github::GitHubClient::post_comment`] but are ignored — the
- /// controller resolves both from the job's DB row.
+ /// Replace this job's section of the trigger comment. The controller
+ /// merges sibling-job sections and PATCHes the comment.
#[tracing::instrument(skip(self, body), fields(job_id = %self.job_id))]
- pub async fn post_comment(&self, _repo: &str, _pr_number: i64, body: &str) -> Result<()> {
+ pub async fn post_section(&self, body: &str) -> Result<()> {
let url = format!("{}/jobs/{}/comment", self.base_url, self.job_id);
let payload = json!({ "body": body });
diff --git a/controller/src/runner/poster.rs b/controller/src/runner/poster.rs
index 8d0b931..01a063c 100644
--- a/controller/src/runner/poster.rs
+++ b/controller/src/runner/poster.rs
@@ -1,26 +1,54 @@
-//! `CommentPoster` chooses between posting PR comments directly to GitHub
-//! (with a `GITHUB_TOKEN` — used by the scheduled main-tracking workflow)
-//! and proxying through the controller (used by PR-triggered runs, which
-//! have no GitHub creds in the pod).
+//! `CommentPoster` chooses between editing the trigger comment directly on
+//! GitHub (with a `GITHUB_TOKEN` — used by the scheduled main-tracking
+//! workflow) and proxying through the controller (used by PR-triggered runs,
+//! which have no GitHub creds in the pod).
+//!
+//! Each runner job owns a single "section" of the trigger comment (the part
+//! that changes from "running" to "completed"/"failed"). Updating a section
+//! re-renders the trigger comment in place rather than posting a new comment.
-use anyhow::Result;
+use anyhow::{Context, Result};
-use crate::github::GitHubClient;
+use crate::comment_render;
+use crate::github::{self, GitHubClient};
use crate::runner::controller_client::ControllerClient;
#[derive(Clone)]
pub enum CommentPoster {
- /// Post directly to GitHub using a `GITHUB_TOKEN`.
- Direct(GitHubClient),
- /// Proxy through the controller's `POST /jobs/{id}/comment` endpoint.
+ /// Edit comments directly on GitHub using a `GITHUB_TOKEN`. Used by the
+ /// main-tracking workflow where one runner owns the entire trigger
+ /// comment, so no inter-job coordination is required.
+ Direct {
+ client: GitHubClient,
+ runner_repo_url: Option,
+ },
+ /// Proxy through the controller's `POST /jobs/{id}/comment` endpoint —
+ /// the controller serializes sibling-job updates per trigger comment.
Proxy(ControllerClient),
}
impl CommentPoster {
- pub async fn post_comment(&self, repo: &str, pr_number: i64, body: &str) -> Result<()> {
+ /// Set the section this runner contributes to the trigger comment,
+ /// re-rendering the comment so the new section becomes visible.
+ /// Replaces any prior section the same runner posted.
+ pub async fn update_section(&self, repo: &str, comment_id: i64, section: &str) -> Result<()> {
match self {
- Self::Direct(c) => c.post_comment(repo, pr_number, body).await,
- Self::Proxy(c) => c.post_comment(repo, pr_number, body).await,
+ Self::Direct {
+ client,
+ runner_repo_url,
+ } => {
+ let original = client
+ .get_comment_body(repo, comment_id)
+ .await
+ .context("fetch trigger comment")?;
+ let footer = github::issues_footer(runner_repo_url.as_deref());
+ let body = comment_render::render(&original, &[section.to_string()], &footer);
+ client
+ .update_comment(repo, comment_id, &body)
+ .await
+ .context("update trigger comment")
+ }
+ Self::Proxy(c) => c.post_section(section).await,
}
}
}