fuchsia_inspect/writer/types/
uint_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, Node, UintArrayProperty,
7};
8use diagnostics_hierarchy::{ArrayFormat, ExponentialHistogramParams};
9use log::error;
10use std::borrow::Cow;
11
12#[derive(Debug, Default)]
13/// An exponential histogram property for uint values.
14pub struct UintExponentialHistogramProperty {
15    array: UintArrayProperty,
16    floor: u64,
17    initial_step: u64,
18    step_multiplier: u64,
19    buckets: usize,
20}
21
22impl InspectType for UintExponentialHistogramProperty {}
23
24impl UintExponentialHistogramProperty {
25    pub(crate) fn new(
26        name: Cow<'_, str>,
27        params: ExponentialHistogramParams<u64>,
28        parent: &Node,
29    ) -> Self {
30        let slots = params.buckets + ArrayFormat::ExponentialHistogram.extra_slots();
31        let array =
32            parent.create_uint_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: u64) -> usize {
46        let floor = self.floor;
47        let mut bucket_end = floor; // The exclusive end of a bucket's range.
48        let mut step = self.initial_step;
49
50        let mut index = ArrayFormat::ExponentialHistogram.underflow_bucket_index();
51        let overflow_index = ArrayFormat::ExponentialHistogram.overflow_bucket_index(self.buckets);
52        while value >= bucket_end && index < overflow_index {
53            if let Some(c) = floor.checked_add(step) {
54                bucket_end = c;
55            } else {
56                // Overflow. The next bucket is guaranteed to be choosen, as it
57                // contains all possible remaining values for a u64.
58                return index + 1;
59            }
60
61            step = step.saturating_mul(self.step_multiplier);
62            index += 1;
63        }
64        index
65    }
66}
67
68impl HistogramProperty for UintExponentialHistogramProperty {
69    type Type = u64;
70
71    fn insert(&self, value: u64) {
72        self.insert_multiple(value, 1);
73    }
74
75    fn insert_multiple(&self, value: u64, count: usize) {
76        self.array.add(self.get_index(value), count as u64);
77    }
78
79    fn clear(&self) {
80        if let Some(ref inner_ref) = self.array.inner.inner_ref() {
81            // Ensure we don't delete the array slots that contain histogram metadata.
82            inner_ref
83                .state
84                .try_lock()
85                .and_then(|mut state| {
86                    // Clear histogram buckets starting at first bucket, which
87                    // is the underflow bucket.
88                    state.clear_array(
89                        inner_ref.block_index,
90                        ArrayFormat::ExponentialHistogram.underflow_bucket_index(),
91                    )
92                })
93                .unwrap_or_else(|err| {
94                    error!(err:?; "Failed to clear property");
95                });
96        }
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use crate::writer::Inspector;
104    use crate::writer::testing_utils::GetBlockExt;
105    use inspect_format::{Array, Uint};
106
107    #[fuchsia::test]
108    fn test_uint_exp_histogram() {
109        let inspector = Inspector::default();
110        let root = inspector.root();
111        let node = root.create_child("node");
112        {
113            let uint_histogram = node.create_uint_exponential_histogram(
114                "uint-histogram",
115                ExponentialHistogramParams {
116                    floor: 1,
117                    initial_step: 1,
118                    step_multiplier: 2,
119                    buckets: 4,
120                },
121            );
122            uint_histogram.insert_multiple(0, 2); // underflow
123            uint_histogram.insert(8);
124            uint_histogram.insert(500); // overflow
125            uint_histogram.array.get_block::<_, Array<Uint>>(|block| {
126                for (i, value) in [1, 1, 2, 2, 0, 0, 0, 1, 1].iter().enumerate() {
127                    assert_eq!(block.get(i).unwrap(), *value);
128                }
129            });
130
131            uint_histogram.clear();
132            uint_histogram.array.get_block::<_, Array<Uint>>(|block| {
133                for (i, value) in [1, 1, 2, 0, 0, 0, 0, 0, 0].iter().enumerate() {
134                    assert_eq!(*value, block.get(i).unwrap());
135                }
136            });
137
138            node.get_block::<_, inspect_format::Node>(|node_block| {
139                assert_eq!(node_block.child_count(), 1);
140            });
141        }
142        node.get_block::<_, inspect_format::Node>(|node_block| {
143            assert_eq!(node_block.child_count(), 0);
144        });
145    }
146
147    #[fuchsia::test]
148    fn overflow_underflow() {
149        let inspector = Inspector::default();
150        let root = inspector.root();
151        let hist = root.create_uint_exponential_histogram(
152            "test",
153            ExponentialHistogramParams {
154                floor: 1,
155                initial_step: u64::MAX / 2,
156                step_multiplier: 2,
157                buckets: 4,
158            },
159        );
160
161        // | Bucket    | Range                      |
162        // |-----------|----------------------------|
163        // | Underflow | [0, 1)                     |
164        // | 0         | [1, u64::MAX/2 + 1)        |
165        // | 1         | [u64::MAX/2 + 1, u64::MAX] |
166        // | 2..3      | Empty                      |
167        // | Overflow  | Empty                      |
168
169        hist.insert((u64::MAX / 2) + 1);
170        hist.insert(0);
171
172        hist.array.get_block::<_, Array<Uint>>(|block| {
173            assert_eq!(block.get(0).unwrap(), 1);
174            assert_eq!(block.get(1).unwrap(), u64::MAX / 2);
175            assert_eq!(block.get(2).unwrap(), 2);
176
177            assert_eq!(block.get(3).unwrap(), 1); // underflow
178            assert_eq!(block.get(4).unwrap(), 0); // bucket 0
179            assert_eq!(block.get(5).unwrap(), 1); // bucket 1
180            assert_eq!(block.get(6).unwrap(), 0); // bucket 2
181            assert_eq!(block.get(7).unwrap(), 0); // bucket 3
182            assert_eq!(block.get(8).unwrap(), 0); // overflow
183        });
184    }
185}