Skip to main content

forge/bench/
types.rs

1//! Data types for benchmark runs and history.
2
3#[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}