1use std::fmt::Display;
2
3use serde::ser::{Serialize, Serializer};
4
5#[cfg(feature = "profiling")]
6use super::profiling_data;
7use super::ProfilingData;
8
9pub type CustomStatisticComputer<T> = Box<dyn (Fn(&ProfilingData) -> Option<T>) + Send + Sync>;
10pub type CustomStatisticPrinter<T> = Box<dyn Fn(T) + Send + Sync>;
11
12pub(super) enum ComputedStatisticFunctions {
13 USize {
14 computer: CustomStatisticComputer<usize>,
15 printer: CustomStatisticPrinter<usize>,
16 },
17 Int {
18 computer: CustomStatisticComputer<i64>,
19 printer: CustomStatisticPrinter<i64>,
20 },
21 Float {
22 computer: CustomStatisticComputer<f64>,
23 printer: CustomStatisticPrinter<f64>,
24 },
25}
26
27impl ComputedStatisticFunctions {
28 pub(super) fn compute(&self, container: &ProfilingData) -> Option<ComputedValue> {
30 match self {
31 ComputedStatisticFunctions::USize { computer, .. } => {
32 computer(container).map(ComputedValue::USize)
33 }
34 ComputedStatisticFunctions::Int { computer, .. } => {
35 computer(container).map(ComputedValue::Int)
36 }
37 ComputedStatisticFunctions::Float { computer, .. } => {
38 computer(container).map(ComputedValue::Float)
39 }
40 }
41 }
42
43 pub(super) fn print(&self, value: ComputedValue) {
45 match value {
46 ComputedValue::USize(value) => {
47 let ComputedStatisticFunctions::USize { printer, .. } = self else {
48 unreachable!(
49 "Ixa internal error: computed statistic value and printer type disagree",
50 )
51 };
52 (printer)(value);
53 }
54 ComputedValue::Int(value) => {
55 let ComputedStatisticFunctions::Int { printer, .. } = self else {
56 unreachable!(
57 "Ixa internal error: computed statistic value and printer type disagree",
58 )
59 };
60 (printer)(value);
61 }
62 ComputedValue::Float(value) => {
63 let ComputedStatisticFunctions::Float { printer, .. } = self else {
64 unreachable!(
65 "Ixa internal error: computed statistic value and printer type disagree",
66 )
67 };
68 (printer)(value);
69 }
70 }
71 }
72}
73
74pub(super) struct ComputedStatistic {
75 pub label: &'static str,
77 pub description: &'static str,
79 pub value: Option<ComputedValue>,
81 pub functions: ComputedStatisticFunctions,
83}
84
85mod sealed {
87 pub(super) trait SealedComputableType {}
88}
89#[allow(private_bounds)]
90pub trait ComputableType: sealed::SealedComputableType
91where
92 Self: Sized,
93{
94 #[allow(private_interfaces)]
96 fn new_functions(
97 computer: CustomStatisticComputer<Self>,
98 printer: CustomStatisticPrinter<Self>,
99 ) -> ComputedStatisticFunctions;
100}
101impl sealed::SealedComputableType for usize {}
102impl ComputableType for usize {
103 #[allow(private_interfaces)]
104 fn new_functions(
105 computer: CustomStatisticComputer<Self>,
106 printer: CustomStatisticPrinter<Self>,
107 ) -> ComputedStatisticFunctions {
108 ComputedStatisticFunctions::USize { computer, printer }
109 }
110}
111impl sealed::SealedComputableType for i64 {}
112impl ComputableType for i64 {
113 #[allow(private_interfaces)]
114 fn new_functions(
115 computer: CustomStatisticComputer<Self>,
116 printer: CustomStatisticPrinter<Self>,
117 ) -> ComputedStatisticFunctions {
118 ComputedStatisticFunctions::Int { computer, printer }
119 }
120}
121impl sealed::SealedComputableType for f64 {}
122impl ComputableType for f64 {
123 #[allow(private_interfaces)]
124 fn new_functions(
125 computer: CustomStatisticComputer<Self>,
126 printer: CustomStatisticPrinter<Self>,
127 ) -> ComputedStatisticFunctions {
128 ComputedStatisticFunctions::Float { computer, printer }
129 }
130}
131
132#[derive(Copy, Clone, PartialEq, Debug)]
134pub(super) enum ComputedValue {
135 USize(usize),
136 Int(i64),
137 Float(f64),
138}
139
140impl Serialize for ComputedValue {
141 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
142 match self {
143 ComputedValue::USize(v) => serializer.serialize_u64(*v as u64),
144 ComputedValue::Int(v) => serializer.serialize_i64(*v),
145 ComputedValue::Float(v) => serializer.serialize_f64(*v),
146 }
147 }
148}
149
150impl Display for ComputedValue {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 match self {
153 ComputedValue::USize(value) => {
154 write!(f, "{}", value)
155 }
156
157 ComputedValue::Int(value) => {
158 write!(f, "{}", value)
159 }
160
161 ComputedValue::Float(value) => {
162 write!(f, "{}", value)
163 }
164 }
165 }
166}
167
168#[cfg(feature = "profiling")]
169pub fn add_computed_statistic<T: ComputableType>(
170 label: &'static str,
171 description: &'static str,
172 computer: CustomStatisticComputer<T>,
173 printer: CustomStatisticPrinter<T>,
174) {
175 let mut container = profiling_data();
176 container.add_computed_statistic(label, description, computer, printer);
177}
178#[cfg(not(feature = "profiling"))]
179pub fn add_computed_statistic<T: ComputableType>(
180 _label: &'static str,
181 _description: &'static str,
182 _computer: CustomStatisticComputer<T>,
183 _printer: CustomStatisticPrinter<T>,
184) {
185}
186
187#[cfg(all(test, feature = "profiling"))]
188mod tests {
189 use std::sync::atomic::{AtomicBool, Ordering};
190
191 use super::*;
192 use crate::profiling::{get_profiling_data, increment_named_count};
193
194 #[test]
195 fn test_computed_statistic_usize() {
196 increment_named_count("comp_stat_events_usize_test");
197 increment_named_count("comp_stat_events_usize_test");
198 increment_named_count("comp_stat_events_usize_test");
199
200 add_computed_statistic::<usize>(
201 "comp_stat_total_events",
202 "Total number of events",
203 Box::new(|data| data.get_named_count("comp_stat_events_usize_test")),
204 Box::new(|value| println!("Total events: {}", value)),
205 );
206
207 let data = get_profiling_data();
208
209 let stat = data
210 .computed_statistics
211 .iter()
212 .find_map(|s| {
213 s.as_ref()
214 .filter(|stat| stat.label == "comp_stat_total_events")
215 })
216 .expect("total_events statistic not found");
217 let computed = stat.functions.compute(&data);
218 assert_eq!(computed, Some(ComputedValue::USize(3)));
219 }
220
221 #[test]
222 fn test_computed_statistic_i64() {
223 increment_named_count("comp_stat_positive_i64_test");
224 increment_named_count("comp_stat_positive_i64_test");
225 increment_named_count("comp_stat_negative_i64_test");
226
227 add_computed_statistic::<i64>(
228 "comp_stat_difference",
229 "Difference between positive and negative",
230 Box::new(|data| {
231 let pos = data
232 .get_named_count("comp_stat_positive_i64_test")
233 .unwrap_or(0) as i64;
234 let neg = data
235 .get_named_count("comp_stat_negative_i64_test")
236 .unwrap_or(0) as i64;
237 Some(pos - neg)
238 }),
239 Box::new(|value| println!("Difference: {}", value)),
240 );
241
242 let data = get_profiling_data();
243 let stat = data
244 .computed_statistics
245 .iter()
246 .find_map(|s| {
247 s.as_ref()
248 .filter(|stat| stat.label == "comp_stat_difference")
249 })
250 .expect("difference statistic not found");
251 let computed = stat.functions.compute(&data);
252 assert_eq!(computed, Some(ComputedValue::Int(1)));
253 }
254
255 #[test]
256 fn test_computed_statistic_f64() {
257 {
258 let mut data = get_profiling_data();
259 *data
260 .counts
261 .entry("comp_stat_successes_f64_test")
262 .or_insert(0) += 3;
263 *data.counts.entry("comp_stat_total_f64_test").or_insert(0) += 4;
264 data.add_computed_statistic::<f64>(
265 "comp_stat_success_rate",
266 "Success rate as percentage",
267 Box::new(|data| {
268 let successes = data.get_named_count("comp_stat_successes_f64_test")? as f64;
269 let total = data.get_named_count("comp_stat_total_f64_test")? as f64;
270 Some(successes / total * 100.0)
271 }),
272 Box::new(|value| println!("Success rate: {:.2}%", value)),
273 );
274
275 let stat = data
276 .computed_statistics
277 .iter()
278 .find_map(|s| {
279 s.as_ref()
280 .filter(|stat| stat.label == "comp_stat_success_rate")
281 })
282 .expect("comp_stat_success_rate statistic not found");
283 let computed = stat.functions.compute(&data);
284 if let Some(ComputedValue::Float(value)) = computed {
285 assert!((value - 75.0).abs() < 0.01);
286 } else {
287 panic!("Expected Float value, got {:?}", computed);
288 }
289 }
290 }
291
292 #[test]
293 fn test_computed_statistic_returns_none() {
294 add_computed_statistic::<usize>(
295 "comp_stat_missing_data",
296 "Statistic with missing data",
297 Box::new(|data| data.get_named_count("comp_stat_nonexistent")),
298 Box::new(|value| println!("Value: {}", value)),
299 );
300
301 let data = get_profiling_data();
302 let stat = data
303 .computed_statistics
304 .iter()
305 .find_map(|s| {
306 s.as_ref()
307 .filter(|stat| stat.label == "comp_stat_missing_data")
308 })
309 .expect("comp_stat_missing_data statistic not found");
310 let computed = stat.functions.compute(&data);
311 assert_eq!(computed, None);
312 }
313
314 #[test]
315 fn test_computed_value_display() {
316 let usize_val = ComputedValue::USize(42);
317 assert_eq!(format!("{}", usize_val), "42");
318
319 let int_val = ComputedValue::Int(-100);
320 assert_eq!(format!("{}", int_val), "-100");
321
322 let float_val = ComputedValue::Float(std::f64::consts::PI);
323 assert_eq!(format!("{}", float_val), "3.141592653589793");
324 }
325
326 #[test]
327 fn test_computed_statistic_print_functions() {
328 static PRINTED: AtomicBool = AtomicBool::new(false);
329
330 PRINTED.store(false, Ordering::SeqCst);
332
333 increment_named_count("comp_stat_test_print_func");
334
335 add_computed_statistic::<usize>(
336 "comp_stat_test_stat",
337 "Test statistic",
338 Box::new(|data| data.get_named_count("comp_stat_test_print_func")),
339 Box::new(|_value| {
340 PRINTED.store(true, Ordering::SeqCst);
341 }),
342 );
343
344 let data = get_profiling_data();
345 let stat = data
346 .computed_statistics
347 .iter()
348 .find_map(|s| {
349 s.as_ref()
350 .filter(|stat| stat.label == "comp_stat_test_stat")
351 })
352 .expect("test_stat statistic not found");
353 let value = stat.functions.compute(&data).unwrap();
354 stat.functions.print(value);
355
356 assert!(PRINTED.load(Ordering::SeqCst));
357 }
358
359 #[test]
360 #[should_panic(
361 expected = "Ixa internal error: computed statistic value and printer type disagree"
362 )]
363 fn mismatched_computed_statistic_printer_panics() {
364 let functions = ComputedStatisticFunctions::USize {
365 computer: Box::new(|_| Some(0)),
366 printer: Box::new(|_| {}),
367 };
368
369 functions.print(ComputedValue::Int(0));
370 }
371}