1use std::mem;
6use std::sync::Arc;
7
8use crate::common_metric_data::{CommonMetricDataInternal, DynamicLabelType};
9use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
10use crate::histogram::{Bucketing, Histogram, HistogramType, LinearOrExponential};
11use crate::metrics::{DistributionData, Metric, MetricType};
12use crate::storage::StorageManager;
13use crate::Glean;
14use crate::{CommonMetricData, TestGetValue};
15
16#[derive(Clone, Debug)]
18pub struct CustomDistributionMetric {
19 meta: Arc<CommonMetricDataInternal>,
20 range_min: u64,
21 range_max: u64,
22 bucket_count: u64,
23 histogram_type: HistogramType,
24}
25
26pub(crate) fn snapshot<B: Bucketing>(hist: &Histogram<B>) -> DistributionData {
30 DistributionData {
31 values: hist
32 .snapshot_values()
33 .into_iter()
34 .map(|(k, v)| (k as i64, v as i64))
35 .collect(),
36 sum: hist.sum() as i64,
37 count: hist.count() as i64,
38 }
39}
40
41impl MetricType for CustomDistributionMetric {
42 fn meta(&self) -> &CommonMetricDataInternal {
43 &self.meta
44 }
45
46 fn with_name(&self, name: String) -> Self {
47 let mut meta = (*self.meta).clone();
48 meta.inner.name = name;
49 Self {
50 meta: Arc::new(meta),
51 range_min: self.range_min,
52 range_max: self.range_max,
53 bucket_count: self.bucket_count,
54 histogram_type: self.histogram_type,
55 }
56 }
57
58 fn with_dynamic_label(&self, label: DynamicLabelType) -> Self {
59 let mut meta = (*self.meta).clone();
60 meta.inner.dynamic_label = Some(label);
61 Self {
62 meta: Arc::new(meta),
63 range_min: self.range_min,
64 range_max: self.range_max,
65 bucket_count: self.bucket_count,
66 histogram_type: self.histogram_type,
67 }
68 }
69}
70
71impl CustomDistributionMetric {
76 pub fn new(
78 meta: CommonMetricData,
79 range_min: i64,
80 range_max: i64,
81 bucket_count: i64,
82 histogram_type: HistogramType,
83 ) -> Self {
84 Self {
85 meta: Arc::new(meta.into()),
86 range_min: range_min as u64,
87 range_max: range_max as u64,
88 bucket_count: bucket_count as u64,
89 histogram_type,
90 }
91 }
92
93 pub fn accumulate_samples(&self, samples: Vec<i64>) {
109 let metric = self.clone();
110 crate::launch_with_glean(move |glean| metric.accumulate_samples_sync(glean, &samples))
111 }
112
113 pub fn accumulate_single_sample(&self, sample: i64) {
128 let metric = self.clone();
129 crate::launch_with_glean(move |glean| metric.accumulate_samples_sync(glean, &[sample]))
130 }
131
132 #[doc(hidden)]
136 pub fn accumulate_samples_sync(&self, glean: &Glean, samples: &[i64]) {
137 if !self.should_record(glean) {
138 return;
139 }
140
141 let mut num_negative_samples = 0;
142
143 fn accumulate<B: Bucketing, F>(
146 samples: &[i64],
147 mut hist: Histogram<B>,
148 metric: F,
149 ) -> (i32, Metric)
150 where
151 F: Fn(Histogram<B>) -> Metric,
152 {
153 let mut num_negative_samples = 0;
154 for &sample in samples.iter() {
155 if sample < 0 {
156 num_negative_samples += 1;
157 } else {
158 let sample = sample as u64;
159 hist.accumulate(sample);
160 }
161 }
162 (num_negative_samples, metric(hist))
163 }
164
165 glean.storage().record_with(glean, &self.meta, |old_value| {
166 let (num_negative, hist) = match self.histogram_type {
167 HistogramType::Linear => {
168 let hist = if let Some(Metric::CustomDistributionLinear(hist)) = old_value {
169 hist
170 } else {
171 Histogram::linear(
172 self.range_min,
173 self.range_max,
174 self.bucket_count as usize,
175 )
176 };
177 accumulate(samples, hist, Metric::CustomDistributionLinear)
178 }
179 HistogramType::Exponential => {
180 let hist = if let Some(Metric::CustomDistributionExponential(hist)) = old_value
181 {
182 hist
183 } else {
184 Histogram::exponential(
185 self.range_min,
186 self.range_max,
187 self.bucket_count as usize,
188 )
189 };
190 accumulate(samples, hist, Metric::CustomDistributionExponential)
191 }
192 };
193
194 num_negative_samples = num_negative;
195 hist
196 });
197
198 if num_negative_samples > 0 {
199 let msg = format!("Accumulated {} negative samples", num_negative_samples);
200 record_error(
201 glean,
202 &self.meta,
203 ErrorType::InvalidValue,
204 msg,
205 num_negative_samples,
206 );
207 }
208 }
209
210 #[doc(hidden)]
212 pub fn get_value<'a, S: Into<Option<&'a str>>>(
213 &self,
214 glean: &Glean,
215 ping_name: S,
216 ) -> Option<DistributionData> {
217 let queried_ping_name = ping_name
218 .into()
219 .unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
220
221 match StorageManager.snapshot_metric_for_test(
222 glean.storage(),
223 queried_ping_name,
224 &self.meta.identifier(glean),
225 self.meta.inner.lifetime,
226 ) {
227 Some(Metric::CustomDistributionExponential(hist)) => Some(snapshot(&hist)),
229 Some(Metric::CustomDistributionLinear(hist)) => Some(snapshot(&hist)),
230 _ => None,
231 }
232 }
233
234 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
246 crate::block_on_dispatcher();
247
248 crate::core::with_glean(|glean| {
249 test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
250 })
251 }
252
253 pub fn start_buffer(&self) -> LocalCustomDistribution<'_> {
258 LocalCustomDistribution::new(self)
259 }
260
261 fn commit_histogram(&self, histogram: Histogram<LinearOrExponential>) {
262 let metric = self.clone();
263 crate::launch_with_glean(move |glean| {
264 glean
265 .storage()
266 .record_with(glean, &metric.meta, move |old_value| {
267 match metric.histogram_type {
268 HistogramType::Linear => {
269 let mut hist =
270 if let Some(Metric::CustomDistributionLinear(hist)) = old_value {
271 hist
272 } else {
273 Histogram::linear(
274 metric.range_min,
275 metric.range_max,
276 metric.bucket_count as usize,
277 )
278 };
279
280 hist._merge(&histogram);
281 Metric::CustomDistributionLinear(hist)
282 }
283 HistogramType::Exponential => {
284 let mut hist = if let Some(Metric::CustomDistributionExponential(
285 hist,
286 )) = old_value
287 {
288 hist
289 } else {
290 Histogram::exponential(
291 metric.range_min,
292 metric.range_max,
293 metric.bucket_count as usize,
294 )
295 };
296
297 hist._merge(&histogram);
298 Metric::CustomDistributionExponential(hist)
299 }
300 }
301 });
302 });
303 }
304}
305
306impl TestGetValue<DistributionData> for CustomDistributionMetric {
307 fn test_get_value(&self, ping_name: Option<String>) -> Option<DistributionData> {
322 crate::block_on_dispatcher();
323 crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref()))
324 }
325}
326
327pub struct LocalCustomDistribution<'a> {
332 histogram: Histogram<LinearOrExponential>,
333 metric: &'a CustomDistributionMetric,
334}
335
336impl<'a> LocalCustomDistribution<'a> {
337 fn new(metric: &'a CustomDistributionMetric) -> Self {
339 let histogram = match metric.histogram_type {
340 HistogramType::Linear => Histogram::<LinearOrExponential>::_linear(
341 metric.range_min,
342 metric.range_max,
343 metric.bucket_count as usize,
344 ),
345 HistogramType::Exponential => Histogram::<LinearOrExponential>::_exponential(
346 metric.range_min,
347 metric.range_max,
348 metric.bucket_count as usize,
349 ),
350 };
351 Self { histogram, metric }
352 }
353
354 pub fn accumulate(&mut self, sample: u64) {
362 self.histogram.accumulate(sample)
363 }
364
365 pub fn abandon(mut self) {
367 self.histogram.clear();
368 }
369}
370
371impl Drop for LocalCustomDistribution<'_> {
372 fn drop(&mut self) {
373 if self.histogram.is_empty() {
374 return;
375 }
376
377 let empty = Histogram::_linear(0, 0, 0);
380 let buffer = mem::replace(&mut self.histogram, empty);
381 self.metric.commit_histogram(buffer);
382 }
383}