+ use crate::timings;
+
+ use charming::{component::Axis, datatype::DataPoint, element::AxisType};
+
+ const BUCKET_COUNT: usize = 10;
+
+ fn find_min_max_duration(pkg_durations: &[f64]) -> (f64, f64) {
+ assert!(!pkg_durations.is_empty());
+
+ let mut min = f64::MAX;
+ let mut max = f64::MIN;
+ for duration in pkg_durations {
+ min = duration.min(min);
+ max = duration.max(max);
+ }
+
+ assert_ne!(min, f64::MAX);
+ assert_ne!(max, f64::MIN);
+ assert!(min.is_sign_positive());
+ assert!(max.is_sign_positive());
+
+ (min, max)
+ }
+
+ pub fn axes(timings: &timings::Output) -> (Axis, Axis) {
+ let pkg_durations: Vec<f64> = timings.pkg_times().collect();
+ let (min_duration, max_duration) = find_min_max_duration(&pkg_durations);
+ let bucket_width = (max_duration - min_duration) / (BUCKET_COUNT as f64);
+
+ let mut x_labels = Vec::with_capacity(BUCKET_COUNT);
+ for bucket_index in 0..BUCKET_COUNT {
+ // The start time is offset, first bucket starts at min_duration
+ let start_time = min_duration + (bucket_width * (bucket_index as f64));
+ // The label is the start time rounded to 2 decimal places
+ x_labels.push(format!("{start_time:.2}"));
+ }
+
+ let x_axis = Axis::new().type_(AxisType::Category).data(x_labels);
+ let y_axis = Axis::new().type_(AxisType::Value);
+
+ (x_axis, y_axis)
+ }
+
+ pub fn data(timings: &timings::Output) -> Vec<DataPoint> {
+ let pkg_durations: Vec<f64> = timings.pkg_times().collect();
+ let (min_duration, max_duration) = find_min_max_duration(&pkg_durations);
+
+ // Make sure to start the buckets at min, not 0
+ let bucket_width = (max_duration - min_duration) / (BUCKET_COUNT as f64);
+ let mut buckets = [0_u64; BUCKET_COUNT];
+
+ for duration in pkg_durations {
+ let relative_duration = duration - min_duration;
+ let remainder = relative_duration % bucket_width;
+ // Calculate the nearest multiple of bucket_size
+ let next_multiple = (relative_duration - remainder) / bucket_width;
+
+ // Make sure we have an integer before casting
+ assert_eq!(next_multiple, next_multiple.floor());
+ let bucket_index = next_multiple as usize;
+
+ // Increment the frequency of the relevant bucket
+ buckets[bucket_index] += 1;
+ }
+
+ // Convert the buckets into `charming::datatype::DataPoint`s
+ buckets
+ .into_iter()
+ .map(|bucket| (bucket as i64).into())
+ .collect()
+ }