diff --git a/tidepool/src/output.rs b/tidepool/src/output.rs index fd63da9..c96f70c 100644 --- a/tidepool/src/output.rs +++ b/tidepool/src/output.rs @@ -34,6 +34,95 @@ pub struct Profile { // TODO: histograms about selected nodes } +#[derive(Copy, Clone, Debug)] +pub struct SummaryStats { + count: usize, + min: Option, + max: Option, + sum: Option, +} + +impl Default for SummaryStats { + fn default() -> Self { + SummaryStats { + count: 0, + min: None, + max: None, + sum: None, + } + } +} + +impl SummaryStats { + pub fn new() -> Self { + Self::default() + } + + pub fn count(&self) -> usize { + self.count + } + + pub fn min(&self) -> Option { + self.min + } + + pub fn max(&self) -> Option { + self.max + } +} + +impl SummaryStats +where + T: Ord + Copy + std::ops::Add, +{ + pub fn insert(&mut self, x: T) { + self.count += 1; + self.min = Some(self.min.map_or(x, |y| x.min(y))); + self.max = Some(self.max.map_or(x, |y| x.max(y))); + self.sum = Some(self.sum.map_or(x, |y| x + y)); + } +} + +impl SummaryStats { + pub fn insert_f64(&mut self, x: f64) { + self.count += 1; + self.min = Some(self.min.map_or(x, |y| x.min(y))); + self.max = Some(self.max.map_or(x, |y| x.max(y))); + self.sum = Some(self.sum.map_or(x, |y| x + y)); + } +} + +impl SummaryStats +where + T: Copy + std::ops::Div, +{ + pub fn avg(&self) -> Option { + self.sum.map(|x| x / self.count) + } +} + +impl SummaryStats +where + T: Copy, + f64: From, +{ + pub fn avg_f64(&self) -> Option { + self.sum.map(|x| f64::from(x) / self.count as f64) + } +} + +impl Serialize for SummaryStats +where + T: Serialize + Copy + std::ops::Div, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + ser::summary_stats(self, serializer) + } +} + mod ser { use super::*; @@ -78,4 +167,27 @@ mod ser { { dur.as_secs_f64().serialize(serializer) } + + pub fn summary_stats(stats: &SummaryStats, serializer: S) -> Result + where + T: Serialize + Copy + std::ops::Div, + S: serde::Serializer, + { + #[derive(Serialize)] + struct Stats { + #[serde(skip_serializing_if = "Option::is_none")] + min: Option, + #[serde(skip_serializing_if = "Option::is_none")] + max: Option, + #[serde(skip_serializing_if = "Option::is_none")] + avg: Option, + } + + Stats { + min: stats.min, + max: stats.max, + avg: stats.avg(), + } + .serialize(serializer) + } }