1use async_trait::async_trait;
6use errors::ModelError;
7use fuchsia_inspect as inspect;
8use fuchsia_inspect_contrib as inspect_contrib;
9use fuchsia_sync::Mutex;
10use hooks::{Event, EventPayload, EventType, HasEventType, Hook, HooksRegistration, StartReason};
11use moniker::Moniker;
12use std::sync::{Arc, Weak};
13
14const MAX_NUMBER_OF_LIFECYCLE_EVENTS: usize = 150;
15const MONIKER: &str = "moniker";
16const TYPE: &str = "type";
17const STARTED: &str = "started";
18const STOPPED: &str = "stopped";
19const START_REASON: &str = "start_reason";
20const TIME: &str = "time";
21const EARLY: &str = "early";
22const LATE: &str = "late";
23
24pub struct ComponentLifecycleTimeStats {
26 _node: inspect::Node,
28 inner: Mutex<Inner>,
29}
30
31struct Inner {
38 early: inspect_contrib::nodes::BoundedListNode,
39 late: inspect_contrib::nodes::BoundedListNode,
40}
41
42impl Inner {
43 fn new(early: inspect::Node, late: inspect::Node) -> Self {
44 let early =
45 inspect_contrib::nodes::BoundedListNode::new(early, MAX_NUMBER_OF_LIFECYCLE_EVENTS);
46 let late =
47 inspect_contrib::nodes::BoundedListNode::new(late, MAX_NUMBER_OF_LIFECYCLE_EVENTS);
48 Self { early, late }
49 }
50
51 fn add_entry(
52 &mut self,
53 moniker: &Moniker,
54 kind: &str,
55 time: zx::BootInstant,
56 start_reason: Option<&StartReason>,
57 ) {
58 let node =
59 if self.early.len() < self.early.capacity() { &mut self.early } else { &mut self.late };
60 node.add_entry(|node| {
61 node.record_string(MONIKER, moniker.to_string());
62 node.record_string(TYPE, kind);
63 node.record_int(TIME, time.into_nanos());
64 if let Some(start_reason) = start_reason {
65 node.record_string(START_REASON, start_reason.as_str());
66 }
67 });
68 }
69}
70
71impl ComponentLifecycleTimeStats {
72 pub fn new(node: inspect::Node) -> Self {
74 let early = node.create_child(EARLY);
75 let late = node.create_child(LATE);
76 Self { _node: node, inner: Mutex::new(Inner::new(early, late)) }
77 }
78
79 pub fn hooks(self: &Arc<Self>) -> Vec<HooksRegistration> {
81 vec![HooksRegistration::new(
82 "ComponentLifecycleTimeStats",
83 vec![EventType::Started, EventType::Stopped],
84 Arc::downgrade(self) as Weak<dyn Hook>,
85 )]
86 }
87
88 fn on_component_started(
89 self: &Arc<Self>,
90 moniker: &Moniker,
91 start_time: zx::BootInstant,
92 start_reason: &StartReason,
93 ) {
94 self.inner.lock().add_entry(moniker, STARTED, start_time, Some(start_reason));
95 }
96
97 fn on_component_stopped(self: &Arc<Self>, moniker: &Moniker, stop_time: zx::BootInstant) {
98 self.inner.lock().add_entry(moniker, STOPPED, stop_time, None);
99 }
100}
101
102#[async_trait]
103impl Hook for ComponentLifecycleTimeStats {
104 async fn on(self: Arc<Self>, event: &Event) -> Result<(), ModelError> {
105 let target_moniker = event
106 .target_moniker
107 .unwrap_instance_moniker_or(ModelError::UnexpectedComponentManagerMoniker)?;
108 match event.event_type() {
109 EventType::Started => {
110 if let EventPayload::Started { runtime, .. } = &event.payload {
111 self.on_component_started(
112 target_moniker,
113 runtime.start_time,
114 &runtime.start_reason,
115 );
116 }
117 }
118 EventType::Stopped => {
119 if let EventPayload::Stopped { stop_time, .. } = &event.payload {
120 self.on_component_stopped(target_moniker, *stop_time);
121 }
122 }
123 _ => {}
124 }
125 Ok(())
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use diagnostics_assertions::assert_data_tree;
133 use fuchsia_inspect::DiagnosticsHierarchyGetter;
134 use itertools::Itertools;
135 use moniker::ChildName;
136
137 #[fuchsia::test]
138 async fn early_doesnt_track_more_than_limit() {
139 let inspector = inspect::Inspector::default();
140 let stats =
141 Arc::new(ComponentLifecycleTimeStats::new(inspector.root().create_child("lifecycle")));
142
143 for i in 0..2 * MAX_NUMBER_OF_LIFECYCLE_EVENTS {
144 stats.on_component_started(
145 &Moniker::new(&[ChildName::parse(format!("{}", i)).unwrap()]),
146 zx::BootInstant::from_nanos(i as i64),
147 &StartReason::Root,
148 );
149 }
150
151 let hierarchy = inspector.get_diagnostics_hierarchy().await;
152 let node = &hierarchy.children[0];
153 let early = node.children.iter().find_or_first(|c| c.name == "early").unwrap();
154 assert_eq!(early.children.len(), MAX_NUMBER_OF_LIFECYCLE_EVENTS);
155 assert_eq!(
156 early.children.iter().map(|c| c.name.parse::<i32>().unwrap()).sorted().last().unwrap(),
157 149
158 );
159 }
160
161 #[fuchsia::test]
162 async fn early_overflow_to_late() {
163 let inspector = inspect::Inspector::default();
164 let stats =
165 Arc::new(ComponentLifecycleTimeStats::new(inspector.root().create_child("lifecycle")));
166
167 for i in 0..MAX_NUMBER_OF_LIFECYCLE_EVENTS + 1 {
168 stats.on_component_started(
169 &Moniker::new(&[ChildName::parse(format!("{}", i)).unwrap()]),
170 zx::BootInstant::from_nanos(i as i64),
171 &StartReason::Root,
172 );
173 }
174
175 let hierarchy = inspector.get_diagnostics_hierarchy().await;
176 let node = &hierarchy.children[0];
177 let early = node.children.iter().find_or_first(|c| c.name == "early").unwrap();
178 let late = node.children.iter().find_or_first(|c| c.name == "late").unwrap();
179 assert_eq!(early.children.len(), MAX_NUMBER_OF_LIFECYCLE_EVENTS);
180 assert_eq!(
181 early.children.iter().map(|c| c.name.parse::<i32>().unwrap()).sorted().last().unwrap(),
182 149
183 );
184 assert_eq!(late.children.len(), 1);
185 assert_data_tree!(late, late: {
186 "0": contains {
187 moniker: "150",
188 "type": "started",
189 start_reason: "Instance is the root",
190 }
191 });
192 }
193
194 #[fuchsia::test]
195 async fn late_doesnt_track_more_than_limit() {
196 let inspector = inspect::Inspector::default();
197 let stats =
198 Arc::new(ComponentLifecycleTimeStats::new(inspector.root().create_child("lifecycle")));
199
200 for i in 0..4 * MAX_NUMBER_OF_LIFECYCLE_EVENTS {
201 stats.on_component_started(
202 &Moniker::new(&[ChildName::parse(format!("{}", i)).unwrap()]),
203 zx::BootInstant::from_nanos(i as i64),
204 &StartReason::Root,
205 );
206 }
207
208 let hierarchy = inspector.get_diagnostics_hierarchy().await;
209 let node = &hierarchy.children[0];
210 let early = node.children.iter().find_or_first(|c| c.name == "early").unwrap();
211 let late = node.children.iter().find_or_first(|c| c.name == "late").unwrap();
212 assert_eq!(early.children.len(), MAX_NUMBER_OF_LIFECYCLE_EVENTS);
213 assert_eq!(late.children.len(), MAX_NUMBER_OF_LIFECYCLE_EVENTS);
214 assert_eq!(
215 late.children.iter().map(|c| c.name.parse::<i32>().unwrap()).sorted().last().unwrap(),
216 449
217 );
218 }
219}