binggan/plugins/events.rs
1//! [PluginManager] for Binggan.
2//!
3//! The plugin manager is responsible for managing plugins and emitting events to them
4//! that occur during the benchmark run.
5//!
6//! See the [PluginEvents] enum for the list of events that can be emitted.
7//! Any type that implements the [EventListener] trait can be added to [PluginManager].
8//!
9
10use crate::{bench::BenchResult, bench_id::BenchId};
11use std::any::Any;
12
13/// Events that can be emitted by the benchmark runner.
14#[derive(Debug, Clone, Copy)]
15pub enum PluginEvents<'a> {
16 /// The number of iterations for the benches in a group has been set.
17 /// The previous event was `GroupStart`.
18 GroupBenchNumIters {
19 /// The number of iterations for each bench in the group. The whole group has the same
20 /// number of iterations to be a fair comparison between the benches in the group.
21 num_iter: usize,
22 },
23 /// The number of iterations for the bench group.
24 /// The previous event was `GroupStart`.
25 GroupNumIters {
26 /// Unlike GroupBenchNumIters, this is the number of iterations for the group.
27 /// So each bench is run `num_iter` * `num_group_iter` times.
28 num_iter: usize,
29 },
30 /// Profiling of the group started
31 GroupStart {
32 /// The name of the runner
33 runner_name: Option<&'a str>,
34 /// The name of the group
35 group_name: Option<&'a str>,
36 /// The name of the column of the output value.
37 output_value_column_title: &'static str,
38 },
39 /// Profiling of the group finished.
40 GroupStop {
41 /// The name of the runner
42 runner_name: Option<&'a str>,
43 /// The name of the group
44 group_name: Option<&'a str>,
45 /// The results of the group
46 /// This will include the results of all the benchmarks in the group.
47 /// It also contains delta information of the last run if available
48 results: &'a [BenchResult],
49 /// The name of the column of the output value.
50 output_value_column_title: &'static str,
51 },
52 /// A benchmark in a group is started. Note that a benchmark can be run multiple times for higher
53 /// accuracy. BenchStart and BenchStop are not called for each iteration.
54 ///
55 /// A group is iterated multiple times. This will be called for every iteration in the group.
56 BenchStart {
57 /// The bench id
58 bench_id: &'a BenchId,
59 },
60 /// A benchmark in a group is stopped.
61 BenchStop {
62 /// The bench id
63 bench_id: &'a BenchId,
64 /// The duration of the benchmark
65 duration: u64,
66 },
67}
68
69/// The trait for listening to events emitted by the benchmark runner.
70pub trait EventListener: Any {
71 /// The priority of the event listener.
72 /// If the event listener has a higher priority, it will be called first.
73 /// E.g. perf counter plugin should be called after the cache trasher in the [PluginEvents::BenchStart] event,
74 /// so that the cache trashing is not included in the perf counter.
75 ///
76 /// The default priority is `u32::MAX / 2`.
77 ///
78 /// Note: In the [PluginEvents::BenchStop] event, the order is reversed. The listener with the highest priority
79 /// is called last. This is to endure symmetry.
80 fn prio(&self) -> u32 {
81 u32::MAX / 2
82 }
83
84 /// The name of the event listener.
85 fn name(&self) -> &'static str;
86 /// Handle an event.
87 /// See the [PluginEvents] enum for the list of events that can be emitted.
88 fn on_event(&mut self, event: PluginEvents);
89 /// Downcast the listener to `Any`.
90 fn as_any(&mut self) -> &mut dyn Any;
91}
92
93/// [PluginManager] is responsible for managing plugins and emitting events.
94///
95/// See the [PluginEvents] enum for the list of events that can be emitted.
96/// Any type that implements the `EventListener` trait can be added to the plugin manager.
97pub struct PluginManager {
98 listeners: Vec<(String, Box<dyn EventListener>)>,
99}
100impl PluginManager {
101 /// Create a new instance of [PluginManager].
102 pub fn new() -> Self {
103 Self {
104 listeners: Vec::new(),
105 }
106 }
107
108 /// Removes any plugins with the same name and sets the new listener.
109 pub fn replace_plugin<L: EventListener + 'static>(&mut self, listener: L) -> &mut Self {
110 self.remove_plugin_by_name(listener.name());
111 self.add_plugin(listener);
112 self
113 }
114
115 /// Add a new plugin. Note that this will not remove listeners with the
116 /// same name.
117 pub fn add_plugin<L: EventListener + 'static>(&mut self, listener: L) -> &mut Self {
118 self.listeners
119 .push((listener.name().to_owned(), Box::new(listener)));
120 self.listeners.sort_by_key(|(_, l)| l.prio());
121 self
122 }
123
124 /// Add a new plugin to the plugin manager if it is not already present by name.
125 pub fn add_plugin_if_absent<L: EventListener + 'static>(&mut self, listener: L) -> &mut Self {
126 if self.get_plugins(listener.name()).is_some() {
127 return self;
128 }
129 self.add_plugin(listener);
130 self
131 }
132
133 /// Get the first plugin that matches the name.
134 pub fn get_plugins(&mut self, name: &str) -> Option<&mut Box<dyn EventListener>> {
135 self.listeners
136 .iter_mut()
137 .find(|(n, _)| n == name)
138 .map(|(_, l)| l)
139 }
140
141 /// Downcast a plugin to a specific type.
142 pub fn downcast_plugin<T: 'static>(&mut self, name: &str) -> Option<&mut T> {
143 self.get_plugins(name)?.as_any().downcast_mut::<T>()
144 }
145
146 /// Remove a plugin by name.
147 pub fn remove_plugin_by_name(&mut self, name: &str) {
148 self.listeners.retain(|(n, _)| n != name);
149 }
150
151 /// Emit an event to all plugin.
152 pub fn emit(&mut self, event: PluginEvents) {
153 if matches!(event, PluginEvents::BenchStop { .. }) {
154 for (_, listener) in self.listeners.iter_mut().rev() {
155 listener.on_event(event);
156 }
157 } else {
158 for (_, listener) in self.listeners.iter_mut() {
159 listener.on_event(event);
160 }
161 }
162 }
163}
164
165impl Default for PluginManager {
166 fn default() -> Self {
167 Self::new()
168 }
169}