1use crate::{
2 data::{MeasurementData, ReductionFunc},
3 measurement_retrieval::{self, summarize_measurements},
4 stats,
5};
6use anyhow::{anyhow, bail, Result};
7use itertools::Itertools;
8use std::iter;
9
10pub fn audit(
11 measurement: &str,
12 max_count: usize,
13 min_count: u16,
14 selectors: &[(String, String)],
15 summarize_by: ReductionFunc,
16 sigma: f64,
17) -> Result<()> {
18 let all = measurement_retrieval::walk_commits(max_count)?;
19
20 let filter_by = |m: &MeasurementData| {
21 m.name == measurement
22 && selectors
23 .iter()
24 .all(|s| m.key_values.get(&s.0).map(|v| *v == s.1).unwrap_or(false))
25 };
26
27 let mut aggregates = summarize_measurements(all, &summarize_by, &filter_by);
28
29 let head = aggregates
30 .next()
31 .ok_or(anyhow!("No commit at HEAD"))
32 .and_then(|s| {
33 eprintln!("Head measurement is: {s:?}");
34 s.and_then(|cs| {
35 cs.measurement
36 .map(|m| m.val)
37 .ok_or(anyhow!("No measurement for HEAD."))
38 })
39 })?;
40
41 let tail: Vec<_> = aggregates
42 .filter_map_ok(|cs| cs.measurement.map(|m| m.val))
43 .take(max_count)
44 .try_collect()?;
45
46 let head_summary = stats::aggregate_measurements(iter::once(head));
47 let tail_summary = stats::aggregate_measurements(tail.into_iter());
48
49 if tail_summary.len < min_count.into() {
50 let number_measurements = tail_summary.len;
52 let plural_s = if number_measurements > 1 { "s" } else { "" };
53 eprintln!("Only {number_measurements} measurement{plural_s} found. Less than requested min_measurements of {min_count}. Skipping test.");
54 return Ok(());
55 }
56
57 if head_summary.significantly_different_from(&tail_summary, sigma) {
58 bail!(
60 "HEAD differs significantly from tail measurements.\nHead: {}\nTail: {}",
61 &head_summary,
62 &tail_summary
63 );
64 }
65
66 Ok(())
67}