Skip to main content

diagnostics/
lifecycle.rs

1// Copyright 2022 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
5use 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
24/// Tracks start and stop timestamps of components.
25pub struct ComponentLifecycleTimeStats {
26    // Keeps the inspect node alive.
27    _node: inspect::Node,
28    inner: Mutex<Inner>,
29}
30
31/// `early` maintains the first `MAX_NUMBER_OF_LIFECYCLE_EVENTS` start/stop events of
32/// components. After more than `MAX_NUMBER_OF_LIFECYCLE_EVENTS` events have occurred,
33/// `early` will stay unchanged, and `late` will maintain the the last
34/// `MAX_NUMBER_OF_LIFECYCLE_EVENTS` start/stop events of components. When more events are
35/// added, the earliest ones in `late` will be discarded. This enables our feedback
36/// snapshots to contain a recent history of started and stopped components.
37struct 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    /// Creates a new startup time tracker. Data will be written to the given inspect node.
73    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    /// Provides the hook events that are needed to work.
80    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}