fuchsia_inspect/writer/types/
int_exponential_histogram.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::writer::{
6    ArithmeticArrayProperty, ArrayProperty, HistogramProperty, InspectType, IntArrayProperty, Node,
7};
8use diagnostics_hierarchy::{ArrayFormat, ExponentialHistogramParams};
9use log::error;
10use std::borrow::Cow;
11
12#[derive(Debug, Default)]
13/// An exponential histogram property for int values.
14pub struct IntExponentialHistogramProperty {
15    array: IntArrayProperty,
16    floor: i64,
17    initial_step: i64,
18    step_multiplier: i64,
19    buckets: usize,
20}
21
22impl InspectType for IntExponentialHistogramProperty {}
23
24impl IntExponentialHistogramProperty {
25    pub(crate) fn new(
26        name: Cow<'_, str>,
27        params: ExponentialHistogramParams<i64>,
28        parent: &Node,
29    ) -> Self {
30        let slots = params.buckets + ArrayFormat::ExponentialHistogram.extra_slots();
31        let array =
32            parent.create_int_array_internal(name, slots, ArrayFormat::ExponentialHistogram);
33        array.set(0, params.floor);
34        array.set(1, params.initial_step);
35        array.set(2, params.step_multiplier);
36        Self {
37            floor: params.floor,
38            initial_step: params.initial_step,
39            step_multiplier: params.step_multiplier,
40            buckets: params.buckets,
41            array,
42        }
43    }
44
45    fn get_index(&self, value: i64) -> usize {
46        // Use a larger data type to support histograms of total width larger
47        // than i64::MAX (e.g. histogram range spanning from i64::MIN to
48        // i64::MAX, a width of u64::MAX).
49        let value = value as i128;
50        let floor = self.floor as i128;
51        let mut bucket_end = floor; // Exclusive end of the current bucket.
52        let mut step = self.initial_step as i128;
53
54        let mut index = ArrayFormat::ExponentialHistogram.underflow_bucket_index();
55        let overflow_index = ArrayFormat::ExponentialHistogram.overflow_bucket_index(self.buckets);
56
57        while value >= bucket_end && index < overflow_index {
58            if let Some(c) = floor.checked_add(step) {
59                bucket_end = c;
60            } else {
61                // Overflow. The next bucket contains all possible remaining
62                // values for an i128; it is guaranteed to be choosen.
63                return index + 1;
64            }
65
66            if bucket_end > i64::MAX as i128 {
67                // The next bucket extends beyond representable i64; it is
68                // guaranteed to be choosen.
69                return index + 1;
70            }
71
72            step = step.saturating_mul(self.step_multiplier as i128);
73            index += 1;
74        }
75        index
76    }
77}
78
79impl HistogramProperty for IntExponentialHistogramProperty {
80    type Type = i64;
81
82    fn insert(&self, value: i64) {
83        self.insert_multiple(value, 1);
84    }
85
86    fn insert_multiple(&self, value: i64, count: usize) {
87        self.array.add(self.get_index(value), count as i64);
88    }
89
90    fn clear(&self) {
91        if let Some(ref inner_ref) = self.array.inner.inner_ref() {
92            // Ensure we don't delete the array slots that contain histogram metadata.
93            inner_ref
94                .state
95                .try_lock()
96                .and_then(|mut state| {
97                    // Clear histogram buckets starting at first bucket, which
98                    // is the underflow bucket.
99                    state.clear_array(
100                        inner_ref.block_index,
101                        ArrayFormat::ExponentialHistogram.underflow_bucket_index(),
102                    )
103                })
104                .unwrap_or_else(|err| {
105                    error!(err:?; "Failed to clear property");
106                });
107        }
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use crate::writer::Inspector;
115    use crate::writer::testing_utils::GetBlockExt;
116    use inspect_format::{Array, Int};
117
118    #[fuchsia::test]
119    fn test_int_exp_histogram() {
120        let inspector = Inspector::default();
121        let root = inspector.root();
122        let node = root.create_child("node");
123        {
124            let int_histogram = node.create_int_exponential_histogram(
125                "int-histogram",
126                ExponentialHistogramParams {
127                    floor: 1,
128                    initial_step: 1,
129                    step_multiplier: 2,
130                    buckets: 4,
131                },
132            );
133            int_histogram.insert_multiple(-1, 2); // underflow
134            int_histogram.insert(8);
135            int_histogram.insert(500); // overflow
136            int_histogram.array.get_block::<_, Array<Int>>(|block| {
137                for (i, value) in [1, 1, 2, 2, 0, 0, 0, 1, 1].iter().enumerate() {
138                    assert_eq!(block.get(i).unwrap(), *value);
139                }
140            });
141
142            node.get_block::<_, inspect_format::Node>(|node_block| {
143                assert_eq!(node_block.child_count(), 1);
144            });
145        }
146        node.get_block::<_, inspect_format::Node>(|node_block| {
147            assert_eq!(node_block.child_count(), 0);
148        });
149    }
150
151    #[fuchsia::test]
152    fn exp_histogram_insert() {
153        let inspector = Inspector::default();
154        let root = inspector.root();
155        let hist = root.create_int_exponential_histogram(
156            "test",
157            ExponentialHistogramParams {
158                floor: 0,
159                initial_step: 2,
160                step_multiplier: 4,
161                buckets: 4,
162            },
163        );
164        for i in -200..200 {
165            hist.insert(i);
166        }
167        hist.array.get_block::<_, Array<Int>>(|block| {
168            assert_eq!(block.get(0).unwrap(), 0);
169            assert_eq!(block.get(1).unwrap(), 2);
170            assert_eq!(block.get(2).unwrap(), 4);
171
172            // Buckets
173            let i = 3;
174            assert_eq!(block.get(i).unwrap(), 200);
175            assert_eq!(block.get(i + 1).unwrap(), 2);
176            assert_eq!(block.get(i + 2).unwrap(), 6);
177            assert_eq!(block.get(i + 3).unwrap(), 24);
178            assert_eq!(block.get(i + 4).unwrap(), 96);
179            assert_eq!(block.get(i + 5).unwrap(), 72);
180        });
181    }
182
183    #[fuchsia::test]
184    fn overflow_underflow() {
185        let inspector = Inspector::default();
186        let root = inspector.root();
187        let hist = root.create_int_exponential_histogram(
188            "test",
189            ExponentialHistogramParams {
190                floor: 0,
191                initial_step: i64::MAX / 2,
192                step_multiplier: 2,
193                buckets: 4,
194            },
195        );
196
197        // this will get multiplied by initial step and overflow
198        hist.insert((i64::MAX / 2) + 1);
199
200        hist.insert(-5);
201
202        hist.array.get_block::<_, Array<Int>>(|block| {
203            assert_eq!(block.get(0).unwrap(), 0);
204            assert_eq!(block.get(1).unwrap(), i64::MAX / 2);
205            assert_eq!(block.get(2).unwrap(), 2);
206
207            assert_eq!(block.get(3).unwrap(), 1);
208            assert_eq!(block.get(4).unwrap(), 0);
209            assert_eq!(block.get(5).unwrap(), 1);
210            assert_eq!(block.get(6).unwrap(), 0);
211            assert_eq!(block.get(7).unwrap(), 0);
212            assert_eq!(block.get(8).unwrap(), 0);
213        });
214    }
215}