settings_inspect_utils/
managed_inspect_queue.rs

1// Copyright 2022 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 fuchsia_inspect::Node;
6use fuchsia_inspect_derive::{AttachError, Inspect, WithInspect};
7use std::collections::VecDeque;
8
9/// A queue that wraps an inspect node and attaches all inserted values to the node.
10///
11/// This class can either be explicitly given an inspect node through
12/// [ManagedInspectQueue::with_node] or can create its own inspect node when included in a struct
13/// that derives Inspect or when [ManagedInspectQueue::with_inspect] is called. ManagedInspectQueue
14/// will only keep the last [size_limit] items.
15#[derive(Default)]
16pub struct ManagedInspectQueue<V> {
17    items: VecDeque<V>,
18    // Required because VecDeque::with_capacity doesn't necessarily give the
19    // exact capacity specified.
20    size_limit: usize,
21    inspect_node: Node,
22}
23
24impl<V> ManagedInspectQueue<V>
25where
26    for<'a> &'a mut V: Inspect,
27    V: std::fmt::Debug + std::default::Default,
28{
29    /// Creates a new [ManagedInspectQueue] with a default node. This gives the parent
30    /// the option to call `with_inspect` itself instead of passing a node into `with_node`.
31    pub fn new(size_limit: usize) -> Self {
32        let mut default = ManagedInspectQueue::<V>::default();
33        default.set_size_limit(size_limit);
34        default
35    }
36
37    /// Creates a new [ManagedInspectQueue] that attaches inserted values to the given node.
38    /// A size limit of 0 indicates an unlimited length.
39    pub fn with_node(node: Node, size_limit: usize) -> Self {
40        Self { items: VecDeque::with_capacity(size_limit), size_limit, inspect_node: node }
41    }
42
43    /// Sets the max number of elements allowed in the queue. If the size is smaller than the
44    /// [new_size_limit], the oldest elements will be dropped until it is the right size. A size
45    /// limit of 0 indicates an unlimited length.
46    fn set_size_limit(&mut self, new_size_limit: usize) {
47        while self.items.len() > new_size_limit {
48            let _ = self.items.pop_front();
49        }
50        self.size_limit = new_size_limit;
51    }
52
53    /// Returns a mutable iterator for the underlying queue.
54    pub fn iter_mut(&mut self) -> std::collections::vec_deque::IterMut<'_, V> {
55        self.items.iter_mut()
56    }
57
58    /// Returns a mutable reference to the underlying queue. Items should be inserted with this
59    /// reference.
60    pub fn items_mut(&mut self) -> &mut VecDeque<V> {
61        &mut self.items
62    }
63
64    /// Filters the queue by the condition function [f].
65    pub fn retain<F>(&mut self, f: F)
66    where
67        F: FnMut(&V) -> bool,
68    {
69        self.items.retain(f);
70    }
71
72    /// Returns a reference to the [ManagedInspectQueue]'s node.
73    pub fn inspect_node(&self) -> &Node {
74        &self.inspect_node
75    }
76
77    /// Inserts the given value into the queue and attaches it to the inspect tree. If the new
78    /// size of the queue is over capacity, the oldest value is removed.
79    pub fn push(&mut self, key: &str, item: V) {
80        // If the queue is over capacity, remove one. If the size limit is 0, assume no limit.
81        if self.items.len() == self.size_limit && self.size_limit != 0 {
82            let _ = self.items.pop_front();
83        }
84        let node = &self.inspect_node;
85        self.items
86            .push_back(item.with_inspect(node, key).expect("Failed to attach new queue entry."));
87    }
88
89    #[cfg(test)]
90    pub(crate) fn len(&self) -> usize {
91        self.items.len()
92    }
93}
94
95impl<V> Inspect for &mut ManagedInspectQueue<V>
96where
97    for<'a> &'a mut V: Inspect,
98{
99    fn iattach(self, parent: &Node, name: impl AsRef<str>) -> Result<(), AttachError> {
100        self.inspect_node = parent.create_child(name.as_ref());
101        Ok(())
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use crate::managed_inspect_queue::ManagedInspectQueue;
108    use diagnostics_assertions::assert_data_tree;
109    use fuchsia_inspect::{self as inspect, Node};
110    use fuchsia_inspect_derive::{IValue, Inspect, WithInspect};
111
112    #[derive(Default, Inspect)]
113    struct TestInspectWrapper {
114        inspect_node: Node,
115        queue: ManagedInspectQueue<TestInspectItem>,
116    }
117
118    #[derive(Debug, Default, Inspect)]
119    struct TestInspectItem {
120        inspect_node: Node,
121        id: IValue<u64>,
122    }
123
124    impl TestInspectItem {
125        fn new(id: u64) -> Self {
126            Self { inspect_node: Node::default(), id: id.into() }
127        }
128    }
129
130    // Test that a queue with less items than its capacity has the correct
131    // inspect and queue content.
132    #[fuchsia::test]
133    fn test_queue_under_capacity() {
134        let inspector = inspect::Inspector::default();
135        let mut wrapper = TestInspectWrapper::default()
136            .with_inspect(inspector.root(), "inspect_wrapper")
137            .expect("failed to create TestInspectWrapper inspect node");
138        wrapper.queue.set_size_limit(2);
139
140        let test_val_1 = TestInspectItem::new(6);
141        let test_val_2 = TestInspectItem::new(5);
142
143        wrapper.queue.push("0", test_val_1);
144        wrapper.queue.push("1", test_val_2);
145
146        assert_data_tree!(inspector, root: {
147            inspect_wrapper: {
148                queue: {
149                    "0": {
150                        "id": 6_u64,
151                    },
152                    "1": {
153                        "id": 5_u64,
154                    },
155                },
156            }
157        });
158        assert_eq!(wrapper.queue.len(), 2);
159    }
160
161    // Test that a queue with more items than its capacity has the correct
162    // inspect and queue content.
163    #[fuchsia::test]
164    fn test_queue_over_capacity() {
165        let inspector = inspect::Inspector::default();
166        let mut wrapper = TestInspectWrapper::default()
167            .with_inspect(inspector.root(), "inspect_wrapper")
168            .expect("failed to create TestInspectWrapper inspect node");
169        wrapper.queue.set_size_limit(2);
170
171        let test_val_1 = TestInspectItem::new(6);
172        let test_val_2 = TestInspectItem::new(5);
173        let test_val_3 = TestInspectItem::new(4);
174
175        wrapper.queue.push("0", test_val_1);
176        wrapper.queue.push("1", test_val_2);
177        wrapper.queue.push("2", test_val_3);
178
179        assert_data_tree!(inspector, root: {
180            inspect_wrapper: {
181                queue: {
182                    "1": {
183                        "id": 5_u64,
184                    },
185                    "2": {
186                        "id": 4_u64,
187                    },
188                },
189            }
190        });
191        assert_eq!(wrapper.queue.len(), 2);
192    }
193
194    // Test that when setting the size limit smaller than the current number of elements, the
195    // excess elements are dropped.
196    #[fuchsia::test]
197    fn test_size_limit() {
198        let inspector = inspect::Inspector::default();
199        let mut wrapper = TestInspectWrapper::default()
200            .with_inspect(inspector.root(), "inspect_wrapper")
201            .expect("failed to create TestInspectWrapper inspect node");
202
203        let test_val_1 = TestInspectItem::new(6);
204        let test_val_2 = TestInspectItem::new(5);
205        let test_val_3 = TestInspectItem::new(4);
206
207        wrapper.queue.push("0", test_val_1);
208        wrapper.queue.push("1", test_val_2);
209        wrapper.queue.push("2", test_val_3);
210        wrapper.queue.set_size_limit(1);
211
212        assert_data_tree!(inspector, root: {
213            inspect_wrapper: {
214                queue: {
215                    "2": {
216                        "id": 4_u64,
217                    },
218                },
219            }
220        });
221        assert_eq!(wrapper.queue.len(), 1);
222    }
223
224    // Tests that removing items from the queue automatically removes them from inspect.
225    #[fuchsia::test]
226    fn test_queue_remove() {
227        let inspector = inspect::Inspector::default();
228
229        let mut queue = ManagedInspectQueue::<IValue<String>>::with_node(
230            inspector.root().create_child("managed_node"),
231            10,
232        );
233
234        queue.push("0", "value1".to_string().into());
235        queue.push("1", "value2".to_string().into());
236        queue.retain(|item| **item != "value1");
237
238        assert_data_tree!(inspector, root: {
239            managed_node: {
240                "1": "value2"
241            }
242        });
243    }
244}