Skip to content

Commit 4e745e0

Browse files
committed
feat: parse go test json output
1 parent 1f011ed commit 4e745e0

6 files changed

Lines changed: 190 additions & 0 deletions

File tree

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
src/run/runner/wall_time/golang/testdata/fuego.txt filter=lfs diff=lfs merge=lfs -text
2+
src/run/runner/wall_time/golang/testdata/simple.txt filter=lfs diff=lfs merge=lfs -text
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod parser;
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
use std::collections::HashMap;
2+
3+
use crate::prelude::*;
4+
use itertools::Itertools;
5+
use regex::Regex;
6+
use serde::{Deserialize, Serialize};
7+
8+
#[derive(Debug, Clone, Serialize, Deserialize)]
9+
pub struct RawTestOutput {
10+
#[serde(rename = "Time")]
11+
pub time: Option<String>,
12+
#[serde(rename = "Action")]
13+
pub action: String,
14+
#[serde(rename = "Package")]
15+
pub package: String,
16+
#[serde(rename = "Test")]
17+
pub test: Option<String>,
18+
#[serde(rename = "Output")]
19+
pub output: Option<String>,
20+
#[serde(rename = "Elapsed")]
21+
pub elapsed: Option<f64>,
22+
}
23+
24+
pub struct RawOutput {
25+
name: String,
26+
time: f64,
27+
iters: u64,
28+
}
29+
30+
impl RawOutput {
31+
fn parse_output(line: &str) -> Result<Option<RawOutput>> {
32+
lazy_static::lazy_static! {
33+
static ref BENCHMARK_REGEX: Regex = Regex::new(
34+
r"^(Benchmark[\w/]+)(?:-\d+)?\s+(\d+)\s+([0-9.]+)\s*ns/op"
35+
).unwrap();
36+
}
37+
38+
if let Some(captures) = BENCHMARK_REGEX.captures(line.trim()) {
39+
let name = captures
40+
.get(1)
41+
.context("Failed to get benchmark name")?
42+
.as_str()
43+
.to_string();
44+
let iters: u64 = captures
45+
.get(2)
46+
.context("Failed to get iterations")?
47+
.as_str()
48+
.parse()?;
49+
let time: f64 = captures
50+
.get(3)
51+
.context("Failed to get time")?
52+
.as_str()
53+
.parse()?;
54+
55+
Ok(Some(RawOutput { name, time, iters }))
56+
} else {
57+
Ok(None)
58+
}
59+
}
60+
61+
pub fn parse(output: &str) -> Result<Vec<(String, Self)>> {
62+
let mut results = Vec::new();
63+
for line in output.lines() {
64+
let event: RawTestOutput = serde_json::from_str(line)?;
65+
66+
if event.action != "output" {
67+
continue;
68+
}
69+
let Some(output_text) = &event.output else {
70+
continue;
71+
};
72+
let Some(measurement) = Self::parse_output(output_text)? else {
73+
continue;
74+
};
75+
76+
results.push((event.package, measurement));
77+
}
78+
79+
Ok(results)
80+
}
81+
}
82+
83+
#[derive(Debug, Clone, Serialize, Deserialize)]
84+
pub struct BenchmarkData {
85+
pub package: String,
86+
pub name: String,
87+
pub times: Vec<f64>,
88+
pub iters: Vec<u64>,
89+
}
90+
91+
impl BenchmarkData {
92+
pub fn process_raw_results(raw_results: Vec<(String, RawOutput)>) -> Vec<Self> {
93+
let grouped: HashMap<(String, String), Vec<&(String, RawOutput)>> = raw_results
94+
.iter()
95+
.into_group_map_by(|(package, bench)| (package.clone(), bench.name.clone()));
96+
97+
grouped
98+
.into_iter()
99+
.map(|((package, name), measurements)| BenchmarkData {
100+
package,
101+
name,
102+
times: measurements.iter().map(|(_, m)| m.time).collect(),
103+
iters: measurements.iter().map(|(_, m)| m.iters).collect(),
104+
})
105+
.collect()
106+
}
107+
}
108+
109+
#[cfg(test)]
110+
mod tests {
111+
use super::*;
112+
113+
#[test]
114+
fn test_parse_output() {
115+
let line = "BenchmarkFibonacci10-16 \t 15564\t 755.2 ns/op\n";
116+
117+
let result = RawOutput::parse_output(line).unwrap().unwrap();
118+
assert_eq!(result.name, "BenchmarkFibonacci10");
119+
assert_eq!(result.iters, 15564);
120+
assert!((result.time - 755.2).abs() < 0.1);
121+
122+
let line =
123+
"BenchmarkOutTransform/pointer_to_value-16 \t 8257989\t 162.7 ns/op\n";
124+
let result = RawOutput::parse_output(line).unwrap().unwrap();
125+
assert_eq!(result.name, "BenchmarkOutTransform/pointer_to_value");
126+
assert_eq!(result.iters, 8257989);
127+
assert!((result.time - 162.7).abs() < 0.1);
128+
}
129+
130+
#[test]
131+
fn test_parse_output_no_match() {
132+
// Test line that doesn't match benchmark pattern
133+
let line = "=== RUN BenchmarkFibonacci10\n";
134+
135+
let result = RawOutput::parse_output(line).unwrap();
136+
assert!(result.is_none());
137+
}
138+
139+
#[test]
140+
fn test_parse_and_process_benchmark_data() {
141+
const RESULT: &str = include_str!("testdata/simple.txt");
142+
143+
let raw_results = RawOutput::parse(RESULT).unwrap();
144+
let processed = BenchmarkData::process_raw_results(raw_results);
145+
assert_eq!(processed.len(), 3);
146+
147+
let fib10 = processed
148+
.iter()
149+
.find(|b| b.name == "BenchmarkFibonacci10")
150+
.unwrap();
151+
152+
let fib20 = processed
153+
.iter()
154+
.find(|b| b.name == "BenchmarkFibonacci20")
155+
.unwrap();
156+
157+
let fib30 = processed
158+
.iter()
159+
.find(|b| b.name == "BenchmarkFibonacci30")
160+
.unwrap();
161+
162+
assert_eq!(fib10.package, "example");
163+
assert_eq!(fib10.times.len(), 10);
164+
assert_eq!(fib10.iters.len(), 10);
165+
assert_eq!(fib20.package, "example");
166+
assert_eq!(fib20.times.len(), 10);
167+
assert_eq!(fib20.iters.len(), 10);
168+
assert_eq!(fib30.package, "example");
169+
assert_eq!(fib30.times.len(), 10);
170+
assert_eq!(fib30.iters.len(), 10);
171+
}
172+
173+
#[test]
174+
fn test_parse_fuego() {
175+
let content = include_str!("testdata/fuego.txt");
176+
let raw_results = RawOutput::parse(content).unwrap();
177+
let processed = BenchmarkData::process_raw_results(raw_results);
178+
assert_eq!(processed.len(), 19);
179+
}
180+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:842a09f7689fc1e8a78a3fa9454bb9d1c9a3fbc89696b5e7403104a76f7ad15c
3+
size 545001
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:56e94f9348ab435ae3dcc08cb94e55b3910ea9447b4e47f174dba5f4e9731312
3+
size 6862

src/run/runner/wall_time/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod executor;
2+
pub mod golang;
23
pub mod perf;

0 commit comments

Comments
 (0)