fuchsia_inspect/writer/types/
inspector.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::private::InspectTypeInternal;
6use crate::writer::state::Stats;
7use crate::writer::{Error, Heap, Node, State};
8use diagnostics_hierarchy::{DiagnosticsHierarchy, DiagnosticsHierarchyGetter};
9use inspect_format::{constants, BlockContainer, Container};
10use log::error;
11use std::borrow::Cow;
12use std::cmp::max;
13use std::sync::Arc;
14
15#[cfg(target_os = "fuchsia")]
16use zx::{self as zx, AsHandleRef, HandleBased};
17
18/// Root of the Inspect API. Through this API, further nodes can be created and inspect can be
19/// served.
20#[derive(Clone)]
21pub struct Inspector {
22    /// The root node.
23    root_node: Arc<Node>,
24
25    /// The storage backing the inspector. This is a VMO when working on Fuchsia.
26    #[allow(dead_code)] // unused and meaningless in the host build.
27    storage: Option<Arc<<Container as BlockContainer>::ShareableData>>,
28}
29
30impl DiagnosticsHierarchyGetter<String> for Inspector {
31    async fn get_diagnostics_hierarchy<'a>(&'a self) -> Cow<'_, DiagnosticsHierarchy>
32    where
33        String: 'a,
34    {
35        let hierarchy = crate::reader::read(self).await.expect("failed to get hierarchy");
36        Cow::Owned(hierarchy)
37    }
38}
39
40pub trait InspectorIntrospectionExt {
41    fn stats(&self) -> Option<Stats>;
42}
43
44impl InspectorIntrospectionExt for Inspector {
45    fn stats(&self) -> Option<Stats> {
46        self.state().and_then(|outer| outer.try_lock().ok().map(|state| state.stats()))
47    }
48}
49
50#[cfg(target_os = "fuchsia")]
51impl Inspector {
52    /// Returns a duplicate of the underlying VMO for this Inspector.
53    ///
54    /// The duplicated VMO will be read-only, and is suitable to send to clients over FIDL.
55    pub fn duplicate_vmo(&self) -> Option<zx::Vmo> {
56        self.storage.as_ref().and_then(|vmo| {
57            vmo.duplicate_handle(
58                zx::Rights::BASIC | zx::Rights::READ | zx::Rights::MAP | zx::Rights::GET_PROPERTY,
59            )
60            .ok()
61        })
62    }
63
64    /// Returns a duplicate of the underlying VMO for this Inspector with the given rights.
65    ///
66    /// The duplicated VMO will be read-only, and is suitable to send to clients over FIDL.
67    pub fn duplicate_vmo_with_rights(&self, rights: zx::Rights) -> Option<zx::Vmo> {
68        self.storage.as_ref().and_then(|vmo| vmo.duplicate_handle(rights).ok())
69    }
70
71    /// This produces a copy-on-write VMO with a generation count marked as
72    /// VMO_FROZEN. The resulting VMO is read-only.
73    ///
74    /// Failure
75    /// This function returns `None` for failure. That can happen for
76    /// a few reasons.
77    ///   1) It is a semantic error to freeze a VMO while an atomic transaction
78    ///      is in progress, because that transaction is supposed to be atomic.
79    ///   2) VMO errors. This can include running out of space or debug assertions.
80    ///
81    /// Note: the generation count for the original VMO is updated immediately. Since
82    /// the new VMO is page-by-page copy-on-write, at least the first page of the
83    /// VMO will immediately do a true copy. The practical implications of this
84    /// depend on implementation details like how large a VMO is versus page size.
85    pub fn frozen_vmo_copy(&self) -> Option<zx::Vmo> {
86        self.state()?.try_lock().ok().and_then(|mut state| state.frozen_vmo_copy().ok()).flatten()
87    }
88
89    /// Returns a VMO holding a copy of the data in this inspector.
90    ///
91    /// The copied VMO will be read-only.
92    pub fn copy_vmo(&self) -> Option<zx::Vmo> {
93        self.copy_vmo_data().and_then(|data| {
94            if let Ok(vmo) = zx::Vmo::create(data.len() as u64) {
95                vmo.write(&data, 0).ok().map(|_| vmo)
96            } else {
97                None
98            }
99        })
100    }
101
102    pub(crate) fn get_storage_handle(&self) -> Option<Arc<zx::Vmo>> {
103        // We can'just share a reference to the underlying vec<u8> storage, so we copy the data
104        self.storage.clone()
105    }
106
107    /// Returns Ok(()) if VMO is frozen, and the generation count if it is not.
108    /// Very unsafe. Propagates unrelated errors by panicking.
109    #[cfg(test)]
110    pub fn is_frozen(&self) -> Result<(), u64> {
111        use inspect_format::{BlockAccessorExt, Header};
112        let vmo = self.storage.as_ref().unwrap();
113        let mut buffer: [u8; 16] = [0; 16];
114        vmo.read(&mut buffer, 0).unwrap();
115        let block = buffer.block_at_unchecked::<Header>(inspect_format::BlockIndex::EMPTY);
116        if block.generation_count() == constants::VMO_FROZEN {
117            Ok(())
118        } else {
119            Err(block.generation_count())
120        }
121    }
122}
123
124#[cfg(not(target_os = "fuchsia"))]
125impl Inspector {
126    pub(crate) fn duplicate_vmo(&self) -> Option<<Container as BlockContainer>::Data> {
127        // We don't support getting a duplicate handle to the data on the host so we lock and copy
128        // the udnerlying data.
129        self.copy_vmo_data()
130    }
131
132    pub(crate) fn get_storage_handle(&self) -> Option<Vec<u8>> {
133        // We can'just share a reference to the underlying vec<u8> storage, so we copy the data
134        self.copy_vmo_data()
135    }
136}
137
138impl Default for Inspector {
139    fn default() -> Self {
140        Inspector::new(InspectorConfig::default())
141    }
142}
143
144impl Inspector {
145    /// Initializes a new Inspect VMO object with the
146    /// [`default maximum size`][constants::DEFAULT_VMO_SIZE_BYTES].
147    pub fn new(conf: InspectorConfig) -> Self {
148        conf.build()
149    }
150
151    /// Returns a copy of the bytes stored in the VMO for this inspector.
152    ///
153    /// The output will be truncated to only those bytes that are needed to accurately read the
154    /// stored data.
155    pub fn copy_vmo_data(&self) -> Option<Vec<u8>> {
156        self.root_node.inner.inner_ref().and_then(|inner_ref| inner_ref.state.copy_vmo_bytes())
157    }
158
159    pub fn max_size(&self) -> Option<usize> {
160        self.state()?.try_lock().ok().map(|state| state.stats().maximum_size)
161    }
162
163    /// True if the Inspector was created successfully (it's not No-Op)
164    pub fn is_valid(&self) -> bool {
165        // It is only necessary to check the root_node, because:
166        //   1) If the Inspector was created as a no-op, the root node is not valid.
167        //   2) If the creation of the Inspector failed, then the root_node is invalid. This
168        //      is because `Inspector::new_root` returns the VMO and root node as a pair.
169        self.root_node.is_valid()
170    }
171
172    /// Returns the root node of the inspect hierarchy.
173    pub fn root(&self) -> &Node {
174        &self.root_node
175    }
176
177    /// Takes a function to execute as under a single lock of the Inspect VMO. This function
178    /// receives a reference to the root of the inspect hierarchy.
179    pub fn atomic_update<F, R>(&self, update_fn: F) -> R
180    where
181        F: FnOnce(&Node) -> R,
182    {
183        self.root().atomic_update(update_fn)
184    }
185
186    pub(crate) fn state(&self) -> Option<State> {
187        self.root().inner.inner_ref().map(|inner_ref| inner_ref.state.clone())
188    }
189}
190
191/// Classic builder pattern object for constructing an `Inspector`.
192pub struct InspectorConfig {
193    is_no_op: bool,
194    size: usize,
195    storage: Option<Arc<<Container as BlockContainer>::ShareableData>>,
196}
197
198impl Default for InspectorConfig {
199    /// A default Inspector:
200    ///     * Fully functional
201    ///     * Size: `constants::DEFAULT_VMO_SIZE_BYTES`
202    ///
203    /// Because the default is so cheap to construct, there is
204    /// no "empty" `InspectorConfig`.
205    fn default() -> Self {
206        Self { is_no_op: false, size: constants::DEFAULT_VMO_SIZE_BYTES, storage: None }
207    }
208}
209
210impl InspectorConfig {
211    /// A read-only Inspector.
212    pub fn no_op(mut self) -> Self {
213        self.is_no_op = true;
214        self
215    }
216
217    /// Size of the VMO.
218    pub fn size(mut self, max_size: usize) -> Self {
219        self.size = max_size;
220        self
221    }
222
223    fn create_no_op(self) -> Inspector {
224        Inspector { storage: self.storage, root_node: Arc::new(Node::new_no_op()) }
225    }
226
227    fn adjusted_buffer_size(max_size: usize) -> usize {
228        let mut size = max(constants::MINIMUM_VMO_SIZE_BYTES, max_size);
229        // If the size is not a multiple of 4096, round up.
230        if size % constants::MINIMUM_VMO_SIZE_BYTES != 0 {
231            size =
232                (1 + size / constants::MINIMUM_VMO_SIZE_BYTES) * constants::MINIMUM_VMO_SIZE_BYTES;
233        }
234
235        size
236    }
237}
238
239#[cfg(target_os = "fuchsia")]
240impl InspectorConfig {
241    /// An Inspector with a readable VMO.
242    /// Implicitly no-op.
243    pub fn vmo(mut self, vmo: zx::Vmo) -> Self {
244        self.storage = Some(Arc::new(vmo));
245        self.no_op()
246    }
247
248    fn build(self) -> Inspector {
249        if self.is_no_op {
250            return self.create_no_op();
251        }
252
253        match Self::new_root(self.size) {
254            Ok((storage, root_node)) => {
255                Inspector { storage: Some(storage), root_node: Arc::new(root_node) }
256            }
257            Err(e) => {
258                error!("Failed to create root node. Error: {:?}", e);
259                self.create_no_op()
260            }
261        }
262    }
263
264    /// Allocates a new VMO and initializes it.
265    fn new_root(
266        max_size: usize,
267    ) -> Result<(Arc<<Container as BlockContainer>::ShareableData>, Node), Error> {
268        let size = Self::adjusted_buffer_size(max_size);
269        let (container, vmo) = Container::read_and_write(size).map_err(Error::AllocateVmo)?;
270        let name = zx::Name::new("InspectHeap").unwrap();
271        vmo.set_name(&name).map_err(Error::AllocateVmo)?;
272        let vmo = Arc::new(vmo);
273        let heap = Heap::new(container).map_err(|e| Error::CreateHeap(Box::new(e)))?;
274        let state =
275            State::create(heap, vmo.clone()).map_err(|e| Error::CreateState(Box::new(e)))?;
276        Ok((vmo, Node::new_root(state)))
277    }
278}
279
280#[cfg(not(target_os = "fuchsia"))]
281impl InspectorConfig {
282    fn build(self) -> Inspector {
283        if self.is_no_op {
284            return self.create_no_op();
285        }
286
287        match Self::new_root(self.size) {
288            Ok((root_node, storage)) => {
289                Inspector { storage: Some(storage), root_node: Arc::new(root_node) }
290            }
291            Err(e) => {
292                error!("Failed to create root node. Error: {:?}", e);
293                self.create_no_op()
294            }
295        }
296    }
297
298    fn new_root(
299        max_size: usize,
300    ) -> Result<(Node, Arc<<Container as BlockContainer>::ShareableData>), Error> {
301        let size = Self::adjusted_buffer_size(max_size);
302        let (container, storage) = Container::read_and_write(size).unwrap();
303        let heap = Heap::new(container).map_err(|e| Error::CreateHeap(Box::new(e)))?;
304        let state =
305            State::create(heap, Arc::new(storage)).map_err(|e| Error::CreateState(Box::new(e)))?;
306        Ok((Node::new_root(state), Arc::new(storage)))
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313    use crate::assert_update_is_atomic;
314
315    #[fuchsia::test]
316    fn inspector_new() {
317        let test_object = Inspector::default();
318        assert_eq!(test_object.max_size().unwrap(), constants::DEFAULT_VMO_SIZE_BYTES);
319    }
320
321    #[fuchsia::test]
322    fn inspector_copy_data() {
323        let test_object = Inspector::default();
324
325        assert_eq!(test_object.max_size().unwrap(), constants::DEFAULT_VMO_SIZE_BYTES);
326
327        // The copy will be a single page, since that is all that is used.
328        assert_eq!(test_object.copy_vmo_data().unwrap().len(), 4096);
329    }
330
331    #[fuchsia::test]
332    fn no_op() {
333        let inspector = Inspector::new(InspectorConfig::default().size(4096));
334        // Make the VMO full.
335        let nodes = (0..84)
336            .map(|i| inspector.root().create_child(format!("test-{i}")))
337            .collect::<Vec<Node>>();
338
339        assert!(nodes.iter().all(|node| node.is_valid()));
340        let no_op_node = inspector.root().create_child("no-op-child");
341        assert!(!no_op_node.is_valid());
342    }
343
344    #[fuchsia::test]
345    fn inspector_new_with_size() {
346        let test_object = Inspector::new(InspectorConfig::default().size(8192));
347        assert_eq!(test_object.max_size().unwrap(), 8192);
348
349        // If size is not a multiple of 4096, it'll be rounded up.
350        let test_object = Inspector::new(InspectorConfig::default().size(10000));
351        assert_eq!(test_object.max_size().unwrap(), 12288);
352
353        // If size is less than the minimum size, the minimum will be set.
354        let test_object = Inspector::new(InspectorConfig::default().size(2000));
355        assert_eq!(test_object.max_size().unwrap(), 4096);
356    }
357
358    #[fuchsia::test]
359    async fn atomic_update() {
360        let insp = Inspector::default();
361        assert_update_is_atomic!(insp, |n| {
362            n.record_int("", 1);
363            n.record_int("", 2);
364            n.record_uint("", 3);
365            n.record_string("", "abcd");
366        });
367    }
368}
369
370// These tests exercise Fuchsia-specific APIs for Inspector.
371#[cfg(all(test, target_os = "fuchsia"))]
372mod fuchsia_tests {
373    use super::*;
374
375    #[fuchsia::test]
376    fn inspector_duplicate_vmo() {
377        let test_object = Inspector::default();
378        assert_eq!(
379            test_object.storage.as_ref().unwrap().get_size().unwrap(),
380            constants::DEFAULT_VMO_SIZE_BYTES as u64
381        );
382        assert_eq!(
383            test_object.duplicate_vmo().unwrap().get_size().unwrap(),
384            constants::DEFAULT_VMO_SIZE_BYTES as u64
385        );
386    }
387
388    #[fuchsia::test]
389    fn inspector_new_root() {
390        // Note, the small size we request should be rounded up to a full 4kB page.
391        let (vmo, root_node) = InspectorConfig::new_root(100).unwrap();
392        assert_eq!(vmo.get_size().unwrap(), 4096);
393        let inner = root_node.inner.inner_ref().unwrap();
394        assert_eq!(*inner.block_index, 0);
395        assert_eq!("InspectHeap", vmo.get_name().expect("Has name"));
396    }
397
398    #[fuchsia::test]
399    fn freeze_vmo_works() {
400        let inspector = Inspector::default();
401        let initial =
402            inspector.state().unwrap().with_current_header(|header| header.generation_count());
403        let vmo = inspector.frozen_vmo_copy();
404
405        let is_frozen_result = inspector.is_frozen();
406        assert!(is_frozen_result.is_err());
407
408        assert_eq!(initial + 2, is_frozen_result.err().unwrap());
409        assert!(is_frozen_result.err().unwrap() % 2 == 0);
410
411        let frozen_insp = Inspector::new(InspectorConfig::default().no_op().vmo(vmo.unwrap()));
412        assert!(frozen_insp.is_frozen().is_ok());
413    }
414
415    #[fuchsia::test]
416    fn transactions_block_freezing() {
417        let inspector = Inspector::default();
418        inspector.atomic_update(|_| assert!(inspector.frozen_vmo_copy().is_none()));
419    }
420
421    #[fuchsia::test]
422    fn transactions_block_copying() {
423        let inspector = Inspector::default();
424        inspector.atomic_update(|_| assert!(inspector.copy_vmo().is_none()));
425        inspector.atomic_update(|_| assert!(inspector.copy_vmo_data().is_none()));
426    }
427
428    #[fuchsia::test]
429    fn inspector_new_with_size() {
430        let test_object = Inspector::new(InspectorConfig::default().size(8192));
431        assert_eq!(test_object.max_size().unwrap(), 8192);
432
433        assert_eq!(
434            "InspectHeap",
435            test_object.storage.as_ref().unwrap().get_name().expect("Has name")
436        );
437
438        // If size is not a multiple of 4096, it'll be rounded up.
439        let test_object = Inspector::new(InspectorConfig::default().size(10000));
440        assert_eq!(test_object.max_size().unwrap(), 12288);
441
442        // If size is less than the minimum size, the minimum will be set.
443        let test_object = Inspector::new(InspectorConfig::default().size(2000));
444        assert_eq!(test_object.max_size().unwrap(), 4096);
445    }
446}