diagnostics/
escrow.rs

1// Copyright 2024 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 std::collections::HashMap;
6use std::sync::{Arc, Weak};
7
8use async_trait::async_trait;
9use errors::ModelError;
10use fuchsia_inspect::{IntExponentialHistogramProperty, IntLinearHistogramProperty};
11use inspect::HistogramProperty;
12use moniker::Moniker;
13use {fuchsia_inspect as inspect, fuchsia_sync as fsync};
14
15use hooks::{Event, EventPayload, EventType, HasEventType, Hook, HooksRegistration};
16
17const STARTED_DURATIONS: &str = "started_durations";
18const STOPPED_DURATIONS: &str = "stopped_durations";
19const HISTOGRAM: &str = "histogram";
20
21/// [-inf, 4, 7, 10, 16, 28, 52, 100, 196, 388, 772, 1540, 3076, 6148, inf]
22const STARTED_DURATIONS_HISTOGRAM_PARAMS: inspect::ExponentialHistogramParams<i64> =
23    inspect::ExponentialHistogramParams {
24        floor: 4,
25        initial_step: 3,
26        step_multiplier: 2,
27        buckets: 12,
28    };
29
30/// [-inf, 10, 20, 30, 40, ..., 240, 250, inf]
31const STOPPED_DURATIONS_HISTOGRAM_PARAMS: inspect::LinearHistogramParams<i64> =
32    inspect::LinearHistogramParams { floor: 10, step_size: 10, buckets: 24 };
33
34type StopTime = zx::MonotonicInstant;
35
36/// [`DurationStats`] tracks:
37///
38/// - durations an escrowing component was executing (`started_durations/histogram/MONIKER`)
39/// - durations an escrowing component stayed stopped in-between two executions
40///   (`stopped_durations/histogram/MONIKER`)
41///
42/// The tracking begins the first time a component sends an escrow request. Subsequently,
43/// started/stopped durations will be tracked regardless if that component keeps sending
44/// escrow requests.
45///
46/// The duration is measured in ticks in the Zircon monotonic clock, hence does
47/// not account into times the system is suspended.
48pub struct DurationStats {
49    // Keeps the inspect node alive.
50    _node: inspect::Node,
51    started_durations: ComponentHistograms<IntExponentialHistogramProperty>,
52    stopped_durations: ComponentHistograms<IntLinearHistogramProperty>,
53    // The set of components that have sent an escrow request at least once,
54    // and their last stop time.
55    escrowing_components: fsync::Mutex<HashMap<Moniker, StopTime>>,
56}
57
58impl DurationStats {
59    /// Creates a new duration tracker. Data will be written to the given inspect node.
60    pub fn new(node: inspect::Node) -> Self {
61        let started = node.create_child(STARTED_DURATIONS);
62        let histogram = started.create_child(HISTOGRAM);
63        node.record(started);
64        let started_durations = ComponentHistograms {
65            node: histogram,
66            properties: Default::default(),
67            init: |node, name| {
68                node.create_int_exponential_histogram(name, STARTED_DURATIONS_HISTOGRAM_PARAMS)
69            },
70        };
71
72        let stopped = node.create_child(STOPPED_DURATIONS);
73        let histogram = stopped.create_child(HISTOGRAM);
74        node.record(stopped);
75        let stopped_durations = ComponentHistograms {
76            node: histogram,
77            properties: Default::default(),
78            init: |node, name| {
79                node.create_int_linear_histogram(name, STOPPED_DURATIONS_HISTOGRAM_PARAMS)
80            },
81        };
82
83        Self {
84            _node: node,
85            started_durations,
86            stopped_durations,
87            escrowing_components: Default::default(),
88        }
89    }
90
91    /// Provides the hook events that are needed to work.
92    pub fn hooks(self: &Arc<Self>) -> Vec<HooksRegistration> {
93        vec![HooksRegistration::new(
94            "DurationStats",
95            vec![EventType::Started, EventType::Stopped],
96            Arc::downgrade(self) as Weak<dyn Hook>,
97        )]
98    }
99
100    fn on_component_started(self: &Arc<Self>, moniker: &Moniker, start_time: zx::MonotonicInstant) {
101        if let Some(stop_time) = self.escrowing_components.lock().get(moniker) {
102            let duration = start_time - *stop_time;
103            self.stopped_durations.record(moniker, duration.into_seconds());
104        }
105    }
106
107    fn on_component_stopped(
108        self: &Arc<Self>,
109        moniker: &Moniker,
110        stop_time: zx::MonotonicInstant,
111        execution_duration: zx::MonotonicDuration,
112        requested_escrow: bool,
113    ) {
114        let mut escrowing_components = self.escrowing_components.lock();
115        if requested_escrow {
116            escrowing_components.insert(moniker.clone(), stop_time);
117        }
118        if !escrowing_components.contains_key(moniker) {
119            return;
120        }
121        self.started_durations.record(moniker, execution_duration.into_seconds());
122    }
123}
124
125/// Maintains a histogram under each moniker where there is data.
126///
127/// The histogram will be a child property created under `node`, and will be named using
128/// the component's moniker.
129struct ComponentHistograms<H: HistogramProperty<Type = i64>> {
130    node: inspect::Node,
131    properties: fsync::Mutex<HashMap<Moniker, H>>,
132    init: fn(&inspect::Node, String) -> H,
133}
134
135impl<H: HistogramProperty<Type = i64>> ComponentHistograms<H> {
136    fn record(&self, moniker: &Moniker, value: i64) {
137        let mut properties = self.properties.lock();
138        let histogram = properties
139            .entry(moniker.clone())
140            .or_insert_with(|| (self.init)(&self.node, moniker.to_string()));
141        histogram.insert(value);
142    }
143}
144
145#[async_trait]
146impl Hook for DurationStats {
147    async fn on(self: Arc<Self>, event: &Event) -> Result<(), ModelError> {
148        let target_moniker = event
149            .target_moniker
150            .unwrap_instance_moniker_or(ModelError::UnexpectedComponentManagerMoniker)?;
151        match event.event_type() {
152            EventType::Started => {
153                if let EventPayload::Started { runtime, .. } = &event.payload {
154                    self.on_component_started(target_moniker, runtime.start_time_monotonic);
155                }
156            }
157            EventType::Stopped => {
158                if let EventPayload::Stopped {
159                    stop_time_monotonic,
160                    execution_duration,
161                    requested_escrow,
162                    ..
163                } = &event.payload
164                {
165                    self.on_component_stopped(
166                        target_moniker,
167                        *stop_time_monotonic,
168                        *execution_duration,
169                        *requested_escrow,
170                    );
171                }
172            }
173            _ => {}
174        }
175        Ok(())
176    }
177}