Skip to content

Commit 3dc4ea8

Browse files
committed
create bench_cmp command
1 parent 8df1e5d commit 3dc4ea8

File tree

3 files changed

+150
-1
lines changed

3 files changed

+150
-1
lines changed

collector/src/bin/collector.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::{str, time::Instant};
1818
use anyhow::Context;
1919
use clap::builder::TypedValueParser;
2020
use clap::{Arg, Parser};
21+
use collector::compare::compare_artifacts;
2122
use humansize::{format_size, BINARY};
2223
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
2324
use tabled::builder::Builder;
@@ -628,6 +629,18 @@ enum Commands {
628629
#[command(flatten)]
629630
db: DbOption,
630631
},
632+
633+
/// Displays diff between two local bench results.
634+
BenchCmp {
635+
#[command(flatten)]
636+
db: DbOption,
637+
638+
/// The name of the base artifact to be compared.
639+
base: String,
640+
641+
/// The name of the modified artifact to be compared.
642+
modified: String,
643+
},
631644
}
632645

633646
#[derive(Debug, clap::Parser)]
@@ -1187,6 +1200,13 @@ Make sure to modify `{dir}/perf-config.json` if the category/artifact don't matc
11871200
println!("Data of artifact {name} were removed");
11881201
Ok(0)
11891202
}
1203+
Commands::BenchCmp { db, base, modified } => {
1204+
let pool = Pool::open(&db.db);
1205+
let rt = build_async_runtime();
1206+
let conn = rt.block_on(pool.connection());
1207+
rt.block_on(compare_artifacts(conn, base, modified))?;
1208+
Ok(0)
1209+
}
11901210
}
11911211
}
11921212

@@ -1736,7 +1756,6 @@ fn bench_compile(
17361756
category,
17371757
));
17381758
print_intro();
1739-
17401759
let mut processor = BenchProcessor::new(
17411760
tx.conn(),
17421761
benchmark_name,

collector/src/compare.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use std::sync::Arc;
2+
3+
use database::{
4+
metric::Metric,
5+
selector::{BenchmarkQuery, CompileBenchmarkQuery},
6+
ArtifactId, Connection,
7+
};
8+
use tabled::{Table, Tabled};
9+
10+
const SIGNIFICANCE_THRESHOLD: f64 = 0.002;
11+
12+
/// Compare 2 artifacts and print the result.
13+
pub async fn compare_artifacts(
14+
mut conn: Box<dyn Connection>,
15+
base: String,
16+
modified: String,
17+
) -> anyhow::Result<()> {
18+
let index = database::Index::load(&mut *conn).await;
19+
20+
let query = CompileBenchmarkQuery::default()
21+
.metric(database::selector::Selector::One(Metric::InstructionsUser));
22+
let resp = query
23+
.execute(
24+
&mut *conn,
25+
&index,
26+
Arc::new(vec![ArtifactId::Tag(base), ArtifactId::Tag(modified)]),
27+
)
28+
.await
29+
.unwrap();
30+
31+
let tuple_pstats = resp
32+
.into_iter()
33+
.map(|resp| {
34+
let points = resp.series.points.collect::<Vec<_>>();
35+
(points[0], points[1])
36+
})
37+
.collect::<Vec<_>>();
38+
39+
#[derive(Tabled)]
40+
struct Regression {
41+
count: usize,
42+
#[tabled(display_with = "display_range")]
43+
range: (Option<f64>, Option<f64>),
44+
#[tabled(display_with = "display_mean")]
45+
mean: Option<f64>,
46+
}
47+
48+
fn format_value(value: Option<f64>) -> String {
49+
match value {
50+
Some(value) => format!("{:+.2}%", value),
51+
None => "-".to_string(),
52+
}
53+
}
54+
55+
fn display_range(&(min, max): &(Option<f64>, Option<f64>)) -> String {
56+
format!("[{}, {}]", &format_value(min), &format_value(max))
57+
}
58+
59+
fn display_mean(value: &Option<f64>) -> String {
60+
match value {
61+
Some(value) => format!("{:+.2}%", value),
62+
None => "-".to_string(),
63+
}
64+
}
65+
66+
impl From<&Vec<f64>> for Regression {
67+
fn from(value: &Vec<f64>) -> Self {
68+
let min = value.iter().copied().min_by(|a, b| a.total_cmp(b));
69+
let max = value.iter().copied().max_by(|a, b| a.total_cmp(b));
70+
let count = value.len();
71+
72+
Regression {
73+
range: (min, max),
74+
count,
75+
mean: if count == 0 {
76+
None
77+
} else {
78+
Some(value.iter().sum::<f64>() / count as f64)
79+
},
80+
}
81+
}
82+
}
83+
84+
let change = tuple_pstats
85+
.iter()
86+
.filter_map(|&(a, b)| match (a, b) {
87+
(Some(a), Some(b)) => {
88+
if a == 0.0 {
89+
None
90+
} else {
91+
Some((b - a) / a)
92+
}
93+
}
94+
(_, _) => None,
95+
})
96+
.filter(|c| c.abs() >= SIGNIFICANCE_THRESHOLD)
97+
.collect::<Vec<_>>();
98+
let negative_change = change
99+
.iter()
100+
.copied()
101+
.filter(|&c| c < 0.0)
102+
.collect::<Vec<_>>();
103+
let positive_change = change
104+
.iter()
105+
.copied()
106+
.filter(|&c| c > 0.0)
107+
.collect::<Vec<_>>();
108+
109+
#[derive(Tabled)]
110+
struct NamedRegression {
111+
name: String,
112+
#[tabled(inline)]
113+
regression: Regression,
114+
}
115+
116+
let regressions = [negative_change, positive_change, change]
117+
.into_iter()
118+
.map(|c| Regression::from(&c))
119+
.zip(["❌", "✅", "✅, ❌"])
120+
.map(|(c, label)| NamedRegression {
121+
name: label.to_string(),
122+
regression: c,
123+
})
124+
.collect::<Vec<_>>();
125+
126+
println!("{}", Table::new(regressions));
127+
128+
Ok(())
129+
}

collector/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod api;
99
pub mod artifact_stats;
1010
pub mod cargo;
1111
pub mod codegen;
12+
pub mod compare;
1213
pub mod compile;
1314
pub mod runtime;
1415
pub mod toolchain;

0 commit comments

Comments
 (0)