Skip to main content

wowlab_types/
bench_metrics.rs

1// #t(file: rust_ambient_syscall, rust_global_state) Benchmark-only single-thread timing requires one isolated clock and thread-local counter set.
2
3//! Benchmark-only engine counters shared by the runtime layers.
4//!
5//! Forge installs these thread-local counters through the `bench-metrics` feature.
6//! Every recorder is an inline no-op without the feature.
7
8use serde::{Deserialize, Serialize};
9
10/// Raw work counters captured for one fixed-seed benchmark sample.
11#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
12pub struct BenchmarkCounters {
13    pub rotation_evaluations: u64,
14    #[serde(default)]
15    pub rotation_ast_lowerings: u64,
16    pub events: u64,
17    pub damage_events: u64,
18    pub aura_events: u64,
19    pub damage_action_setups: u64,
20    pub damage_owner_setups: u64,
21    pub damage_target_setups: u64,
22    pub damage_targets: u64,
23    pub aura_slots_visited: u64,
24    #[serde(default)]
25    pub planned_guardian_slots_visited: u64,
26    pub planned_aura_bindings: u64,
27    pub scanned_aura_definitions: u64,
28    pub planned_aura_spell_bindings: u64,
29    pub scanned_aura_spells: u64,
30    pub planned_aura_projection_bindings: u64,
31    pub scanned_aura_projections: u64,
32    pub planned_rotation_projection_bindings: u64,
33    pub scanned_rotation_projection_spells: u64,
34    pub keyed_lookups: u64,
35    pub game_data_hot_reads: u64,
36    pub damage_plan_keyed_lookups: u64,
37    pub damage_plan_raw_binary_searches: u64,
38    pub target_plan_runtime_lowerings: u64,
39    pub target_plan_runtime_fallbacks: u64,
40    pub resets: u64,
41    pub reset_time_ns: u64,
42}
43
44#[cfg(feature = "bench-metrics")]
45mod enabled {
46    use std::{cell::Cell, time::Instant};
47
48    use super::BenchmarkCounters;
49
50    thread_local! {
51        static COUNTERS: Cell<BenchmarkCounters> = const { Cell::new(BenchmarkCounters {
52            rotation_evaluations: 0,
53            rotation_ast_lowerings: 0,
54            events: 0,
55            damage_events: 0,
56            aura_events: 0,
57            damage_action_setups: 0,
58            damage_owner_setups: 0,
59            damage_target_setups: 0,
60            damage_targets: 0,
61            aura_slots_visited: 0,
62            planned_guardian_slots_visited: 0,
63            planned_aura_bindings: 0,
64            scanned_aura_definitions: 0,
65            planned_aura_spell_bindings: 0,
66            scanned_aura_spells: 0,
67            planned_aura_projection_bindings: 0,
68            scanned_aura_projections: 0,
69            planned_rotation_projection_bindings: 0,
70            scanned_rotation_projection_spells: 0,
71            keyed_lookups: 0,
72            game_data_hot_reads: 0,
73            damage_plan_keyed_lookups: 0,
74            damage_plan_raw_binary_searches: 0,
75            target_plan_runtime_lowerings: 0,
76            target_plan_runtime_fallbacks: 0,
77            resets: 0,
78            reset_time_ns: 0,
79        }) };
80    }
81
82    fn update(apply: impl FnOnce(&mut BenchmarkCounters)) {
83        COUNTERS.with(|cell| {
84            let mut counters = cell.get();
85
86            apply(&mut counters);
87            cell.set(counters);
88        });
89    }
90
91    pub(super) fn reset() {
92        COUNTERS.with(|cell| cell.set(BenchmarkCounters::default()));
93    }
94
95    pub(super) fn snapshot() -> BenchmarkCounters {
96        COUNTERS.with(Cell::get)
97    }
98
99    pub(super) fn rotation_evaluation() {
100        update(|counters| counters.rotation_evaluations += 1);
101    }
102
103    pub(super) fn rotation_ast_lowering() {
104        update(|counters| counters.rotation_ast_lowerings += 1);
105    }
106
107    pub(super) fn event() {
108        update(|counters| counters.events += 1);
109    }
110
111    pub(super) fn damage_event() {
112        update(|counters| counters.damage_events += 1);
113    }
114
115    pub(super) fn aura_event() {
116        update(|counters| counters.aura_events += 1);
117    }
118
119    pub(super) fn damage_action_setup() {
120        update(|counters| counters.damage_action_setups += 1);
121    }
122
123    pub(super) fn damage_owner_setup() {
124        update(|counters| counters.damage_owner_setups += 1);
125    }
126
127    pub(super) fn damage_target_setup() {
128        update(|counters| counters.damage_target_setups += 1);
129    }
130
131    pub(super) fn damage_setup_stages() {
132        update(|counters| {
133            counters.damage_action_setups += 1;
134            counters.damage_owner_setups += 1;
135            counters.damage_target_setups += 1;
136        });
137    }
138
139    pub(super) fn damage_target() {
140        update(|counters| counters.damage_targets += 1);
141    }
142
143    pub(super) fn aura_slot_visit() {
144        update(|counters| counters.aura_slots_visited += 1);
145    }
146
147    pub(super) fn planned_guardian_slot_visit() {
148        update(|counters| counters.planned_guardian_slots_visited += 1);
149    }
150
151    pub(super) fn planned_aura_bindings(amount: u64) {
152        update(|counters| counters.planned_aura_bindings += amount);
153    }
154
155    pub(super) fn scanned_aura_definitions(amount: u64) {
156        update(|counters| counters.scanned_aura_definitions += amount);
157    }
158
159    pub(super) fn planned_aura_spell_bindings(amount: u64) {
160        update(|counters| counters.planned_aura_spell_bindings += amount);
161    }
162
163    pub(super) fn scanned_aura_spells(amount: u64) {
164        update(|counters| counters.scanned_aura_spells += amount);
165    }
166
167    pub(super) fn planned_aura_projection_bindings(amount: u64) {
168        update(|counters| counters.planned_aura_projection_bindings += amount);
169    }
170
171    pub(super) fn scanned_aura_projections(amount: u64) {
172        update(|counters| counters.scanned_aura_projections += amount);
173    }
174
175    pub(super) fn planned_rotation_projection_binding() {
176        update(|counters| counters.planned_rotation_projection_bindings += 1);
177    }
178
179    pub(super) fn scanned_rotation_projection_spell() {
180        update(|counters| counters.scanned_rotation_projection_spells += 1);
181    }
182
183    pub(super) fn keyed_lookup() {
184        update(|counters| counters.keyed_lookups += 1);
185    }
186
187    pub(super) fn game_data_hot_read() {
188        update(|counters| counters.game_data_hot_reads += 1);
189    }
190
191    pub(super) fn damage_plan_keyed_lookup() {
192        update(|counters| counters.damage_plan_keyed_lookups += 1);
193    }
194
195    pub(super) fn damage_plan_raw_binary_search() {
196        update(|counters| counters.damage_plan_raw_binary_searches += 1);
197    }
198
199    pub(super) fn target_plan_runtime_lowering() {
200        update(|counters| counters.target_plan_runtime_lowerings += 1);
201    }
202
203    pub(super) fn target_plan_runtime_fallback() {
204        update(|counters| counters.target_plan_runtime_fallbacks += 1);
205    }
206
207    pub(super) fn measure_reset<R>(reset: impl FnOnce() -> R) -> R {
208        let started_at = Instant::now();
209        let result = reset();
210        let elapsed = u64::try_from(started_at.elapsed().as_nanos()).unwrap_or(u64::MAX);
211
212        update(|counters| {
213            counters.resets += 1;
214            counters.reset_time_ns = counters.reset_time_ns.saturating_add(elapsed);
215        });
216
217        result
218    }
219}
220
221/// Clear counters before a measured sample.
222#[inline]
223pub fn reset() {
224    #[cfg(feature = "bench-metrics")]
225    enabled::reset();
226}
227
228/// Snapshot the current counters.
229#[inline]
230#[must_use]
231pub fn snapshot() -> BenchmarkCounters {
232    #[cfg(feature = "bench-metrics")]
233    {
234        enabled::snapshot()
235    }
236
237    #[cfg(not(feature = "bench-metrics"))]
238    {
239        BenchmarkCounters::default()
240    }
241}
242
243macro_rules! recorder {
244    ($name:ident, $enabled:ident) => {
245        #[inline(always)]
246        pub fn $name() {
247            #[cfg(feature = "bench-metrics")]
248            enabled::$enabled();
249        }
250    };
251}
252
253recorder!(record_rotation_evaluation, rotation_evaluation);
254recorder!(record_rotation_ast_lowering, rotation_ast_lowering);
255recorder!(record_event, event);
256recorder!(record_damage_event, damage_event);
257recorder!(record_aura_event, aura_event);
258recorder!(record_damage_action_setup, damage_action_setup);
259recorder!(record_damage_owner_setup, damage_owner_setup);
260recorder!(record_damage_target_setup, damage_target_setup);
261recorder!(record_damage_setup_stages, damage_setup_stages);
262recorder!(record_damage_target, damage_target);
263recorder!(record_aura_slot_visit, aura_slot_visit);
264recorder!(
265    record_planned_guardian_slot_visit,
266    planned_guardian_slot_visit
267);
268recorder!(record_keyed_lookup, keyed_lookup);
269recorder!(record_game_data_hot_read, game_data_hot_read);
270recorder!(record_damage_plan_keyed_lookup, damage_plan_keyed_lookup);
271recorder!(
272    record_damage_plan_raw_binary_search,
273    damage_plan_raw_binary_search
274);
275recorder!(
276    record_target_plan_runtime_lowering,
277    target_plan_runtime_lowering
278);
279recorder!(
280    record_target_plan_runtime_fallback,
281    target_plan_runtime_fallback
282);
283recorder!(
284    record_planned_rotation_projection_binding,
285    planned_rotation_projection_binding
286);
287recorder!(
288    record_scanned_rotation_projection_spell,
289    scanned_rotation_projection_spell
290);
291
292macro_rules! amount_recorder {
293    ($name:ident, $enabled:ident) => {
294        #[inline(always)]
295        pub fn $name(amount: usize) {
296            #[cfg(feature = "bench-metrics")]
297            enabled::$enabled(u64::try_from(amount).unwrap_or(u64::MAX));
298
299            #[cfg(not(feature = "bench-metrics"))]
300            let _ = amount;
301        }
302    };
303}
304
305amount_recorder!(record_planned_aura_bindings, planned_aura_bindings);
306amount_recorder!(record_scanned_aura_definitions, scanned_aura_definitions);
307amount_recorder!(
308    record_planned_aura_spell_bindings,
309    planned_aura_spell_bindings
310);
311amount_recorder!(record_scanned_aura_spells, scanned_aura_spells);
312amount_recorder!(
313    record_planned_aura_projection_bindings,
314    planned_aura_projection_bindings
315);
316amount_recorder!(record_scanned_aura_projections, scanned_aura_projections);
317
318/// Time one handler reset when benchmark metrics are enabled.
319#[inline]
320pub fn measure_reset<R>(reset: impl FnOnce() -> R) -> R {
321    #[cfg(feature = "bench-metrics")]
322    {
323        enabled::measure_reset(reset)
324    }
325
326    #[cfg(not(feature = "bench-metrics"))]
327    {
328        reset()
329    }
330}
331
332#[cfg(test)]
333#[path = "bench_metrics/tests.rs"]
334mod tests;