fuchsia_inspect/
health.rs1use super::{InspectType, Property, StringProperty};
42use injectable_time::TimeSource;
43use std::fmt;
44
45#[cfg(not(target_os = "fuchsia"))]
46use injectable_time::UtcInstant as TimeType;
47
48#[cfg(target_os = "fuchsia")]
49use injectable_time::MonotonicInstant as TimeType;
50
51pub trait Reporter {
56 fn set_starting_up(&mut self);
58 fn set_ok(&mut self);
60 fn set_unhealthy(&mut self, message: &str);
63}
64
65const FUCHSIA_INSPECT_HEALTH: &str = "fuchsia.inspect.Health";
67
68const STATUS_PROPERTY_KEY: &str = "status";
69
70const MESSAGE_PROPERTY_KEY: &str = "message";
71
72#[derive(Debug, PartialEq, Eq)]
75enum Status {
76 StartingUp,
78 Unhealthy,
81 Ok,
83}
84
85impl fmt::Display for Status {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 match self {
88 Status::StartingUp => write!(f, "STARTING_UP"),
89 Status::Unhealthy => write!(f, "UNHEALTHY"),
90 Status::Ok => write!(f, "OK"),
91 }
92 }
93}
94
95#[derive(Debug)]
100pub struct Node {
101 node: super::Node,
103
104 status: StringProperty,
106
107 message: Option<StringProperty>,
109}
110
111impl InspectType for Node {
112 fn into_recorded(self) -> crate::writer::types::RecordedInspectType {
113 crate::writer::types::RecordedInspectType::Boxed(Box::new(self))
114 }
115}
116
117impl Reporter for Node {
118 fn set_starting_up(&mut self) {
120 self.set_status_enum(Status::StartingUp, None);
121 }
122
123 fn set_ok(&mut self) {
125 self.set_status_enum(Status::Ok, None);
126 }
127
128 fn set_unhealthy(&mut self, message: &str) {
131 self.set_status_enum(Status::Unhealthy, Some(message));
132 }
133}
134
135impl Node {
136 pub fn new(parent: &super::Node) -> Self {
139 Self::new_internal(parent, TimeType::new())
140 }
141
142 #[cfg(test)]
144 pub fn new_with_timestamp<T: TimeSource>(parent: &super::Node, timestamper: T) -> Self {
145 Self::new_internal(parent, timestamper)
146 }
147
148 fn new_internal<T: TimeSource>(parent: &super::Node, timestamper: T) -> Self {
149 let node = parent.create_child(FUCHSIA_INSPECT_HEALTH);
150 node.record_int("start_timestamp_nanos", timestamper.now());
151 let status = node.create_string(STATUS_PROPERTY_KEY, Status::StartingUp.to_string());
152 let message = None;
153 Node { node, status, message }
154 }
155
156 fn set_status_enum(&mut self, status: Status, message: Option<&str>) {
159 assert!(status != Status::Unhealthy || message.is_some(), "UNHEALTHY must have a message.");
160 self.set_status(&status.to_string(), message);
161 }
162
163 fn set_status(&mut self, status: &str, message: Option<&str>) {
167 self.status.set(status);
168 match (&self.message, message) {
169 (_, None) => self.message = None,
170 (Some(m), Some(n)) => m.set(n),
171 (None, Some(n)) => {
172 self.message = Some(self.node.create_string(MESSAGE_PROPERTY_KEY, n))
173 }
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::Inspector;
182 use diagnostics_assertions::assert_data_tree;
183 use injectable_time::FakeTime;
184
185 #[fuchsia::test]
186 async fn health_checker_lifecycle() {
187 let inspector = Inspector::default();
188 let root = inspector.root();
189 assert_data_tree!(inspector, root: contains {});
191
192 let fake_time = FakeTime::new();
193 fake_time.set_ticks(42);
194 let mut health = Node::new_with_timestamp(root, fake_time);
195 assert_data_tree!(inspector,
196 root: contains {
197 "fuchsia.inspect.Health": {
198 status: "STARTING_UP",
199 start_timestamp_nanos: 42i64,
200 }
201 });
202
203 health.set_ok();
204 assert_data_tree!(inspector,
205 root: contains {
206 "fuchsia.inspect.Health": {
207 status: "OK",
208 start_timestamp_nanos: 42i64,
209 }
210 });
211
212 health.set_unhealthy("Bad state");
213 assert_data_tree!(inspector,
214 root: contains {
215 "fuchsia.inspect.Health": {
216 status: "UNHEALTHY",
217 message: "Bad state",
218 start_timestamp_nanos: 42i64,
219 }
220 });
221
222 health.set_unhealthy("Another bad state");
224 assert_data_tree!(inspector,
225 root: contains {
226 "fuchsia.inspect.Health": {
227 status: "UNHEALTHY",
228 message: "Another bad state",
229 start_timestamp_nanos: 42i64,
230 }
231 });
232
233 health.set_ok();
235 assert_data_tree!(inspector,
236 root: contains {
237 "fuchsia.inspect.Health": {
238 status: "OK",
239 start_timestamp_nanos: 42i64,
240 }
241 });
242
243 health.set_starting_up();
245 assert_data_tree!(inspector,
246 root: contains {
247 "fuchsia.inspect.Health": {
248 status: "STARTING_UP",
249 start_timestamp_nanos: 42i64,
250 }
251 });
252 }
253
254 #[fuchsia::test]
255 async fn health_is_recordable() {
256 let inspector = Inspector::default();
257 let root = inspector.root();
258
259 let fake_time = FakeTime::new();
260 fake_time.set_ticks(42);
261 {
262 let mut health = Node::new_with_timestamp(root, fake_time);
263 health.set_ok();
264 assert_data_tree!(inspector,
265 root: contains {
266 "fuchsia.inspect.Health": {
267 status: "OK",
268 start_timestamp_nanos: 42i64,
269 }
270 });
271
272 root.record(health);
273 }
274
275 assert_data_tree!(inspector,
276 root: contains {
277 "fuchsia.inspect.Health": {
278 status: "OK",
279 start_timestamp_nanos: 42i64,
280 }
281 });
282 }
283}