1#[cfg(test)]
4use googletest::{Result as GtestResult, prelude::*};
5use serde::{Deserialize, Serialize};
6use wowlab_common::sys::OsProfile;
7use wowlab_types::bench_metrics::BenchmarkCounters;
8
9use crate::allocation::AllocationSnapshot;
10
11#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
12#[serde(rename_all = "snake_case")]
13pub(crate) enum BenchmarkBackend {
14 #[default]
15 Portable,
16 Jit,
17}
18
19#[derive(Clone, Debug, Deserialize, Serialize)]
20pub(crate) struct BenchmarkSample {
21 pub sample: u32,
22 pub elapsed_ms: f64,
23 pub iterations_per_sec: f64,
24 pub mean_dps: f64,
25 pub semantic_fingerprint: String,
26 pub counters: BenchmarkCounters,
27 pub build_time_ms: f64,
28 pub reset_time_ms: f64,
29 pub allocations: Option<AllocationSnapshot>,
30}
31
32#[derive(Clone, Debug, Deserialize, Serialize)]
33pub(crate) struct SpecBenchResult {
34 pub slug: String,
35 #[serde(default = "default_encounter_fixture")]
36 pub encounter_fixture: String,
37 #[serde(default)]
38 pub backend: BenchmarkBackend,
39 pub iterations: u32,
40 pub duration_secs: u32,
41 #[serde(default)]
42 pub warmups: u32,
43 #[serde(default)]
44 pub samples: Vec<BenchmarkSample>,
45 pub elapsed_ms: u64,
46 pub iterations_per_sec: f64,
47 #[serde(default)]
48 pub mean_iterations_per_sec: f64,
49 #[serde(default)]
50 pub median_iterations_per_sec: f64,
51 #[serde(default)]
52 pub population_stddev_iterations_per_sec: f64,
53 pub mean_dps: f64,
54 #[serde(default)]
55 pub semantic_fingerprint: String,
56}
57
58fn default_encounter_fixture() -> String {
59 "patchwerk".to_string()
60}
61
62#[derive(Clone, Debug, Deserialize, Serialize)]
63pub(crate) struct ScalingPoint {
64 pub threads: usize,
65 pub elapsed_ms: u64,
66 pub iterations_per_sec: f64,
67 pub multiplier: f64,
68 pub efficiency: f64,
69 pub marginal_gain: f64,
70 pub is_p_core_range: bool,
71}
72
73#[derive(Clone, Debug, Deserialize, Serialize)]
74pub(crate) struct ScalingResult {
75 pub slug: String,
76 pub iterations: u32,
77 pub points: Vec<ScalingPoint>,
78}
79
80#[derive(Clone, Debug, Deserialize, Serialize)]
81pub(crate) struct BenchRun {
82 pub engine_version: String,
83 pub timestamp: String,
84 pub git_hash: String,
85 pub git_branch: String,
86 pub rustc_version: String,
87 #[serde(default)]
88 pub features: Vec<String>,
89 #[serde(default)]
90 pub backend: BenchmarkBackend,
91 pub system: OsProfile,
92 pub results: Vec<SpecBenchResult>,
93 #[serde(default)]
94 pub scaling: Vec<ScalingResult>,
95}
96
97#[derive(Clone, Debug, Deserialize, Serialize)]
98pub(crate) struct BenchHistory {
99 pub runs: Vec<BenchRun>,
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[gtest]
107 fn spec_result_json_preserves_the_persisted_run_fields() -> GtestResult<()> {
108 let result = SpecBenchResult {
109 slug: "fire_mage".to_string(),
110 encounter_fixture: "patchwerk".to_string(),
111 backend: BenchmarkBackend::Portable,
112 iterations: 17,
113 duration_secs: 45,
114 warmups: 1,
115 samples: vec![BenchmarkSample {
116 sample: 1,
117 elapsed_ms: 123.0,
118 iterations_per_sec: 138.2,
119 mean_dps: 456.7,
120 semantic_fingerprint: "abc".to_string(),
121 counters: BenchmarkCounters {
122 events: 99,
123 rotation_evaluations: 7,
124 damage_events: 8,
125 aura_events: 9,
126 resets: 18,
127 reset_time_ns: 1_500_000,
128 ..BenchmarkCounters::default()
129 },
130 build_time_ms: 12.5,
131 reset_time_ms: 1.5,
132 allocations: Some(AllocationSnapshot {
133 count: 10,
134 bytes: 2048,
135 }),
136 }],
137 elapsed_ms: 123,
138 iterations_per_sec: 138.2,
139 mean_iterations_per_sec: 138.2,
140 median_iterations_per_sec: 138.2,
141 population_stddev_iterations_per_sec: 0.0,
142 mean_dps: 456.7,
143 semantic_fingerprint: "abc".to_string(),
144 };
145 let expected_counters = serde_json::to_value(BenchmarkCounters {
146 events: 99,
147 rotation_evaluations: 7,
148 damage_events: 8,
149 aura_events: 9,
150 resets: 18,
151 reset_time_ns: 1_500_000,
152 ..BenchmarkCounters::default()
153 })
154 .or_fail()?;
155
156 verify_that!(
157 serde_json::to_value(result).or_fail()?,
158 eq(&serde_json::json!({
159 "slug": "fire_mage",
160 "encounter_fixture": "patchwerk",
161 "backend": "portable",
162 "iterations": 17,
163 "duration_secs": 45,
164 "warmups": 1,
165 "samples": [{
166 "sample": 1,
167 "elapsed_ms": 123.0,
168 "iterations_per_sec": 138.2,
169 "mean_dps": 456.7,
170 "semantic_fingerprint": "abc",
171 "counters": expected_counters,
172 "build_time_ms": 12.5,
173 "reset_time_ms": 1.5,
174 "allocations": { "count": 10, "bytes": 2048 }
175 }],
176 "elapsed_ms": 123,
177 "iterations_per_sec": 138.2,
178 "mean_iterations_per_sec": 138.2,
179 "median_iterations_per_sec": 138.2,
180 "population_stddev_iterations_per_sec": 0.0,
181 "mean_dps": 456.7,
182 "semantic_fingerprint": "abc",
183 }))
184 )?;
185
186 Ok(())
187 }
188}