1use crate::{Config, Error, Histogram};
2use core::sync::atomic::{AtomicU64, Ordering};
3
4pub struct AtomicHistogram {
10 config: Config,
11 buckets: Box<[AtomicU64]>,
12}
13
14impl AtomicHistogram {
15 pub fn new(p: u8, n: u8) -> Result<Self, Error> {
18 let config = Config::new(p, n)?;
19
20 Ok(Self::with_config(&config))
21 }
22
23 pub fn with_config(config: &Config) -> Self {
25 let mut buckets = Vec::with_capacity(config.total_buckets());
26 buckets.resize_with(config.total_buckets(), || AtomicU64::new(0));
27
28 Self {
29 config: *config,
30 buckets: buckets.into(),
31 }
32 }
33
34 pub fn increment(&self, value: u64) -> Result<(), Error> {
36 self.add(value, 1)
37 }
38
39 pub fn add(&self, value: u64, count: u64) -> Result<(), Error> {
41 let index = self.config.value_to_index(value)?;
42 self.buckets[index].fetch_add(count, Ordering::Relaxed);
43 Ok(())
44 }
45
46 #[cfg(target_has_atomic = "64")]
48 pub fn drain(&self) -> Histogram {
53 let buckets: Vec<u64> = self
54 .buckets
55 .iter()
56 .map(|bucket| bucket.swap(0, Ordering::Relaxed))
57 .collect();
58
59 Histogram {
60 config: self.config,
61 buckets: buckets.into(),
62 }
63 }
64
65 pub fn load(&self) -> Histogram {
67 let buckets: Vec<u64> = self
68 .buckets
69 .iter()
70 .map(|bucket| bucket.load(Ordering::Relaxed))
71 .collect();
72
73 Histogram {
74 config: self.config,
75 buckets: buckets.into(),
76 }
77 }
78}
79
80#[cfg(test)]
81mod test {
82 use crate::*;
83
84 #[cfg(target_pointer_width = "64")]
85 #[test]
86 fn size() {
87 assert_eq!(std::mem::size_of::<AtomicHistogram>(), 48);
88 }
89
90 #[cfg(target_has_atomic = "64")]
91 #[test]
92 fn drain() {
94 let histogram = AtomicHistogram::new(7, 64).unwrap();
95 for i in 0..=100 {
96 let _ = histogram.increment(i);
97 }
98 let percentiles = histogram.drain();
99 assert_eq!(
100 percentiles.percentile(50.0),
101 Ok(Some(Bucket {
102 count: 1,
103 range: 50..=50,
104 }))
105 );
106 histogram.increment(1000).unwrap();
107 let percentiles = histogram.drain();
109 assert_eq!(
110 percentiles.percentile(50.0),
111 Ok(Some(Bucket {
112 count: 1,
113 range: 1000..=1003,
114 }))
115 );
116 }
117
118 #[test]
119 fn percentiles() {
121 let histogram = AtomicHistogram::new(7, 64).unwrap();
122 let percentiles = [25.0, 50.0, 75.0, 90.0, 99.0];
123
124 assert_eq!(histogram.load().percentiles(&percentiles), Ok(None));
126
127 for percentile in percentiles {
128 assert_eq!(histogram.load().percentile(percentile), Ok(None));
129 }
130
131 for i in 0..=100 {
133 let _ = histogram.increment(i);
134 assert_eq!(
135 histogram.load().percentile(0.0),
136 Ok(Some(Bucket {
137 count: 1,
138 range: 0..=0,
139 }))
140 );
141 assert_eq!(
142 histogram.load().percentile(100.0),
143 Ok(Some(Bucket {
144 count: 1,
145 range: i..=i,
146 }))
147 );
148 }
149
150 for percentile in percentiles {
151 assert_eq!(
152 histogram
153 .load()
154 .percentile(percentile)
155 .map(|b| b.unwrap().end()),
156 Ok(percentile as u64)
157 );
158 }
159
160 assert_eq!(
161 histogram.load().percentile(99.9).map(|b| b.unwrap().end()),
162 Ok(100)
163 );
164
165 assert_eq!(
166 histogram.load().percentile(-1.0),
167 Err(Error::InvalidPercentile)
168 );
169 assert_eq!(
170 histogram.load().percentile(101.0),
171 Err(Error::InvalidPercentile)
172 );
173
174 let percentiles: Vec<(f64, u64)> = histogram
175 .load()
176 .percentiles(&[50.0, 90.0, 99.0, 99.9])
177 .unwrap()
178 .unwrap()
179 .iter()
180 .map(|(p, b)| (*p, b.end()))
181 .collect();
182
183 assert_eq!(
184 percentiles,
185 vec![(50.0, 50), (90.0, 90), (99.0, 99), (99.9, 100)]
186 );
187
188 let _ = histogram.increment(1024);
189 assert_eq!(
190 histogram.load().percentile(99.9),
191 Ok(Some(Bucket {
192 count: 1,
193 range: 1024..=1031,
194 }))
195 );
196 }
197}