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}