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::{health, Inspector, InspectorConfig};
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    *INSPECTOR_SIZE.lock() = max_size;
85    &INSPECTOR
86}
87
88/// Returns a handle to the standardized singleton top-level health reporter on each call.
89///
90/// Calling this function installs a health reporting child node below the default inspector's
91/// `root` node.  When using it, consider using the default inspector for all health reporting, for
92/// uniformity: `fuchsia_inspect::component::inspector()`.
93///
94/// # Caveats
95///
96/// The health reporting node is created when it is first referenced.  It is advisable to reference
97/// it as early as possible, so that it could export a `STARTING_UP` health status while the
98/// component is initializing.
99pub fn health() -> Health {
100    Health { health_node: HEALTH.clone() }
101}
102
103/// Serves statistics about inspect such as size or number of dynamic children in the
104/// `fuchsia.inspect.Stats` lazy node.
105pub fn serve_inspect_stats() {
106    INSPECTOR.record_lazy_stats();
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use crate::health::Reporter;
113    use diagnostics_assertions::{assert_data_tree, assert_json_diff, AnyProperty};
114    use futures::FutureExt;
115
116    #[fuchsia::test]
117    fn health_checker_lifecycle() {
118        let inspector = super::inspector();
119        // In the beginning, the inspector has no stats.
120        assert_data_tree!(inspector, root: contains {});
121
122        let mut health = health();
123        assert_data_tree!(inspector,
124        root: contains {
125            "fuchsia.inspect.Health": {
126                status: "STARTING_UP",
127                start_timestamp_nanos: AnyProperty,
128            }
129        });
130
131        health.set_ok();
132        assert_data_tree!(inspector,
133        root: contains {
134            "fuchsia.inspect.Health": {
135                status: "OK",
136                start_timestamp_nanos: AnyProperty,
137            }
138        });
139
140        health.set_unhealthy("Bad state");
141        assert_data_tree!(inspector,
142        root: contains {
143            "fuchsia.inspect.Health": {
144                status: "UNHEALTHY",
145                message: "Bad state",
146                start_timestamp_nanos: AnyProperty,
147            }
148        });
149
150        // Verify that the message changes.
151        health.set_unhealthy("Another bad state");
152        assert_data_tree!(inspector,
153        root: contains {
154            "fuchsia.inspect.Health": {
155                status: "UNHEALTHY",
156                message: "Another bad state",
157                start_timestamp_nanos: AnyProperty,
158            }
159        });
160
161        // Also verifies that there is no more message.
162        health.set_ok();
163        assert_data_tree!(inspector,
164        root: contains {
165            "fuchsia.inspect.Health": {
166                status: "OK",
167                start_timestamp_nanos: AnyProperty,
168            }
169        });
170    }
171
172    #[fuchsia::test]
173    fn record_on_inspector() {
174        let inspector = super::inspector();
175        assert_eq!(inspector.max_size().unwrap(), constants::DEFAULT_VMO_SIZE_BYTES);
176        inspector.root().record_int("a", 1);
177        assert_data_tree!(inspector, root: contains {
178            a: 1i64,
179        })
180    }
181
182    #[fuchsia::test]
183    fn init_inspector_with_size() {
184        super::init_inspector_with_size(8192);
185        assert_eq!(super::inspector().max_size().unwrap(), 8192);
186    }
187
188    #[fuchsia::test]
189    fn inspect_stats() {
190        let inspector = super::inspector();
191        super::serve_inspect_stats();
192        inspector.root().record_lazy_child("foo", || {
193            async move {
194                let inspector = Inspector::default();
195                inspector.root().record_uint("a", 1);
196                Ok(inspector)
197            }
198            .boxed()
199        });
200        assert_json_diff!(inspector, root: {
201            foo: {
202                a: 1u64,
203            },
204            "fuchsia.inspect.Stats": {
205                current_size: 4096u64,
206                maximum_size: constants::DEFAULT_VMO_SIZE_BYTES as u64,
207                utilization_per_ten_k: 156u64,
208                total_dynamic_children: 2u64,
209                allocated_blocks: 7u64,
210                deallocated_blocks: 0u64,
211                failed_allocations: 0u64,
212            }
213        });
214    }
215}