1use 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 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 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 fn clone_with_child(&self, name: &str) -> Self;
59}
60
61pub struct TimeMatrixClient {
62 node: InspectNode,
63}
64
65impl TimeMatrixClient {
66 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 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
178fn 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, 3, 0, 0, 0, 10, 0, 0, 0, 1, 0, 16, 0, 10, 0, 1, 0, 0, 0, 1, 0x0f, 15, 0, 0, 0, 0, 0, 1, 0, 7, 0, 60, 0, 0, 0, 0, 0, 0, ]
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}