Skip to main content

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