Skip to main content

fuchsia_inspect/writer/types/
base.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::{Error, Node, State};
6use derivative::Derivative;
7use inspect_format::BlockIndex;
8use private::InspectTypeInternal;
9use std::borrow::Cow;
10use std::fmt::Debug;
11use std::sync::{Arc, Weak};
12
13/// Trait implemented by all inspect types.
14pub trait InspectType: Send + Sync + Debug {
15    fn into_recorded(self) -> crate::writer::types::RecordedInspectType
16    where
17        Self: Sized + 'static;
18}
19
20pub(crate) mod private {
21    use crate::writer::State;
22    use inspect_format::BlockIndex;
23
24    /// Trait implemented by all inspect types. It provides functions that are not
25    /// intended for use outside the crate.
26    /// Use `impl_inspect_type_internal` for easy implementation.
27    pub trait InspectTypeInternal {
28        fn is_valid(&self) -> bool;
29        fn block_index(&self) -> Option<BlockIndex>;
30        fn state(&self) -> Option<State>;
31        fn atomic_access<R, F: FnOnce(&Self) -> R>(&self, accessor: F) -> R;
32    }
33}
34
35/// Trait allowing a `Node` to adopt any Inspect type as its child, removing
36/// it from the original parent's tree.
37///
38/// This trait is not implementable by external types.
39pub trait InspectTypeReparentable: private::InspectTypeInternal {
40    #[doc(hidden)]
41    /// This function is called by a child with the new parent as an argument.
42    /// The child will be removed from its current parent and added to the tree
43    /// under new_parent.
44    fn reparent(&self, new_parent: &Node) -> Result<(), Error> {
45        if let (
46            Some(child_state),
47            Some(child_index),
48            Some(new_parent_state),
49            Some(new_parent_index),
50        ) = (self.state(), self.block_index(), new_parent.state(), new_parent.block_index())
51        {
52            if new_parent_state != child_state {
53                return Err(Error::AdoptionIntoWrongVmo);
54            }
55
56            new_parent_state
57                .try_lock()
58                .and_then(|mut state| state.reparent(child_index, new_parent_index))?;
59        }
60
61        Ok(())
62    }
63}
64
65impl<T: private::InspectTypeInternal> InspectTypeReparentable for T {}
66
67/// Trait allowing an Inspect type to be renamed.
68///
69/// This trait is not implementable by external types.
70pub trait InspectTypeRenameable: private::InspectTypeInternal {
71    /// Rename this inspect node or property.
72    fn rename<'a>(&self, name: impl Into<Cow<'a, str>>) -> Result<(), Error>;
73}
74
75/// Macro to generate private::InspectTypeInternal
76macro_rules! impl_inspect_type_internal {
77    ($type_name:ident) => {
78        impl $type_name {
79            pub(crate) fn new(
80                state: $crate::writer::State,
81                block_index: inspect_format::BlockIndex,
82            ) -> $type_name {
83                $type_name { inner: $crate::writer::types::base::Inner::new(state, block_index) }
84            }
85
86            pub(crate) fn new_no_op() -> $type_name {
87                $type_name { inner: $crate::writer::types::base::Inner::None }
88            }
89
90            /// Rename this inspect node or property.
91            pub fn rename<'a>(
92                &self,
93                name: impl Into<std::borrow::Cow<'a, str>>,
94            ) -> Result<(), $crate::writer::Error> {
95                <Self as $crate::writer::InspectTypeRenameable>::rename(self, name)
96            }
97        }
98
99        impl $crate::writer::InspectTypeRenameable for $type_name {
100            fn rename<'a>(
101                &self,
102                name: impl Into<std::borrow::Cow<'a, str>>,
103            ) -> Result<(), $crate::writer::Error> {
104                self.inner.rename(name)
105            }
106        }
107
108        impl $crate::private::InspectTypeInternal for $type_name {
109            fn is_valid(&self) -> bool {
110                self.inner.is_valid()
111            }
112
113            fn state(&self) -> Option<$crate::writer::State> {
114                Some(self.inner.inner_ref()?.state.clone())
115            }
116
117            fn block_index(&self) -> Option<inspect_format::BlockIndex> {
118                if let Some(ref inner_ref) = self.inner.inner_ref() {
119                    Some(inner_ref.block_index)
120                } else {
121                    None
122                }
123            }
124
125            fn atomic_access<R, F: FnOnce(&Self) -> R>(&self, accessor: F) -> R {
126                match self.inner.inner_ref() {
127                    None => {
128                        // If the node was a no-op we still execute the `accessor` even if all
129                        // operations inside it will be no-ops to return `R`.
130                        accessor(&self)
131                    }
132                    Some(inner_ref) => {
133                        // Silently ignore the error when fail to lock (as in any regular operation).
134                        // All operations performed in the `accessor` won't update the vmo
135                        // generation count since we'll be holding one lock here.
136                        inner_ref.state.begin_transaction();
137                        let result = accessor(&self);
138                        inner_ref.state.end_transaction();
139                        result
140                    }
141                }
142            }
143        }
144    };
145}
146
147pub(crate) use impl_inspect_type_internal;
148
149macro_rules! impl_inspect_type_internal_histogram {
150    ($type_name:ident) => {
151        impl $type_name {
152            /// Rename this inspect histogram property.
153            pub fn rename<'a>(
154                &self,
155                name: impl Into<std::borrow::Cow<'a, str>>,
156            ) -> Result<(), $crate::writer::Error> {
157                <Self as $crate::writer::InspectTypeRenameable>::rename(self, name)
158            }
159        }
160
161        impl $crate::writer::InspectTypeRenameable for $type_name {
162            fn rename<'a>(
163                &self,
164                name: impl Into<std::borrow::Cow<'a, str>>,
165            ) -> Result<(), $crate::writer::Error> {
166                self.array.rename(name)
167            }
168        }
169
170        impl $crate::private::InspectTypeInternal for $type_name {
171            fn is_valid(&self) -> bool {
172                self.array.is_valid()
173            }
174
175            fn state(&self) -> Option<$crate::writer::State> {
176                self.array.state()
177            }
178
179            fn block_index(&self) -> Option<inspect_format::BlockIndex> {
180                self.array.block_index()
181            }
182
183            fn atomic_access<R, F: FnOnce(&Self) -> R>(&self, accessor: F) -> R {
184                self.array.atomic_access(|_| accessor(self))
185            }
186        }
187    };
188}
189
190pub(crate) use impl_inspect_type_internal_histogram;
191
192/// An inner type of all inspect nodes and properties. Each variant implies a
193/// different relationship with the underlying inspect VMO.
194#[derive(Debug, Derivative)]
195#[derivative(Default)]
196pub(crate) enum Inner<T: InnerType> {
197    /// The node or property is not attached to the inspect VMO.
198    #[derivative(Default)]
199    None,
200
201    /// The node or property is attached to the inspect VMO, iff its strong
202    /// reference is still alive.
203    Weak(Weak<InnerRef<T>>),
204
205    /// The node or property is attached to the inspect VMO.
206    Strong(Arc<InnerRef<T>>),
207}
208
209impl<T: InnerType> Inner<T> {
210    /// Creates a new Inner with the desired block index within the inspect VMO
211    pub(crate) fn new(state: State, block_index: BlockIndex) -> Self {
212        Self::Strong(Arc::new(InnerRef { state, block_index, data: T::Data::default() }))
213    }
214
215    pub(crate) fn rename<'a>(&self, name: impl Into<Cow<'a, str>>) -> Result<(), Error> {
216        if let Some(inner_ref) = self.inner_ref() {
217            let mut state = inner_ref.state.try_lock()?;
218            if inner_ref.data.is_valid() {
219                state.set_name(inner_ref.block_index, name)?;
220            }
221        }
222        Ok(())
223    }
224
225    /// Returns true if the number of strong references to this node or property
226    /// is greater than 0.
227    pub(crate) fn is_valid(&self) -> bool {
228        match self {
229            Self::None => false,
230            Self::Weak(weak_ref) => match weak_ref.upgrade() {
231                None => false,
232                Some(inner_ref) => inner_ref.data.is_valid(),
233            },
234            Self::Strong(inner_ref) => inner_ref.data.is_valid(),
235        }
236    }
237
238    /// Returns a `Some(Arc<InnerRef>)` iff the node or property is currently
239    /// attached to inspect, or `None` otherwise. Weak pointers are upgraded
240    /// if possible, but their lifetime as strong references are expected to be
241    /// short.
242    pub(crate) fn inner_ref(&self) -> Option<Arc<InnerRef<T>>> {
243        match self {
244            Self::None => None,
245            Self::Weak(weak_ref) => {
246                if let Some(inner_ref) = weak_ref.upgrade()
247                    && inner_ref.data.is_valid()
248                {
249                    return Some(inner_ref);
250                }
251                None
252            }
253            Self::Strong(inner_ref) => {
254                if inner_ref.data.is_valid() {
255                    Some(Arc::clone(inner_ref))
256                } else {
257                    None
258                }
259            }
260        }
261    }
262
263    /// Make a weak reference.
264    pub(crate) fn clone_weak(&self) -> Self {
265        match self {
266            Self::None => Self::None,
267            Self::Weak(weak_ref) => Self::Weak(weak_ref.clone()),
268            Self::Strong(inner_ref) => {
269                if inner_ref.data.is_valid() {
270                    Self::Weak(Arc::downgrade(inner_ref))
271                } else {
272                    Self::None
273                }
274            }
275        }
276    }
277}
278
279/// Inspect API types implement Eq,PartialEq returning true all the time so that
280/// structs embedding inspect types can derive these traits as well.
281/// IMPORTANT: Do not rely on these traits implementations for real comparisons
282/// or validation tests, instead leverage the reader.
283impl<T: InnerType> PartialEq for Inner<T> {
284    fn eq(&self, _other: &Self) -> bool {
285        true
286    }
287}
288
289impl<T: InnerType> Eq for Inner<T> {}
290
291/// A type that is owned by inspect nodes and properties, sharing ownership of
292/// the inspect VMO heap, and with numerical pointers to the location in the
293/// heap in which it resides.
294#[derive(Debug)]
295pub(crate) struct InnerRef<T: InnerType> {
296    /// Index of the block in the VMO.
297    pub(crate) block_index: BlockIndex,
298
299    /// Reference to the VMO heap.
300    pub(crate) state: State,
301
302    /// Associated data for this type.
303    pub(crate) data: T::Data,
304}
305
306impl<T: InnerType> Drop for InnerRef<T> {
307    /// InnerRef has a manual drop impl, to guarantee a single deallocation in
308    /// the case of multiple strong references.
309    fn drop(&mut self) {
310        if let Err(e) = T::free(&self.state, &self.data, self.block_index) {
311            log::error!("Failed to free InnerRef: {:?}", e);
312        }
313    }
314}
315
316/// De-allocation behavior and associated data for an inner type.
317pub(crate) trait InnerType {
318    /// Associated data stored on the InnerRef
319    type Data: Default + Debug + InnerData;
320
321    /// De-allocation behavior for when the InnerRef gets dropped
322    fn free(state: &State, data: &Self::Data, block_index: BlockIndex) -> Result<(), Error>;
323}
324
325pub(crate) trait InnerData {
326    fn is_valid(&self) -> bool;
327}
328
329impl InnerData for () {
330    fn is_valid(&self) -> bool {
331        true
332    }
333}
334
335#[derive(Default, Debug)]
336pub(crate) struct InnerValueType;
337
338impl InnerType for InnerValueType {
339    type Data = ();
340    fn free(state: &State, _: &Self::Data, block_index: BlockIndex) -> Result<(), Error> {
341        let mut state_lock = state.try_lock()?;
342        state_lock.free_value(block_index).map_err(|err| Error::free("value", block_index, err))
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use crate::Inspector;
350    use diagnostics_assertions::assert_data_tree;
351
352    #[fuchsia::test]
353    async fn test_reparent_from_state() {
354        let insp = Inspector::default();
355        let root = insp.root();
356        let a = root.create_child("a");
357        let b = a.create_child("b");
358
359        assert_data_tree!(insp, root: {
360            a: {
361                b: {},
362            },
363        });
364
365        b.reparent(root).unwrap();
366
367        assert_data_tree!(insp, root: {
368            b: {},
369            a: {},
370        });
371    }
372
373    #[fuchsia::test]
374    fn reparent_from_wrong_state() {
375        let insp1 = Inspector::default();
376        let insp2 = Inspector::default();
377
378        assert!(insp1.root().reparent(insp2.root()).is_err());
379
380        let a = insp1.root().create_child("a");
381        let b = insp2.root().create_child("b");
382
383        assert!(a.reparent(&b).is_err());
384        assert!(b.reparent(&a).is_err());
385    }
386
387    #[fuchsia::test]
388    async fn test_rename_nodes_and_properties() {
389        use crate::writer::{ArrayProperty, HistogramProperty};
390        use diagnostics_hierarchy::{LinearHistogram, LinearHistogramParams};
391        use futures::FutureExt;
392
393        let insp = Inspector::default();
394        let root = insp.root();
395        let node = root.create_child("node");
396        let int_prop = node.create_int("int_prop", 42);
397        let str_prop = node.create_string("str_prop", "hello");
398        let array_prop = node.create_int_array("array_prop", 2);
399        array_prop.set(0, 1);
400        array_prop.set(1, 2);
401        let hist_prop = node.create_int_linear_histogram(
402            "hist_prop",
403            LinearHistogramParams { floor: 0, step_size: 10, buckets: 2 },
404        );
405        hist_prop.insert(5);
406        let lazy_node = node.create_lazy_child("lazy_node", || {
407            async move {
408                let lazy_insp = Inspector::default();
409                lazy_insp.root().record_int("val", 99);
410                Ok(lazy_insp)
411            }
412            .boxed()
413        });
414
415        assert_data_tree!(insp, root: {
416            node: {
417                int_prop: 42i64,
418                str_prop: "hello",
419                array_prop: vec![1i64, 2i64],
420                hist_prop: LinearHistogram {
421                    floor: 0i64,
422                    step: 10,
423                    counts: vec![1],
424                    indexes: Some(vec![1]),
425                    size: 4,
426                },
427                lazy_node: {
428                    val: 99i64,
429                },
430            },
431        });
432
433        node.rename("node_renamed").unwrap();
434        int_prop.rename("int_renamed").unwrap();
435        str_prop.rename("str_renamed").unwrap();
436        array_prop.rename("array_renamed").unwrap();
437        hist_prop.rename("hist_renamed").unwrap();
438        lazy_node.rename("lazy_renamed").unwrap();
439
440        assert_data_tree!(insp, root: {
441            node_renamed: {
442                int_renamed: 42i64,
443                str_renamed: "hello",
444                array_renamed: vec![1i64, 2i64],
445                hist_renamed: LinearHistogram {
446                    floor: 0i64,
447                    step: 10,
448                    counts: vec![1],
449                    indexes: Some(vec![1]),
450                    size: 4,
451                },
452                lazy_renamed: {
453                    val: 99i64,
454                },
455            },
456        });
457    }
458
459    #[fuchsia::test]
460    async fn test_rename_root_and_noop_and_weak() {
461        let insp = Inspector::default();
462        assert_eq!(insp.root().rename("new_root"), Err(Error::RenameRoot));
463
464        let noop_node = Node::default();
465        assert_eq!(noop_node.rename("ignored"), Ok(()));
466
467        let node = insp.root().create_child("node");
468        let _child = node.create_child("child");
469        let weak = node.clone_weak();
470        weak.rename("weak_renamed").unwrap();
471        assert_data_tree!(insp, root: {
472            weak_renamed: {
473                child: {},
474            },
475        });
476
477        node.forget();
478        assert_eq!(node.rename("after_forget"), Ok(()));
479        assert_eq!(weak.rename("after_forget"), Ok(()));
480    }
481
482    #[fuchsia::test]
483    fn test_rename_string_reference_lifecycle() {
484        let insp = Inspector::default();
485        let state = insp.state().unwrap();
486
487        let node = insp.root().create_child("unique_old_name");
488        let stats_before = state.try_lock().unwrap().stats();
489
490        // Renaming to the same name should be a no-op with no block allocations/deallocations.
491        node.rename("unique_old_name").unwrap();
492        let stats_same = state.try_lock().unwrap().stats();
493        assert_eq!(stats_before.allocated_blocks, stats_same.allocated_blocks);
494        assert_eq!(stats_before.deallocated_blocks, stats_same.deallocated_blocks);
495
496        // Renaming to a new unique name should allocate 1 new string ref block and deallocate the
497        // old 1.
498        node.rename("unique_new_name").unwrap();
499        let stats_after = state.try_lock().unwrap().stats();
500        assert_eq!(stats_after.allocated_blocks, stats_before.allocated_blocks + 1);
501        assert_eq!(stats_after.deallocated_blocks, stats_before.deallocated_blocks + 1);
502    }
503}