1/// A quantile that has both the raw value and a human-friendly display label.
2///
3/// We work with quantiles for optimal floating-point precison over percentiles, but most of the
4/// time, monitoring systems show us percentiles, and usually in an abbreviated form: `p99`.
5///
6/// On top of holding the quantile value, we calculate the familiar "p99" style of label, doing the
7/// appropriate percentile conversion. Thus, if you have a quantile of `0.99`, the resulting label
8/// is `p99`, and if you have a quantile of `0.999`, the resulting label is `p999`.
9///
10/// There are two special cases, where we label `0.0` and `1.0` as `min` and `max`, respectively.
11#[derive(Debug, Clone, PartialEq)]
12pub struct Quantile(f64, String);
1314impl Quantile {
15/// Creates a new [`Quantile`] from a floating-point value.
16 ///
17 /// All values are clamped between 0.0 and 1.0.
18pub fn new(quantile: f64) -> Quantile {
19let clamped = quantile.max(0.0);
20let clamped = clamped.min(1.0);
21let display = clamped * 100.0;
2223let raw_label = format!("{}", clamped);
24let label = match raw_label.as_str() {
25"0" => "min".to_string(),
26"1" => "max".to_string(),
27_ => {
28let raw = format!("p{}", display);
29 raw.replace('.', "")
30 }
31 };
3233 Quantile(clamped, label)
34 }
3536/// Gets the human-friendly display label.
37pub fn label(&self) -> &str {
38self.1.as_str()
39 }
4041/// Gets the raw quantile value.
42pub fn value(&self) -> f64 {
43self.0
44}
45}
4647/// Parses a slice of floating-point values into a vector of [`Quantile`]s.
48pub fn parse_quantiles(quantiles: &[f64]) -> Vec<Quantile> {
49 quantiles.iter().map(|f| Quantile::new(*f)).collect()
50}
5152#[cfg(test)]
53mod tests {
54use super::{parse_quantiles, Quantile};
5556#[test]
57fn test_quantiles() {
58let min = Quantile::new(0.0);
59assert_eq!(min.value(), 0.0);
60assert_eq!(min.label(), "min");
6162let max = Quantile::new(1.0);
63assert_eq!(max.value(), 1.0);
64assert_eq!(max.label(), "max");
6566let p99 = Quantile::new(0.99);
67assert_eq!(p99.value(), 0.99);
68assert_eq!(p99.label(), "p99");
6970let p999 = Quantile::new(0.999);
71assert_eq!(p999.value(), 0.999);
72assert_eq!(p999.label(), "p999");
7374let p9999 = Quantile::new(0.9999);
75assert_eq!(p9999.value(), 0.9999);
76assert_eq!(p9999.label(), "p9999");
7778let under = Quantile::new(-1.0);
79assert_eq!(under.value(), 0.0);
80assert_eq!(under.label(), "min");
8182let over = Quantile::new(1.2);
83assert_eq!(over.value(), 1.0);
84assert_eq!(over.label(), "max");
85 }
8687#[test]
88fn test_parse_quantiles() {
89let empty = vec![];
90let result = parse_quantiles(&empty);
91assert_eq!(result.len(), 0);
9293let normal = vec![0.0, 0.5, 0.99, 0.999, 1.0];
94let result = parse_quantiles(&normal);
95assert_eq!(result.len(), 5);
96assert_eq!(result[0], Quantile::new(0.0));
97assert_eq!(result[1], Quantile::new(0.5));
98assert_eq!(result[2], Quantile::new(0.99));
99assert_eq!(result[3], Quantile::new(0.999));
100assert_eq!(result[4], Quantile::new(1.0));
101 }
102}