Skip to content

Rollup of 7 pull requests #138630

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Mar 18, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
50c659f
Clarify "owned data" in E0515.md
hkBst Mar 14, 2025
899eed1
Refactor metrics generation step
Kobzol Mar 14, 2025
301c384
Do not fail the build if metrics postprocessing or DataDog upload fails
Kobzol Mar 14, 2025
09d44a4
Print metrics postprocessing to stdout
Kobzol Mar 14, 2025
e757dea
Refactor metrics and analysis in citool to distinguish them better
Kobzol Mar 15, 2025
413fd52
Print number of found test diffs
Kobzol Mar 15, 2025
6c24c9c
Use first-level heading for test differences header
Kobzol Mar 15, 2025
30d5757
Print test diffs into GitHub summary
Kobzol Mar 15, 2025
634a11e
Add bootstrap stage to test names
Kobzol Mar 15, 2025
232be86
Add a helper function for outputting details
Kobzol Mar 15, 2025
b4cccf0
Put test differences into a `<details>` section and add better explan…
Kobzol Mar 15, 2025
e845318
Do not error out on missing parent metrics
Kobzol Mar 15, 2025
4801dba
Reformat code
Kobzol Mar 15, 2025
7c792e2
Only use `DIST_TRY_BUILD` for try jobs that were not selected explicitly
Kobzol Mar 15, 2025
b2fda93
Add a note to rustc-dev-guide
Kobzol Mar 16, 2025
6698c26
Fix `is_relevant_impl`.
nnethercote Mar 12, 2025
b148106
Fix ICE: attempted to remap an already remapped filename
charmitro Mar 16, 2025
0ee99cf
rustc_target: Add target feature constraints for LoongArch
heiher Mar 17, 2025
36f6bc5
Flatten `if`s in `rustc_codegen_ssa`
yotamofek Mar 17, 2025
f2ddbcd
Move `hir::Item::ident` into `hir::ItemKind`.
nnethercote Mar 6, 2025
c9d3147
Small review improvements
Kobzol Mar 17, 2025
e1acc68
Rollup merge of #138384 - nnethercote:hir-ItemKind-idents, r=fmease
matthiaskrgr Mar 17, 2025
01062ba
Rollup merge of #138508 - hkBst:patch-3, r=wesleywiser
matthiaskrgr Mar 17, 2025
5786233
Rollup merge of #138531 - Kobzol:test-diff-try-build, r=marcoieni
matthiaskrgr Mar 17, 2025
c19ce9d
Rollup merge of #138533 - Kobzol:try-job-auto-tests, r=marcoieni
matthiaskrgr Mar 17, 2025
cd4dfd7
Rollup merge of #138556 - charmitro:already-remapped-filename, r=Guil…
matthiaskrgr Mar 17, 2025
aa53a72
Rollup merge of #138608 - heiher:issue-116344, r=RalfJung
matthiaskrgr Mar 17, 2025
597500d
Rollup merge of #138619 - yotamofek:pr/codegen_ssa/flatten-ifs, r=lcnr
matthiaskrgr Mar 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor metrics and analysis in citool to distinguish them better
  • Loading branch information
Kobzol committed Mar 15, 2025
commit e757deab233aea31612c593773f242b6b9c6e386
204 changes: 118 additions & 86 deletions src/ci/citool/src/merge_report.rs → src/ci/citool/src/analysis.rs
Original file line number Diff line number Diff line change
@@ -1,97 +1,135 @@
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use crate::metrics;
use crate::metrics::{JobMetrics, JobName, get_test_suites};
use crate::utils::pluralize;
use build_helper::metrics::{
BuildStep, JsonRoot, TestOutcome, TestSuite, TestSuiteMetadata, format_build_steps,
};
use std::collections::{BTreeMap, HashMap, HashSet};

pub fn output_bootstrap_stats(metrics: &JsonRoot) {
if !metrics.invocations.is_empty() {
println!("# Bootstrap steps");
record_bootstrap_step_durations(&metrics);
record_test_suites(&metrics);
}
}

use anyhow::Context;
use build_helper::metrics::{JsonRoot, TestOutcome, TestSuiteMetadata};
fn record_bootstrap_step_durations(metrics: &JsonRoot) {
for invocation in &metrics.invocations {
let step = BuildStep::from_invocation(invocation);
let table = format_build_steps(&step);
eprintln!("Step `{}`\n{table}\n", invocation.cmdline);
println!(
r"<details>
<summary>{}</summary>
<pre><code>{table}</code></pre>
</details>
",
invocation.cmdline
);
}
eprintln!("Recorded {} bootstrap invocation(s)", metrics.invocations.len());
}

use crate::jobs::JobDatabase;
use crate::metrics::get_test_suites;
fn record_test_suites(metrics: &JsonRoot) {
let suites = metrics::get_test_suites(&metrics);

type Sha = String;
type JobName = String;
if !suites.is_empty() {
let aggregated = aggregate_test_suites(&suites);
let table = render_table(aggregated);
println!("\n# Test results\n");
println!("{table}");
} else {
eprintln!("No test suites found in metrics");
}
}

/// Computes a post merge CI analysis report between the `parent` and `current` commits.
pub fn post_merge_report(job_db: JobDatabase, parent: Sha, current: Sha) -> anyhow::Result<()> {
let jobs = download_all_metrics(&job_db, &parent, &current)?;
let aggregated_test_diffs = aggregate_test_diffs(&jobs)?;
fn render_table(suites: BTreeMap<String, TestSuiteRecord>) -> String {
use std::fmt::Write;

println!("Comparing {parent} (base) -> {current} (this PR)\n");
report_test_diffs(aggregated_test_diffs);
let mut table = "| Test suite | Passed ✅ | Ignored 🚫 | Failed ❌ |\n".to_string();
writeln!(table, "|:------|------:|------:|------:|").unwrap();

Ok(())
}
fn compute_pct(value: f64, total: f64) -> f64 {
if total == 0.0 { 0.0 } else { value / total }
}

struct JobMetrics {
parent: Option<JsonRoot>,
current: JsonRoot,
}
fn write_row(
buffer: &mut String,
name: &str,
record: &TestSuiteRecord,
surround: &str,
) -> std::fmt::Result {
let TestSuiteRecord { passed, ignored, failed } = record;
let total = (record.passed + record.ignored + record.failed) as f64;
let passed_pct = compute_pct(*passed as f64, total) * 100.0;
let ignored_pct = compute_pct(*ignored as f64, total) * 100.0;
let failed_pct = compute_pct(*failed as f64, total) * 100.0;

write!(buffer, "| {surround}{name}{surround} |")?;
write!(buffer, " {surround}{passed} ({passed_pct:.0}%){surround} |")?;
write!(buffer, " {surround}{ignored} ({ignored_pct:.0}%){surround} |")?;
writeln!(buffer, " {surround}{failed} ({failed_pct:.0}%){surround} |")?;

Ok(())
}

/// Download before/after metrics for all auto jobs in the job database.
fn download_all_metrics(
job_db: &JobDatabase,
parent: &str,
current: &str,
) -> anyhow::Result<HashMap<JobName, JobMetrics>> {
let mut jobs = HashMap::default();

for job in &job_db.auto_jobs {
eprintln!("Downloading metrics of job {}", job.name);
let metrics_parent = match download_job_metrics(&job.name, parent) {
Ok(metrics) => Some(metrics),
Err(error) => {
eprintln!(
r#"Did not find metrics for job `{}` at `{}`: {error:?}.
Maybe it was newly added?"#,
job.name, parent
);
None
}
};
let metrics_current = download_job_metrics(&job.name, current)?;
jobs.insert(
job.name.clone(),
JobMetrics { parent: metrics_parent, current: metrics_current },
);
let mut total = TestSuiteRecord::default();
for (name, record) in suites {
write_row(&mut table, &name, &record, "").unwrap();
total.passed += record.passed;
total.ignored += record.ignored;
total.failed += record.failed;
}
Ok(jobs)
write_row(&mut table, "Total", &total, "**").unwrap();
table
}

/// Downloads job metrics of the given job for the given commit.
/// Caches the result on the local disk.
fn download_job_metrics(job_name: &str, sha: &str) -> anyhow::Result<JsonRoot> {
let cache_path = PathBuf::from(".citool-cache").join(sha).join(job_name).join("metrics.json");
if let Some(cache_entry) =
std::fs::read_to_string(&cache_path).ok().and_then(|data| serde_json::from_str(&data).ok())
{
return Ok(cache_entry);
}
/// Computes a post merge CI analysis report of test differences
/// between the `parent` and `current` commits.
pub fn output_test_diffs(job_metrics: HashMap<JobName, JobMetrics>) {
let aggregated_test_diffs = aggregate_test_diffs(&job_metrics);
report_test_diffs(aggregated_test_diffs);
}

let url = get_metrics_url(job_name, sha);
let mut response = ureq::get(&url).call()?;
if !response.status().is_success() {
return Err(anyhow::anyhow!(
"Cannot fetch metrics from {url}: {}\n{}",
response.status(),
response.body_mut().read_to_string()?
));
}
let data: JsonRoot = response
.body_mut()
.read_json()
.with_context(|| anyhow::anyhow!("cannot deserialize metrics from {url}"))?;

// Ignore errors if cache cannot be created
if std::fs::create_dir_all(cache_path.parent().unwrap()).is_ok() {
if let Ok(serialized) = serde_json::to_string(&data) {
let _ = std::fs::write(&cache_path, &serialized);
#[derive(Default)]
struct TestSuiteRecord {
passed: u64,
ignored: u64,
failed: u64,
}

fn test_metadata_name(metadata: &TestSuiteMetadata) -> String {
match metadata {
TestSuiteMetadata::CargoPackage { crates, stage, .. } => {
format!("{} (stage {stage})", crates.join(", "))
}
TestSuiteMetadata::Compiletest { suite, stage, .. } => {
format!("{suite} (stage {stage})")
}
}
Ok(data)
}

fn get_metrics_url(job_name: &str, sha: &str) -> String {
let suffix = if job_name.ends_with("-alt") { "-alt" } else { "" };
format!("https://ci-artifacts.rust-lang.org/rustc-builds{suffix}/{sha}/metrics-{job_name}.json")
fn aggregate_test_suites(suites: &[&TestSuite]) -> BTreeMap<String, TestSuiteRecord> {
let mut records: BTreeMap<String, TestSuiteRecord> = BTreeMap::new();
for suite in suites {
let name = test_metadata_name(&suite.metadata);
let record = records.entry(name).or_default();
for test in &suite.tests {
match test.outcome {
TestOutcome::Passed => {
record.passed += 1;
}
TestOutcome::Failed => {
record.failed += 1;
}
TestOutcome::Ignored { .. } => {
record.ignored += 1;
}
}
}
}
records
}

/// Represents a difference in the outcome of tests between a base and a current commit.
Expand All @@ -101,9 +139,7 @@ struct AggregatedTestDiffs {
diffs: HashMap<TestDiff, Vec<JobName>>,
}

fn aggregate_test_diffs(
jobs: &HashMap<JobName, JobMetrics>,
) -> anyhow::Result<AggregatedTestDiffs> {
fn aggregate_test_diffs(jobs: &HashMap<JobName, JobMetrics>) -> AggregatedTestDiffs {
let mut diffs: HashMap<TestDiff, Vec<JobName>> = HashMap::new();

// Aggregate test suites
Expand All @@ -117,7 +153,7 @@ fn aggregate_test_diffs(
}
}

Ok(AggregatedTestDiffs { diffs })
AggregatedTestDiffs { diffs }
}

#[derive(Eq, PartialEq, Hash, Debug)]
Expand Down Expand Up @@ -312,7 +348,3 @@ fn report_test_diffs(diff: AggregatedTestDiffs) {
);
}
}

fn pluralize(text: &str, count: usize) -> String {
if count == 1 { text.to_string() } else { format!("{text}s") }
}
17 changes: 11 additions & 6 deletions src/ci/citool/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod analysis;
mod cpu_usage;
mod datadog;
mod jobs;
mod merge_report;
mod metrics;
mod utils;

Expand All @@ -14,12 +14,13 @@ use clap::Parser;
use jobs::JobDatabase;
use serde_yaml::Value;

use crate::analysis::output_test_diffs;
use crate::cpu_usage::load_cpu_usage;
use crate::datadog::upload_datadog_metric;
use crate::jobs::RunType;
use crate::merge_report::post_merge_report;
use crate::metrics::postprocess_metrics;
use crate::metrics::{download_auto_job_metrics, load_metrics};
use crate::utils::load_env_var;
use analysis::output_bootstrap_stats;

const CI_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/..");
const DOCKER_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../docker");
Expand Down Expand Up @@ -209,10 +210,14 @@ fn main() -> anyhow::Result<()> {
upload_ci_metrics(&cpu_usage_csv)?;
}
Args::PostprocessMetrics { metrics_path } => {
postprocess_metrics(&metrics_path)?;
let metrics = load_metrics(&metrics_path)?;
output_bootstrap_stats(&metrics);
}
Args::PostMergeReport { current: commit, parent } => {
post_merge_report(load_db(default_jobs_file)?, parent, commit)?;
Args::PostMergeReport { current, parent } => {
let db = load_db(default_jobs_file)?;
let metrics = download_auto_job_metrics(&db, &parent, &current)?;
println!("Comparing {parent} (base) -> {current} (this PR)\n");
output_test_diffs(metrics);
}
}

Expand Down
Loading