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