Skip to main content

fuchsia_inspect/
component.rs

1// Copyright 2020 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
5//! # Component inspection utilities
6//!
7//!
8//! This module contains standardized entry points to the Fuchsia inspect subsystem. It works based
9//! on the assumpton that a top-level static [`Inspector`][Inspector] is desirable.
10//!
11//! The [`inspector()`][inspector] function can be used to get a top level inspector, which ensures
12//! consistent inspect behavior across components.
13//!
14//! Use the [`health()`][health] function to report the component health state through the
15//! component inspector.
16//!
17//! While using the component inspector is not mandatory, it is probably a good idea from the
18//! standpoint of uniform reporting.
19//!
20//! # Examples
21//!
22//! ```rust
23//! use fuchsia_inspect::component;
24//! let inspector = component::inspector();
25//! // Add a standardized health node to the default inspector as early as possible in code.
26//! // The component will report `STARTING_UP` as the status from here on.
27//! let mut health = component::health();
28//!
29//! // Add a node with a metric to the inspector.
30//! inspector.root().create_string("property", "value");
31//!
32//! // Report the component health as `OK` when ready.  Calls to `health` are thread-safe.
33//! health.set_ok();
34//! ```
35
36use super::stats::InspectorExt;
37use super::{Inspector, InspectorConfig, health};
38use fuchsia_sync::Mutex;
39use inspect_format::constants;
40use std::sync::{Arc, LazyLock};
41
42// The size with which the default inspector is initialized.
43static INSPECTOR_SIZE: Mutex<usize> = Mutex::new(constants::DEFAULT_VMO_SIZE_BYTES);
44
45// The component-level inspector.  We probably want to use this inspector across components where
46// practical.
47static INSPECTOR: LazyLock<Inspector> =
48    LazyLock::new(|| Inspector::new(InspectorConfig::default().size(*INSPECTOR_SIZE.lock())));
49
50// Health node based on the global inspector from `inspector()`.
51static HEALTH: LazyLock<Arc<Mutex<health::Node>>> =
52    LazyLock::new(|| Arc::new(Mutex::new(health::Node::new(INSPECTOR.root()))));
53
54/// A thread-safe handle to a health reporter.  See `component::health()` for instructions on how
55/// to create one.
56pub struct Health {
57    // The thread-safe component health reporter that reports to the top-level inspector.
58    health_node: Arc<Mutex<health::Node>>,
59}
60
61// A thread-safe implementation of a global health reporter.
62impl health::Reporter for Health {
63    fn set_starting_up(&mut self) {
64        self.health_node.lock().set_starting_up();
65    }
66    fn set_ok(&mut self) {
67        self.health_node.lock().set_ok();
68    }
69    fn set_unhealthy(&mut self, message: &str) {
70        self.health_node.lock().set_unhealthy(message);
71    }
72}
73
74/// Returns the singleton component inspector.
75///
76/// It is recommended that all health nodes register with this inspector (as opposed to any other
77/// that may have been created).
78pub fn inspector() -> &'static Inspector {
79    &INSPECTOR
80}
81
82/// Initializes and returns the singleton component inspector.
83pub fn init_inspector_with_size(max_size: usize) -> &'static Inspector {
84    if LazyLock::get(&INSPECTOR).is_some() {
85        log::warn!(
86            "init_inspector_with_size called after inspector singleton was already initialized"
87        );
88    }
89    *INSPECTOR_SIZE.lock() = max_size;
90    &INSPECTOR
91}
92
93/// Returns a handle to the standardized singleton top-level health reporter on each call.
94///
95/// Calling this function installs a health reporting child node below the default inspector's
96/// `root` node.  When using it, consider using the default inspector for all health reporting, for
97/// uniformity: `fuchsia_inspect::component::inspector()`.
98///
99/// # Caveats
100///
101/// The health reporting node is created when it is first referenced.  It is advisable to reference
102/// it as early as possible, so that it could export a `STARTING_UP` health status while the
103/// component is initializing.
104pub fn health() -> Health {
105    Health { health_node: HEALTH.clone() }
106}
107
108/// Serves statistics about inspect such as size or number of dynamic children in the
109/// `fuchsia.inspect.Stats` lazy node.
110pub fn serve_inspect_stats() {
111    INSPECTOR.record_lazy_stats();
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use crate::health::Reporter;
118    use diagnostics_assertions::{AnyProperty, assert_data_tree, assert_json_diff};
119    use futures::FutureExt;
120
121    #[fuchsia::test]
122    async fn health_checker_lifecycle() {
123        let inspector = super::inspector();
124        // In the beginning, the inspector has no stats.
125        assert_data_tree!(inspector, root: contains {});
126
127        let mut health = health();
128        assert_data_tree!(inspector,
129        root: contains {
130            "fuchsia.inspect.Health": {
131                status: "STARTING_UP",
132                start_timestamp_nanos: AnyProperty,
133            }
134        });
135
136        health.set_ok();
137        assert_data_tree!(inspector,
138        root: contains {
139            "fuchsia.inspect.Health": {
140                status: "OK",
141                start_timestamp_nanos: AnyProperty,
142            }
143        });
144
145        health.set_unhealthy("Bad state");
146        assert_data_tree!(inspector,
147        root: contains {
148            "fuchsia.inspect.Health": {
149                status: "UNHEALTHY",
150                message: "Bad state",
151                start_timestamp_nanos: AnyProperty,
152            }
153        });
154
155        // Verify that the message changes.
156        health.set_unhealthy("Another bad state");
157        assert_data_tree!(inspector,
158        root: contains {
159            "fuchsia.inspect.Health": {
160                status: "UNHEALTHY",
161                message: "Another bad state",
162                start_timestamp_nanos: AnyProperty,
163            }
164        });
165
166        // Also verifies that there is no more message.
167        health.set_ok();
168        assert_data_tree!(inspector,
169        root: contains {
170            "fuchsia.inspect.Health": {
171                status: "OK",
172                start_timestamp_nanos: AnyProperty,
173            }
174        });
175    }
176
177    #[fuchsia::test]
178    async fn record_on_inspector() {
179        let inspector = super::inspector();
180        assert_eq!(inspector.max_size().unwrap(), constants::DEFAULT_VMO_SIZE_BYTES);
181        inspector.root().record_int("a", 1);
182        assert_data_tree!(inspector, root: contains {
183            a: 1i64,
184        })
185    }
186
187    #[fuchsia::test]
188    fn init_inspector_with_size() {
189        super::init_inspector_with_size(8192);
190        assert_eq!(super::inspector().max_size().unwrap(), 8192);
191    }
192
193    #[fuchsia::test]
194    async fn inspect_stats() {
195        let inspector = super::inspector();
196        super::serve_inspect_stats();
197        inspector.root().record_lazy_child("foo", || {
198            async move {
199                let inspector = Inspector::default();
200                inspector.root().record_uint("a", 1);
201                Ok(inspector)
202            }
203            .boxed()
204        });
205        assert_json_diff!(inspector, root: {
206            foo: {
207                a: 1u64,
208            },
209            "fuchsia.inspect.Stats": {
210                current_size: 4096u64,
211                maximum_size: constants::DEFAULT_VMO_SIZE_BYTES as u64,
212                utilization_per_ten_k: 156u64,
213                total_dynamic_children: 2u64,
214                allocated_blocks: 7u64,
215                deallocated_blocks: 0u64,
216                failed_allocations: 0u64,
217                peak_bytes_requested: 240u64,
218            }
219        });
220    }
221}