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