perfgate_app/
aggregate.rs1use 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 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 let work_units = receipts[0].bench.work_units;
52
53 let stats = compute_stats(&combined_samples, work_units)?;
54
55 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}