Skip to content

Commit e757dea

Browse files
committed
Refactor metrics and analysis in citool to distinguish them better
1 parent 09d44a4 commit e757dea

File tree

4 files changed

+196
-219
lines changed

4 files changed

+196
-219
lines changed

src/ci/citool/src/merge_report.rs renamed to src/ci/citool/src/analysis.rs

Lines changed: 118 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,135 @@
1-
use std::collections::{HashMap, HashSet};
2-
use std::path::PathBuf;
1+
use crate::metrics;
2+
use crate::metrics::{JobMetrics, JobName, get_test_suites};
3+
use crate::utils::pluralize;
4+
use build_helper::metrics::{
5+
BuildStep, JsonRoot, TestOutcome, TestSuite, TestSuiteMetadata, format_build_steps,
6+
};
7+
use std::collections::{BTreeMap, HashMap, HashSet};
8+
9+
pub fn output_bootstrap_stats(metrics: &JsonRoot) {
10+
if !metrics.invocations.is_empty() {
11+
println!("# Bootstrap steps");
12+
record_bootstrap_step_durations(&metrics);
13+
record_test_suites(&metrics);
14+
}
15+
}
316

4-
use anyhow::Context;
5-
use build_helper::metrics::{JsonRoot, TestOutcome, TestSuiteMetadata};
17+
fn record_bootstrap_step_durations(metrics: &JsonRoot) {
18+
for invocation in &metrics.invocations {
19+
let step = BuildStep::from_invocation(invocation);
20+
let table = format_build_steps(&step);
21+
eprintln!("Step `{}`\n{table}\n", invocation.cmdline);
22+
println!(
23+
r"<details>
24+
<summary>{}</summary>
25+
<pre><code>{table}</code></pre>
26+
</details>
27+
",
28+
invocation.cmdline
29+
);
30+
}
31+
eprintln!("Recorded {} bootstrap invocation(s)", metrics.invocations.len());
32+
}
633

7-
use crate::jobs::JobDatabase;
8-
use crate::metrics::get_test_suites;
34+
fn record_test_suites(metrics: &JsonRoot) {
35+
let suites = metrics::get_test_suites(&metrics);
936

10-
type Sha = String;
11-
type JobName = String;
37+
if !suites.is_empty() {
38+
let aggregated = aggregate_test_suites(&suites);
39+
let table = render_table(aggregated);
40+
println!("\n# Test results\n");
41+
println!("{table}");
42+
} else {
43+
eprintln!("No test suites found in metrics");
44+
}
45+
}
1246

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

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

21-
Ok(())
22-
}
53+
fn compute_pct(value: f64, total: f64) -> f64 {
54+
if total == 0.0 { 0.0 } else { value / total }
55+
}
2356

24-
struct JobMetrics {
25-
parent: Option<JsonRoot>,
26-
current: JsonRoot,
27-
}
57+
fn write_row(
58+
buffer: &mut String,
59+
name: &str,
60+
record: &TestSuiteRecord,
61+
surround: &str,
62+
) -> std::fmt::Result {
63+
let TestSuiteRecord { passed, ignored, failed } = record;
64+
let total = (record.passed + record.ignored + record.failed) as f64;
65+
let passed_pct = compute_pct(*passed as f64, total) * 100.0;
66+
let ignored_pct = compute_pct(*ignored as f64, total) * 100.0;
67+
let failed_pct = compute_pct(*failed as f64, total) * 100.0;
68+
69+
write!(buffer, "| {surround}{name}{surround} |")?;
70+
write!(buffer, " {surround}{passed} ({passed_pct:.0}%){surround} |")?;
71+
write!(buffer, " {surround}{ignored} ({ignored_pct:.0}%){surround} |")?;
72+
writeln!(buffer, " {surround}{failed} ({failed_pct:.0}%){surround} |")?;
73+
74+
Ok(())
75+
}
2876

29-
/// Download before/after metrics for all auto jobs in the job database.
30-
fn download_all_metrics(
31-
job_db: &JobDatabase,
32-
parent: &str,
33-
current: &str,
34-
) -> anyhow::Result<HashMap<JobName, JobMetrics>> {
35-
let mut jobs = HashMap::default();
36-
37-
for job in &job_db.auto_jobs {
38-
eprintln!("Downloading metrics of job {}", job.name);
39-
let metrics_parent = match download_job_metrics(&job.name, parent) {
40-
Ok(metrics) => Some(metrics),
41-
Err(error) => {
42-
eprintln!(
43-
r#"Did not find metrics for job `{}` at `{}`: {error:?}.
44-
Maybe it was newly added?"#,
45-
job.name, parent
46-
);
47-
None
48-
}
49-
};
50-
let metrics_current = download_job_metrics(&job.name, current)?;
51-
jobs.insert(
52-
job.name.clone(),
53-
JobMetrics { parent: metrics_parent, current: metrics_current },
54-
);
77+
let mut total = TestSuiteRecord::default();
78+
for (name, record) in suites {
79+
write_row(&mut table, &name, &record, "").unwrap();
80+
total.passed += record.passed;
81+
total.ignored += record.ignored;
82+
total.failed += record.failed;
5583
}
56-
Ok(jobs)
84+
write_row(&mut table, "Total", &total, "**").unwrap();
85+
table
5786
}
5887

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

69-
let url = get_metrics_url(job_name, sha);
70-
let mut response = ureq::get(&url).call()?;
71-
if !response.status().is_success() {
72-
return Err(anyhow::anyhow!(
73-
"Cannot fetch metrics from {url}: {}\n{}",
74-
response.status(),
75-
response.body_mut().read_to_string()?
76-
));
77-
}
78-
let data: JsonRoot = response
79-
.body_mut()
80-
.read_json()
81-
.with_context(|| anyhow::anyhow!("cannot deserialize metrics from {url}"))?;
82-
83-
// Ignore errors if cache cannot be created
84-
if std::fs::create_dir_all(cache_path.parent().unwrap()).is_ok() {
85-
if let Ok(serialized) = serde_json::to_string(&data) {
86-
let _ = std::fs::write(&cache_path, &serialized);
95+
#[derive(Default)]
96+
struct TestSuiteRecord {
97+
passed: u64,
98+
ignored: u64,
99+
failed: u64,
100+
}
101+
102+
fn test_metadata_name(metadata: &TestSuiteMetadata) -> String {
103+
match metadata {
104+
TestSuiteMetadata::CargoPackage { crates, stage, .. } => {
105+
format!("{} (stage {stage})", crates.join(", "))
106+
}
107+
TestSuiteMetadata::Compiletest { suite, stage, .. } => {
108+
format!("{suite} (stage {stage})")
87109
}
88110
}
89-
Ok(data)
90111
}
91112

92-
fn get_metrics_url(job_name: &str, sha: &str) -> String {
93-
let suffix = if job_name.ends_with("-alt") { "-alt" } else { "" };
94-
format!("https://ci-artifacts.rust-lang.org/rustc-builds{suffix}/{sha}/metrics-{job_name}.json")
113+
fn aggregate_test_suites(suites: &[&TestSuite]) -> BTreeMap<String, TestSuiteRecord> {
114+
let mut records: BTreeMap<String, TestSuiteRecord> = BTreeMap::new();
115+
for suite in suites {
116+
let name = test_metadata_name(&suite.metadata);
117+
let record = records.entry(name).or_default();
118+
for test in &suite.tests {
119+
match test.outcome {
120+
TestOutcome::Passed => {
121+
record.passed += 1;
122+
}
123+
TestOutcome::Failed => {
124+
record.failed += 1;
125+
}
126+
TestOutcome::Ignored { .. } => {
127+
record.ignored += 1;
128+
}
129+
}
130+
}
131+
}
132+
records
95133
}
96134

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

104-
fn aggregate_test_diffs(
105-
jobs: &HashMap<JobName, JobMetrics>,
106-
) -> anyhow::Result<AggregatedTestDiffs> {
142+
fn aggregate_test_diffs(jobs: &HashMap<JobName, JobMetrics>) -> AggregatedTestDiffs {
107143
let mut diffs: HashMap<TestDiff, Vec<JobName>> = HashMap::new();
108144

109145
// Aggregate test suites
@@ -117,7 +153,7 @@ fn aggregate_test_diffs(
117153
}
118154
}
119155

120-
Ok(AggregatedTestDiffs { diffs })
156+
AggregatedTestDiffs { diffs }
121157
}
122158

123159
#[derive(Eq, PartialEq, Hash, Debug)]
@@ -312,7 +348,3 @@ fn report_test_diffs(diff: AggregatedTestDiffs) {
312348
);
313349
}
314350
}
315-
316-
fn pluralize(text: &str, count: usize) -> String {
317-
if count == 1 { text.to_string() } else { format!("{text}s") }
318-
}

src/ci/citool/src/main.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
mod analysis;
12
mod cpu_usage;
23
mod datadog;
34
mod jobs;
4-
mod merge_report;
55
mod metrics;
66
mod utils;
77

@@ -14,12 +14,13 @@ use clap::Parser;
1414
use jobs::JobDatabase;
1515
use serde_yaml::Value;
1616

17+
use crate::analysis::output_test_diffs;
1718
use crate::cpu_usage::load_cpu_usage;
1819
use crate::datadog::upload_datadog_metric;
1920
use crate::jobs::RunType;
20-
use crate::merge_report::post_merge_report;
21-
use crate::metrics::postprocess_metrics;
21+
use crate::metrics::{download_auto_job_metrics, load_metrics};
2222
use crate::utils::load_env_var;
23+
use analysis::output_bootstrap_stats;
2324

2425
const CI_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/..");
2526
const DOCKER_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../docker");
@@ -209,10 +210,14 @@ fn main() -> anyhow::Result<()> {
209210
upload_ci_metrics(&cpu_usage_csv)?;
210211
}
211212
Args::PostprocessMetrics { metrics_path } => {
212-
postprocess_metrics(&metrics_path)?;
213+
let metrics = load_metrics(&metrics_path)?;
214+
output_bootstrap_stats(&metrics);
213215
}
214-
Args::PostMergeReport { current: commit, parent } => {
215-
post_merge_report(load_db(default_jobs_file)?, parent, commit)?;
216+
Args::PostMergeReport { current, parent } => {
217+
let db = load_db(default_jobs_file)?;
218+
let metrics = download_auto_job_metrics(&db, &parent, &current)?;
219+
println!("Comparing {parent} (base) -> {current} (this PR)\n");
220+
output_test_diffs(metrics);
216221
}
217222
}
218223

0 commit comments

Comments
 (0)