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)]
12 pub struct Quantile(f64, String);
13 
14 impl Quantile {
15     /// Creates a new [`Quantile`] from a floating-point value.
16     ///
17     /// All values are clamped between 0.0 and 1.0.
new(quantile: f64) -> Quantile18     pub fn new(quantile: f64) -> Quantile {
19         let clamped = quantile.max(0.0);
20         let clamped = clamped.min(1.0);
21         let display = clamped * 100.0;
22 
23         let raw_label = format!("{}", clamped);
24         let label = match raw_label.as_str() {
25             "0" => "min".to_string(),
26             "1" => "max".to_string(),
27             _ => {
28                 let raw = format!("p{}", display);
29                 raw.replace(".", "")
30             }
31         };
32 
33         Quantile(clamped, label)
34     }
35 
36     /// Gets the human-friendly display label.
label(&self) -> &str37     pub fn label(&self) -> &str {
38         self.1.as_str()
39     }
40 
41     /// Gets the raw quantile value.
value(&self) -> f6442     pub fn value(&self) -> f64 {
43         self.0
44     }
45 }
46 
47 /// Parses a slice of floating-point values into a vector of [`Quantile`]s.
parse_quantiles(quantiles: &[f64]) -> Vec<Quantile>48 pub fn parse_quantiles(quantiles: &[f64]) -> Vec<Quantile> {
49     quantiles.iter().map(|f| Quantile::new(*f)).collect()
50 }
51 
52 #[cfg(test)]
53 mod tests {
54     use super::{parse_quantiles, Quantile};
55 
56     #[test]
test_quantiles()57     fn test_quantiles() {
58         let min = Quantile::new(0.0);
59         assert_eq!(min.value(), 0.0);
60         assert_eq!(min.label(), "min");
61 
62         let max = Quantile::new(1.0);
63         assert_eq!(max.value(), 1.0);
64         assert_eq!(max.label(), "max");
65 
66         let p99 = Quantile::new(0.99);
67         assert_eq!(p99.value(), 0.99);
68         assert_eq!(p99.label(), "p99");
69 
70         let p999 = Quantile::new(0.999);
71         assert_eq!(p999.value(), 0.999);
72         assert_eq!(p999.label(), "p999");
73 
74         let p9999 = Quantile::new(0.9999);
75         assert_eq!(p9999.value(), 0.9999);
76         assert_eq!(p9999.label(), "p9999");
77 
78         let under = Quantile::new(-1.0);
79         assert_eq!(under.value(), 0.0);
80         assert_eq!(under.label(), "min");
81 
82         let over = Quantile::new(1.2);
83         assert_eq!(over.value(), 1.0);
84         assert_eq!(over.label(), "max");
85     }
86 
87     #[test]
test_parse_quantiles()88     fn test_parse_quantiles() {
89         let empty = vec![];
90         let result = parse_quantiles(&empty);
91         assert_eq!(result.len(), 0);
92 
93         let normal = vec![0.0, 0.5, 0.99, 0.999, 1.0];
94         let result = parse_quantiles(&normal);
95         assert_eq!(result.len(), 5);
96         assert_eq!(result[0], Quantile::new(0.0));
97         assert_eq!(result[1], Quantile::new(0.5));
98         assert_eq!(result[2], Quantile::new(0.99));
99         assert_eq!(result[3], Quantile::new(0.999));
100         assert_eq!(result[4], Quantile::new(1.0));
101     }
102 }
103