1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! # Health-checking inspect node.
//!
//! Health installs a health checking inspect node. This node reports the current program's health
//! status in the form of an enumeration string and, in case of an unhealthy status, a free-form
//! message.
//!
//! Possible statuses are as follows:
//!
//! - `OK`, the Node is HEALTHY
//! - `STARTING_UP`, the node is not yet HEALTHY
//! - `UNHEALTHY`, the node is NOT HEALTHY (the program is required to provide a status message).
//! - any other value, the node is NOT HEALTHY.
//!
//! # Usage
//!
//! To use the health checker one must first obtain a `fuchsia_inspect::Node` to add the health
//! information into. Once that is available, use `fuchsia_inspect::health::Node::new(...)` to
//! add a standardized health checker.
//!
//! # Examples
//!
//! ```
//! use fuchsia_inspect as inspect;
//! use fuchsia_inspect::health;
//!
//! let inspector = /* the inspector of your choice */
//! let mut root = inspector.root(); // Or perhaps a different Inspect Node of your choice.
//! let mut health = health::Node::new(root);
//!
//! health.set_ok();
//! // ...
//! health.set_unhealthy("I am not feeling well."); // Report an error
//! // ...
//! health.set_ok(); // The component is healthy again.
//! ```
use super::{InspectType, Property, StringProperty};
use injectable_time::TimeSource;
use std::fmt;
#[cfg(not(target_os = "fuchsia"))]
use injectable_time::UtcInstant as TimeType;
#[cfg(target_os = "fuchsia")]
use injectable_time::MonotonicInstant as TimeType;
/// A trait of a standardized health checker.
///
/// Contains the methods to set standardized health status. All standardized health status reporters
/// must implement this trait.
pub trait Reporter {
/// Sets the health status to `STARTING_UP`.
fn set_starting_up(&mut self);
/// Sets the health status to `OK`.
fn set_ok(&mut self);
/// Sets the health status to `UNHEALTHY`. A `message` that explains why the node is healthy
/// MUST be given.
fn set_unhealthy(&mut self, message: &str);
}
// The metric node name, as exposed by the health checker.
const FUCHSIA_INSPECT_HEALTH: &str = "fuchsia.inspect.Health";
const STATUS_PROPERTY_KEY: &str = "status";
const MESSAGE_PROPERTY_KEY: &str = "message";
/// Predefined statuses, per the Inspect health specification. Note that the specification
/// also allows custom string statuses.
#[derive(Debug, PartialEq, Eq)]
enum Status {
/// The health checker is available, but has not been initialized with program status yet.
StartingUp,
/// The program reports unhealthy status. The program MUST provide a status message if reporting
/// unhealthy.
Unhealthy,
/// The program reports healthy operation. The definition of healthy is up to the program.
Ok,
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Status::StartingUp => write!(f, "STARTING_UP"),
Status::Unhealthy => write!(f, "UNHEALTHY"),
Status::Ok => write!(f, "OK"),
}
}
}
/// Contains subsystem health information. A global instance of Health is used implicitly
/// if the user calls the functions `init()`, `ok()` and `unhealthy(...)`.
///
/// Use as: ```fuchsia_inspect::health::Node``.
#[derive(Debug)]
pub struct Node {
// The generic inspect node that hosts the health metric.
node: super::Node,
// The health status of the property
status: StringProperty,
// The detailed status message, filled out in case the health status is not OK.
message: Option<StringProperty>,
}
impl InspectType for Node {}
impl Reporter for Node {
/// Sets the health status to `STARTING_UP`.
fn set_starting_up(&mut self) {
self.set_status_enum(Status::StartingUp, None);
}
/// Sets the health status to `OK`.
fn set_ok(&mut self) {
self.set_status_enum(Status::Ok, None);
}
/// Sets the health status to `UNHEALTHY`. A `message` that explains why the node is healthy
/// MUST be given.
fn set_unhealthy(&mut self, message: &str) {
self.set_status_enum(Status::Unhealthy, Some(message));
}
}
impl Node {
/// Creates a new health checking node as a child of `parent`. The initial observed state
/// is `STARTING_UP`, and remains so until the programs call one of `set_ok` or `set_unhealthy`.
pub fn new(parent: &super::Node) -> Self {
Self::new_internal(parent, TimeType::new())
}
// Creates a health node using a specified timestamp. Useful for tests.
#[cfg(test)]
pub fn new_with_timestamp<T: TimeSource>(parent: &super::Node, timestamper: T) -> Self {
Self::new_internal(parent, timestamper)
}
fn new_internal<T: TimeSource>(parent: &super::Node, timestamper: T) -> Self {
let node = parent.create_child(FUCHSIA_INSPECT_HEALTH);
node.record_int("start_timestamp_nanos", timestamper.now());
let status = node.create_string(STATUS_PROPERTY_KEY, Status::StartingUp.to_string());
let message = None;
Node { node, status, message }
}
// Sets the health status from the supplied `status` and `message`. Panics if setting invalid
// status, e.g. setting `UNHEALTHY` without a message.
fn set_status_enum(&mut self, status: Status, message: Option<&str>) {
assert!(status != Status::Unhealthy || message.is_some(), "UNHEALTHY must have a message.");
self.set_status(&status.to_string(), message);
}
// Sets an arbitrary status and an arbitrary (optional) message into the health report.
// Prefer setting standard status using one of the predefined API methods. This one will
// allow you to set whatever you want.
fn set_status(&mut self, status: &str, message: Option<&str>) {
self.status.set(status);
match (&self.message, message) {
(_, None) => self.message = None,
(Some(m), Some(n)) => m.set(n),
(None, Some(n)) => {
self.message = Some(self.node.create_string(MESSAGE_PROPERTY_KEY, n))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Inspector;
use diagnostics_assertions::assert_data_tree;
use injectable_time::FakeTime;
#[fuchsia::test]
fn health_checker_lifecycle() {
let inspector = Inspector::default();
let root = inspector.root();
// In the beginning, the inspector has no stats.
assert_data_tree!(inspector, root: contains {});
let fake_time = FakeTime::new();
fake_time.set_ticks(42);
let mut health = Node::new_with_timestamp(root, fake_time);
assert_data_tree!(inspector,
root: contains {
"fuchsia.inspect.Health": {
status: "STARTING_UP",
start_timestamp_nanos: 42i64,
}
});
health.set_ok();
assert_data_tree!(inspector,
root: contains {
"fuchsia.inspect.Health": {
status: "OK",
start_timestamp_nanos: 42i64,
}
});
health.set_unhealthy("Bad state");
assert_data_tree!(inspector,
root: contains {
"fuchsia.inspect.Health": {
status: "UNHEALTHY",
message: "Bad state",
start_timestamp_nanos: 42i64,
}
});
// Verify that the message changes.
health.set_unhealthy("Another bad state");
assert_data_tree!(inspector,
root: contains {
"fuchsia.inspect.Health": {
status: "UNHEALTHY",
message: "Another bad state",
start_timestamp_nanos: 42i64,
}
});
// Also verifies that there is no more message.
health.set_ok();
assert_data_tree!(inspector,
root: contains {
"fuchsia.inspect.Health": {
status: "OK",
start_timestamp_nanos: 42i64,
}
});
// Revert to STARTING_UP, but only for tests.
health.set_starting_up();
assert_data_tree!(inspector,
root: contains {
"fuchsia.inspect.Health": {
status: "STARTING_UP",
start_timestamp_nanos: 42i64,
}
});
}
#[fuchsia::test]
fn health_is_recordable() {
let inspector = Inspector::default();
let root = inspector.root();
let fake_time = FakeTime::new();
fake_time.set_ticks(42);
{
let mut health = Node::new_with_timestamp(root, fake_time);
health.set_ok();
assert_data_tree!(inspector,
root: contains {
"fuchsia.inspect.Health": {
status: "OK",
start_timestamp_nanos: 42i64,
}
});
root.record(health);
}
assert_data_tree!(inspector,
root: contains {
"fuchsia.inspect.Health": {
status: "OK",
start_timestamp_nanos: 42i64,
}
});
}
}