Skip to main content

perfgate_app/
aggregate.rs

1use anyhow::Context;
2use perfgate_domain::compute_stats;
3use perfgate_types::{HostInfo, RunMeta, RunReceipt};
4use std::fs;
5use std::path::PathBuf;
6
7pub struct AggregateRequest {
8    pub files: Vec<PathBuf>,
9}
10
11pub struct AggregateOutcome {
12    pub receipt: RunReceipt,
13}
14
15pub struct AggregateUseCase;
16
17impl AggregateUseCase {
18    pub fn execute(&self, req: AggregateRequest) -> anyhow::Result<AggregateOutcome> {
19        if req.files.is_empty() {
20            anyhow::bail!("No files provided for aggregation");
21        }
22
23        let mut receipts = Vec::new();
24        for file in &req.files {
25            let content =
26                fs::read_to_string(file).with_context(|| format!("failed to read {:?}", file))?;
27            let receipt: RunReceipt = serde_json::from_str(&content)
28                .with_context(|| format!("failed to parse {:?}", file))?;
29            receipts.push(receipt);
30        }
31
32        // Verify all receipts are for the same bench name
33        let first_bench_name = &receipts[0].bench.name;
34        for r in &receipts {
35            if &r.bench.name != first_bench_name {
36                anyhow::bail!(
37                    "Cannot aggregate receipts for different benchmarks: {} vs {}",
38                    first_bench_name,
39                    r.bench.name
40                );
41            }
42        }
43
44        let mut combined_samples = Vec::new();
45        for r in &receipts {
46            combined_samples.extend(r.samples.clone());
47        }
48
49        // We assume work_units is consistent across the receipts.
50        // If they differ, we take the first one.
51        let work_units = receipts[0].bench.work_units;
52
53        let stats = compute_stats(&combined_samples, work_units)?;
54
55        // Update bench metadata.
56        let mut bench = receipts[0].bench.clone();
57        bench.repeat = combined_samples.len() as u32;
58
59        let receipt = RunReceipt {
60            schema: perfgate_types::RUN_SCHEMA_V1.to_string(),
61            tool: receipts[0].tool.clone(),
62            run: RunMeta {
63                id: uuid::Uuid::new_v4().to_string(),
64                started_at: receipts[0].run.started_at.clone(),
65                ended_at: receipts.last().unwrap().run.ended_at.clone(),
66                host: HostInfo {
67                    os: "fleet".to_string(),
68                    arch: "mixed".to_string(),
69                    cpu_count: None,
70                    memory_bytes: None,
71                    hostname_hash: None,
72                },
73            },
74            bench,
75            samples: combined_samples,
76            stats,
77        };
78
79        Ok(AggregateOutcome { receipt })
80    }
81}