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}