Skip to main content

windowed_stats/experimental/
inspect.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 derivative::Derivative;
6use fuchsia_inspect::{Inspector, Node as InspectNode};
7use fuchsia_sync::Mutex;
8use futures::FutureExt as _;
9use log::warn;
10use std::sync::Arc;
11
12use crate::experimental::clock::{Timed, Timestamp};
13use crate::experimental::series::interpolation::InterpolationKind;
14use crate::experimental::series::statistic::{FoldError, Metadata, SerialStatistic};
15use crate::experimental::series::{SerializedBuffer, TimeMatrix, TimeMatrixFold, TimeMatrixTick};
16
17pub trait InspectSender {
18    /// Sends a [`TimeMatrix`] to the client's inspection server.
19    ///
20    /// See [`inspect_time_matrix_with_metadata`].
21    ///
22    /// [`inspect_time_matrix_with_metadata`]: crate::experimental::serve::TimeMatrixClient::inspect_time_matrix_with_metadata
23    /// [`TimeMatrix`]: crate::experimental::series::TimeMatrix
24    fn inspect_time_matrix<F, P>(
25        &self,
26        name: impl Into<String>,
27        matrix: TimeMatrix<F, P>,
28    ) -> InspectedTimeMatrix<F::Sample>
29    where
30        TimeMatrix<F, P>: 'static + TimeMatrixFold<F::Sample> + Send,
31        Metadata<F>: 'static + Send + Sync,
32        F: SerialStatistic<P>,
33        F::Sample: Send,
34        P: InterpolationKind;
35
36    /// Sends a [`TimeMatrix`] to the client's inspection server.
37    ///
38    /// This function lazily records the given [`TimeMatrix`] to Inspect. The server end
39    /// periodically interpolates the matrix and records data as needed. The returned
40    /// [handle][`InspectedTimeMatrix`] can be used to fold samples into the matrix.
41    ///
42    /// [`InspectedTimeMatrix`]: crate::experimental::serve::InspectedTimeMatrix
43    /// [`TimeMatrix`]: crate::experimental::series::TimeMatrix
44    fn inspect_time_matrix_with_metadata<F, P>(
45        &self,
46        name: impl Into<String>,
47        matrix: TimeMatrix<F, P>,
48        metadata: impl Into<Metadata<F>>,
49    ) -> InspectedTimeMatrix<F::Sample>
50    where
51        TimeMatrix<F, P>: 'static + TimeMatrixFold<F::Sample> + Send,
52        Metadata<F>: 'static + Send + Sync,
53        F: SerialStatistic<P>,
54        F::Sample: Send,
55        P: InterpolationKind;
56
57    /// Clones the client and scopes it to a child node with the given name.
58    fn clone_with_child(&self, name: &str) -> Self;
59}
60
61pub struct TimeMatrixClient {
62    node: InspectNode,
63}
64
65impl TimeMatrixClient {
66    /// Create a new TimeMatrixClient that holds a given Inspect Node
67    ///
68    /// Note: If TimeMatrixClient is constructed with a weak reference to Inspect
69    /// Node, then the original Node needs to be preserved for time series
70    /// data shows up in Inspect. If TimeMatrixClient is constructed with the original
71    /// Inspect node, then the TimeMatrixClient itself needs to be preserved.
72    pub fn new(node: InspectNode) -> Self {
73        Self { node }
74    }
75
76    fn inspect_and_record_with<F, P, R>(
77        &self,
78        name: impl Into<String>,
79        matrix: TimeMatrix<F, P>,
80        record: R,
81    ) -> InspectedTimeMatrix<F::Sample>
82    where
83        TimeMatrix<F, P>: 'static + TimeMatrixFold<F::Sample> + Send,
84        Metadata<F>: 'static + Send + Sync,
85        F: SerialStatistic<P>,
86        F::Sample: Send,
87        P: InterpolationKind,
88        R: 'static + Clone + Fn(&InspectNode) + Send + Sync,
89    {
90        let name = name.into();
91        let matrix = Arc::new(Mutex::new(matrix));
92        self::record_lazy_time_matrix_with(&self.node, &name, matrix.clone(), record);
93        InspectedTimeMatrix::new(name, matrix)
94    }
95}
96
97impl Clone for TimeMatrixClient {
98    fn clone(&self) -> Self {
99        TimeMatrixClient { node: self.node.clone_weak() }
100    }
101}
102
103impl InspectSender for TimeMatrixClient {
104    fn inspect_time_matrix<F, P>(
105        &self,
106        name: impl Into<String>,
107        matrix: TimeMatrix<F, P>,
108    ) -> InspectedTimeMatrix<F::Sample>
109    where
110        TimeMatrix<F, P>: 'static + TimeMatrixFold<F::Sample> + Send,
111        Metadata<F>: 'static + Send + Sync,
112        F: SerialStatistic<P>,
113        F::Sample: Send,
114        P: InterpolationKind,
115    {
116        self.inspect_and_record_with(name, matrix, |_node| {})
117    }
118
119    fn inspect_time_matrix_with_metadata<F, P>(
120        &self,
121        name: impl Into<String>,
122        matrix: TimeMatrix<F, P>,
123        metadata: impl Into<Metadata<F>>,
124    ) -> InspectedTimeMatrix<F::Sample>
125    where
126        TimeMatrix<F, P>: 'static + TimeMatrixFold<F::Sample> + Send,
127        Metadata<F>: 'static + Send + Sync,
128        F: SerialStatistic<P>,
129        F::Sample: Send,
130        P: InterpolationKind,
131    {
132        let metadata = Arc::new(metadata.into());
133        self.inspect_and_record_with(name, matrix, move |node| {
134            use crate::experimental::series::metadata::Metadata;
135            metadata.record_with_parent(node);
136        })
137    }
138
139    fn clone_with_child(&self, name: &str) -> Self {
140        Self { node: self.node.create_child(name) }
141    }
142}
143
144#[derive(Derivative)]
145#[derivative(Debug, Clone)]
146pub struct InspectedTimeMatrix<T> {
147    name: String,
148    #[derivative(Debug = "ignore")]
149    matrix: Arc<Mutex<dyn TimeMatrixFold<T> + Send>>,
150}
151
152impl<T> InspectedTimeMatrix<T> {
153    pub(crate) fn new(
154        name: impl Into<String>,
155        matrix: Arc<Mutex<dyn TimeMatrixFold<T> + Send>>,
156    ) -> Self {
157        Self { name: name.into(), matrix }
158    }
159
160    /// Folding a sample to the time series.
161    ///
162    /// Note: the timestamp for the time series is generated only after acquiring a lock.
163    /// If the lock is already being held by another thread/process that reads the Inspect
164    /// data, the timestamp generated may fall on the time interval after.
165    ///
166    /// TODO(https://fxbug.dev/457421826) - Fix issue with delayed timestamp
167    pub fn fold(&self, sample: T) -> Result<(), FoldError> {
168        self.matrix.lock().fold(Timed::now(sample))
169    }
170
171    pub fn fold_or_log_error(&self, sample: T) {
172        if let Err(error) = self.matrix.lock().fold(Timed::now(sample)) {
173            warn!("failed to fold sample into time matrix \"{}\": {:?}", self.name, error);
174        }
175    }
176}
177
178/// Records a lazy child node in the given node that records buffers and metadata for the given
179/// time matrix.
180///
181/// The function `f` is passed a node to record arbitrary data after the data semantic and buffers
182/// of the time matrix have been recorded using that same node.
183fn record_lazy_time_matrix_with<F>(
184    node: &InspectNode,
185    name: impl Into<String>,
186    matrix: Arc<Mutex<dyn TimeMatrixTick + Send>>,
187    f: F,
188) where
189    F: 'static + Clone + Fn(&InspectNode) + Send + Sync,
190{
191    let name = name.into();
192    node.record_lazy_child(name, move || {
193        let matrix = matrix.clone();
194        let f = f.clone();
195        async move {
196            let inspector = Inspector::default();
197            let result = matrix.lock().tick_and_get_buffers(Timestamp::now());
198            inspector.root().atomic_update(|node| {
199                if result.is_ok() {
200                    f(node);
201                }
202                SerializedBuffer::write_to_inspect_or_error(result, node);
203            });
204            Ok(inspector)
205        }
206        .boxed()
207    });
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213    use diagnostics_assertions::{AnyBytesProperty, assert_data_tree};
214    use fuchsia_async as fasync;
215
216    use crate::experimental::series::SamplingProfile;
217    use crate::experimental::series::interpolation::{ConstantSample, LastSample};
218    use crate::experimental::series::metadata::BitsetMap;
219    use crate::experimental::series::statistic::{Max, Union};
220
221    #[fuchsia::test]
222    fn inspected_time_matrix_folded_sample_appears_in_inspect() {
223        let mut exec = fasync::TestExecutor::new_with_fake_time();
224        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(3_000_000_000));
225
226        let inspector = Inspector::default();
227        let client = TimeMatrixClient::new(inspector.root().create_child("serve_test_node"));
228        let time_matrix = TimeMatrix::<Max<u64>, ConstantSample>::new(
229            SamplingProfile::highly_granular(),
230            ConstantSample::default(),
231        );
232        let inspected_matrix = client.inspect_time_matrix("time_series_1", time_matrix);
233
234        inspected_matrix.fold(15).unwrap();
235        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(10_000_000_000));
236
237        assert_data_tree!(@executor exec, inspector, root: contains {
238            serve_test_node: {
239                time_series_1: {
240                    "type": "gauge",
241                    "data": vec![
242                        1u8, // version number
243                        3, 0, 0, 0, // created timestamp
244                        10, 0, 0, 0, // last timestamp
245                        1, 0, // type: simple8b RLE; subtype: unsigned
246                        16, 0, // series 1: length in bytes
247                        10, 0, // series 1 granularity: 10s
248                        1, 0, // number of selector elements and value blocks
249                        0, 0,    // head selector index
250                        1,    // number of values in last block
251                        0x0f, // RLE selector
252                        15, 0, 0, 0, 0, 0, 1, 0, // value 15 appears 1 time
253                        7, 0, // series 2: length in bytes
254                        60, 0, // series 2 granularity: 60s
255                        0, 0, // number of selector elements and value blocks
256                        0, 0, // head selector index
257                        0, // number of values in last block
258                    ]
259                }
260            }
261        });
262    }
263
264    #[fuchsia::test]
265    async fn inspect_time_matrix_then_inspect_data_tree_contains_buffers() {
266        let inspector = Inspector::default();
267        let client = TimeMatrixClient::new(inspector.root().create_child("serve_test_node"));
268        let _matrix = client
269            .inspect_time_matrix("connectivity", TimeMatrix::<Union<u64>, LastSample>::default());
270
271        assert_data_tree!(inspector, root: contains {
272            serve_test_node: {
273                connectivity: {
274                    "type": "bitset",
275                    "data": AnyBytesProperty,
276                }
277            }
278        });
279    }
280
281    #[fuchsia::test]
282    async fn inspect_time_matrix_with_metadata_then_inspect_data_tree_contains_metadata() {
283        let inspector = Inspector::default();
284        let client = TimeMatrixClient::new(inspector.root().create_child("serve_test_node"));
285        let _matrix = client.inspect_time_matrix_with_metadata(
286            "engine",
287            TimeMatrix::<Union<u64>, LastSample>::default(),
288            BitsetMap::from_ordered(["check", "oil", "battery", "coolant"]),
289        );
290
291        assert_data_tree!(inspector, root: contains {
292            serve_test_node: {
293                engine: {
294                    "type": "bitset",
295                    "data": AnyBytesProperty,
296                    metadata: {
297                        index: {
298                            "0": "check",
299                            "1": "oil",
300                            "2": "battery",
301                            "3": "coolant",
302                        }
303                    }
304                }
305            }
306        });
307    }
308
309    #[fuchsia::test]
310    async fn inspected_time_matrix_clone_with_child_properly_scoped() {
311        let inspector = Inspector::default();
312        let client = TimeMatrixClient::new(inspector.root().create_child("serve_test_node"));
313        let child_client = client.clone_with_child("child");
314        let _matrix = child_client
315            .inspect_time_matrix("connectivity", TimeMatrix::<Union<u64>, LastSample>::default());
316
317        assert_data_tree!(inspector, root: contains {
318            serve_test_node: {
319                child: {
320                    connectivity: {
321                        "type": "bitset",
322                        "data": AnyBytesProperty,
323                    }
324                }
325            }
326        });
327    }
328}