Skip to main content

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