fuchsia_bluetooth/
inspect.rs

1// Copyright 2019 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
5#[cfg(target_os = "fuchsia")]
6use fuchsia_async as fasync;
7#[cfg(target_os = "fuchsia")]
8use fuchsia_inspect::{self as inspect, Node, NumericProperty, Property};
9#[cfg(target_os = "fuchsia")]
10use fuchsia_inspect_contrib::nodes::NodeTimeExt;
11#[cfg(target_os = "fuchsia")]
12use fuchsia_inspect_derive::Inspect;
13use std::fmt;
14
15const FALSE_VALUE: u64 = 0;
16const TRUE_VALUE: u64 = 1;
17
18/// Convert a type to the correct supported Inspect Property type. This is used in Bluetooth to
19/// ensure consistent representations of values in Inspect.
20///
21/// Note: It represents them appropriately for Bluetooth but may not be the appropriate type
22/// for other use cases. It shouldn't be used outside of the Bluetooth project.
23pub trait ToProperty {
24    type PropertyType;
25    fn to_property(&self) -> Self::PropertyType;
26}
27
28impl ToProperty for bool {
29    type PropertyType = u64;
30    fn to_property(&self) -> Self::PropertyType {
31        if *self { TRUE_VALUE } else { FALSE_VALUE }
32    }
33}
34
35impl ToProperty for Option<bool> {
36    type PropertyType = u64;
37    fn to_property(&self) -> Self::PropertyType {
38        self.as_ref().map(bool::to_property).unwrap_or(FALSE_VALUE)
39    }
40}
41
42impl ToProperty for String {
43    type PropertyType = String;
44    fn to_property(&self) -> Self::PropertyType {
45        self.to_string()
46    }
47}
48
49impl<T, V> ToProperty for Vec<T>
50where
51    T: ToProperty<PropertyType = V>,
52    V: ToString,
53{
54    type PropertyType = String;
55    fn to_property(&self) -> Self::PropertyType {
56        self.iter()
57            .map(|t| <T as ToProperty>::to_property(t).to_string())
58            .collect::<Vec<String>>()
59            .join(", ")
60    }
61}
62
63/// Vectors of T show up as a comma separated list string property. `None` types are
64/// represented as an empty string.
65impl<T, V> ToProperty for Option<Vec<T>>
66where
67    T: ToProperty<PropertyType = V>,
68    V: ToString,
69{
70    type PropertyType = String;
71    fn to_property(&self) -> Self::PropertyType {
72        self.as_ref().map(ToProperty::to_property).unwrap_or_else(String::new)
73    }
74}
75
76/// Convenience function to create a string containing the debug representation of an object that
77/// implements `Debug`
78pub trait DebugExt {
79    fn debug(&self) -> String;
80}
81
82impl<T: fmt::Debug> DebugExt for T {
83    fn debug(&self) -> String {
84        format!("{:?}", self)
85    }
86}
87
88/// Represents inspect data that is tied to a specific object. This inspect data and the object of
89/// type T should always be bundled together.
90#[cfg(target_os = "fuchsia")]
91pub trait InspectData<T> {
92    fn new(object: &T, inspect: inspect::Node) -> Self;
93}
94
95#[cfg(target_os = "fuchsia")]
96pub trait IsInspectable
97where
98    Self: Sized + Send + Sync + 'static,
99{
100    type I: InspectData<Self>;
101}
102
103/// A wrapper around a type T that bundles some inspect data alongside instances of the type.
104#[derive(Debug)]
105#[cfg(target_os = "fuchsia")]
106pub struct Inspectable<T: IsInspectable> {
107    pub(crate) inner: T,
108    pub(crate) inspect: T::I,
109}
110
111#[cfg(target_os = "fuchsia")]
112impl<T: IsInspectable> Inspectable<T> {
113    /// Create a new instance of an `Inspectable` wrapper type containing the T instance that
114    /// it wraps along with populated inspect data.
115    pub fn new(object: T, inspect: inspect::Node) -> Inspectable<T> {
116        Inspectable { inspect: T::I::new(&object, inspect), inner: object }
117    }
118}
119
120/// `Inspectable`s can always safely be immutably dereferenced as the type T that they wrap
121/// because the data will not be mutated through this reference.
122#[cfg(target_os = "fuchsia")]
123impl<T: IsInspectable> std::ops::Deref for Inspectable<T> {
124    type Target = T;
125    fn deref(&self) -> &Self::Target {
126        &self.inner
127    }
128}
129
130/// A trait representing the inspect data for a type T that will never be mutated. This trait
131/// allows for a simpler "fire and forget" representation of the inspect data associated with an
132/// object. This is because inspect handles for the data will never need to be accessed after
133/// creation.
134#[cfg(target_os = "fuchsia")]
135pub trait ImmutableDataInspect<T> {
136    fn new(data: &T, manager: Node) -> Self;
137}
138
139/// "Fire and forget" representation of some inspect data that does not allow access inspect
140/// handles after they are created.
141#[cfg(target_os = "fuchsia")]
142pub struct ImmutableDataInspectManager {
143    pub(crate) _manager: Node,
144}
145
146#[cfg(target_os = "fuchsia")]
147impl<T, I: ImmutableDataInspect<T>> InspectData<T> for I {
148    /// Create a new instance of some type `I` that represents the immutable inspect data for a type
149    /// `T`. This is done by handing `I` a `Node` and calling into the
150    /// monomorphized version of ImmutableDataInspect<T> for I.
151    fn new(data: &T, inspect: inspect::Node) -> I {
152        I::new(data, inspect)
153    }
154}
155
156/// The values associated with a data transfer.
157#[cfg(target_os = "fuchsia")]
158struct DataTransferStats {
159    /// The time at which the data transfer was recorded.
160    time: fasync::MonotonicInstant,
161    /// The elapsed amount of time (nanos) the data transfer took place over.
162    elapsed: std::num::NonZeroU64,
163    /// The bytes transferred.
164    bytes: usize,
165}
166
167#[cfg(target_os = "fuchsia")]
168impl DataTransferStats {
169    /// Calculates and returns the throughput of the `bytes` received in the
170    /// data transfer.
171    fn calculate_throughput(&self) -> u64 {
172        // NOTE: probably a better way to calculate the speed than using floats.
173        let bytes_per_nano = self.bytes as f64 / self.elapsed.get() as f64;
174        let bytes_per_second =
175            zx::MonotonicDuration::from_seconds(1).into_nanos() as f64 * bytes_per_nano;
176        bytes_per_second as u64
177    }
178}
179
180/// An inspect node that represents a stream of data, recording the total amount of data
181/// transferred and an instantaneous rate.
182#[cfg(target_os = "fuchsia")]
183#[derive(Inspect, Default)]
184pub struct DataStreamInspect {
185    /// The total number of bytes transferred in this stream
186    total_bytes: inspect::UintProperty,
187    /// Bytes per second, based on the most recent update.
188    bytes_per_second_current: inspect::UintProperty,
189    /// Time that this stream started.
190    /// Managed manually.
191    #[inspect(skip)]
192    start_time_prop: Option<fuchsia_inspect_contrib::nodes::MonotonicTimeProperty>,
193    /// Time that we were last started.  Used to calculate seconds running.
194    #[inspect(skip)]
195    started: Option<fasync::MonotonicInstant>,
196    /// Seconds that this stream has been active, measured from the start time to the last
197    /// recorded transfer.
198    streaming_secs: inspect::UintProperty,
199    /// Used to calculate instantaneous bytes_per_second.
200    #[inspect(skip)]
201    last_update: Option<DataTransferStats>,
202    inspect_node: inspect::Node,
203}
204
205#[cfg(target_os = "fuchsia")]
206impl DataStreamInspect {
207    pub fn start(&mut self) {
208        let now = fasync::MonotonicInstant::now();
209        if let Some(prop) = &self.start_time_prop {
210            prop.set_at(now.into());
211        } else {
212            self.start_time_prop = Some(self.inspect_node.create_time_at("start_time", now.into()));
213        }
214        self.started = Some(now);
215        self.last_update = Some(DataTransferStats {
216            time: now,
217            elapsed: std::num::NonZeroU64::new(1).unwrap(), // Default smallest interval of 1 nano
218            bytes: 0,
219        });
220    }
221
222    /// Record that `bytes` have been transferred as of `at`.
223    /// This is recorded since the `last_update` time or since `start` if it
224    /// has never been called.
225    /// Does nothing if this stream has never been started or if the provided `at` time
226    /// is in the past relative to the `last_update` time.
227    pub fn record_transferred(&mut self, bytes: usize, at: fasync::MonotonicInstant) {
228        let (elapsed, current_bytes) = match self.last_update {
229            Some(DataTransferStats { time: last, .. }) if at > last => {
230                // A new data transfer - calculate the new elapsed time interval.
231                let elapsed = (at - last).into_nanos() as u64;
232                (std::num::NonZeroU64::new(elapsed).unwrap(), bytes)
233            }
234            Some(DataTransferStats { time: last, elapsed, bytes: last_bytes }) if at == last => {
235                // An addition to the previous data transfer - use the previous elapsed time
236                // interval and an updated byte count.
237                (elapsed, last_bytes + bytes)
238            }
239            _ => return, // Otherwise, we haven't started or received an invalid `at` time.
240        };
241
242        let transfer = DataTransferStats { time: at, elapsed, bytes: current_bytes };
243        let _ = self.total_bytes.add(bytes as u64);
244        self.bytes_per_second_current.set(transfer.calculate_throughput());
245        self.last_update = Some(transfer);
246        if let Some(started) = &self.started {
247            let secs: u64 = (at - *started).into_seconds().try_into().unwrap_or(0);
248            self.streaming_secs.set(secs);
249        }
250    }
251}
252
253#[cfg(test)]
254#[cfg(target_os = "fuchsia")]
255mod tests {
256    use super::*;
257    use diagnostics_assertions::assert_data_tree;
258    use fuchsia_async::DurationExt;
259    use fuchsia_inspect_derive::WithInspect;
260
261    #[test]
262    fn bool_to_property() {
263        let b = false.to_property();
264        assert_eq!(b, FALSE_VALUE);
265        let b = true.to_property();
266        assert_eq!(b, TRUE_VALUE);
267    }
268
269    #[test]
270    fn optional_bool_to_property() {
271        let b: u64 = None::<bool>.to_property();
272        assert_eq!(b, FALSE_VALUE);
273        let b = Some(false).to_property();
274        assert_eq!(b, FALSE_VALUE);
275        let b = Some(true).to_property();
276        assert_eq!(b, TRUE_VALUE);
277    }
278
279    #[test]
280    fn string_vec_to_property() {
281        let s = Vec::<String>::new().to_property();
282        assert_eq!(s, "");
283        let s = vec!["foo".to_string()].to_property();
284        assert_eq!(s, "foo");
285        let s = vec!["foo".to_string(), "bar".to_string(), "baz".to_string()].to_property();
286        assert_eq!(s, "foo, bar, baz");
287    }
288
289    #[test]
290    fn optional_string_vec_to_property() {
291        let s = Some(vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]).to_property();
292        assert_eq!(s, "foo, bar, baz");
293    }
294
295    #[test]
296    fn debug_string() {
297        #[derive(Debug)]
298        struct Foo {
299            // TODO(https://fxbug.dev/42165549)
300            #[allow(unused)]
301            bar: u8,
302            // TODO(https://fxbug.dev/42165549)
303            #[allow(unused)]
304            baz: &'static str,
305        }
306        let foo = Foo { bar: 1, baz: "baz value" };
307        assert_eq!(format!("{:?}", foo), foo.debug());
308    }
309
310    /// Sets up an inspect test with an executor at timestamp `curr_time`.
311    fn setup_inspect(
312        curr_time: i64,
313    ) -> (fasync::TestExecutor, fuchsia_inspect::Inspector, DataStreamInspect) {
314        let exec = fasync::TestExecutor::new_with_fake_time();
315        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(curr_time));
316        let inspector = fuchsia_inspect::Inspector::default();
317        let d = DataStreamInspect::default()
318            .with_inspect(inspector.root(), "data_stream")
319            .expect("attach to tree");
320
321        (exec, inspector, d)
322    }
323
324    #[test]
325    fn data_stream_inspect_data_transfer_before_start_has_no_effect() {
326        let (mut exec, inspector, mut d) = setup_inspect(5_123400000);
327
328        // Default inspect tree.
329        assert_data_tree!(@executor exec, inspector, root: {
330            data_stream: {
331                total_bytes: 0 as u64,
332                streaming_secs: 0 as u64,
333                bytes_per_second_current: 0 as u64,
334            }
335        });
336
337        // Recording a data transfer before start() has no effect.
338        d.record_transferred(1, fasync::MonotonicInstant::now());
339        assert_data_tree!(@executor exec, inspector, root: {
340            data_stream: {
341                total_bytes: 0 as u64,
342                streaming_secs: 0 as u64,
343                bytes_per_second_current: 0 as u64,
344            }
345        });
346    }
347
348    #[test]
349    fn data_stream_inspect_record_past_time_has_no_effect() {
350        let curr_time = 5_678900000;
351        let (mut exec, inspector, mut d) = setup_inspect(curr_time);
352
353        d.start();
354        assert_data_tree!(@executor exec, inspector, root: {
355            data_stream: {
356                start_time: 5_678900000i64,
357                total_bytes: 0 as u64,
358                streaming_secs: 0 as u64,
359                bytes_per_second_current: 0 as u64,
360            }
361        });
362
363        // Recording a data transfer with an older time has no effect.
364        let time_from_past = curr_time - 10;
365        d.record_transferred(1, fasync::MonotonicInstant::from_nanos(time_from_past));
366        assert_data_tree!(@executor exec, inspector, root: {
367            data_stream: {
368                start_time: 5_678900000i64,
369                total_bytes: 0 as u64,
370                streaming_secs: 0 as u64,
371                bytes_per_second_current: 0 as u64,
372            }
373        });
374    }
375
376    #[test]
377    fn data_stream_inspect_data_transfer_immediately_after_start_is_ok() {
378        let curr_time = 5_678900000;
379        let (mut exec, inspector, mut d) = setup_inspect(curr_time);
380
381        d.start();
382        assert_data_tree!(@executor exec, inspector, root: {
383            data_stream: {
384                start_time: 5_678900000i64,
385                total_bytes: 0 as u64,
386                streaming_secs: 0 as u64,
387                bytes_per_second_current: 0 as u64,
388            }
389        });
390
391        // Although unlikely, recording a data transfer at the same instantaneous moment as starting
392        // is OK.
393        d.record_transferred(5, fasync::MonotonicInstant::from_nanos(curr_time));
394        assert_data_tree!(@executor exec, inspector, root: {
395            data_stream: {
396                start_time: 5_678900000i64,
397                total_bytes: 5 as u64,
398                streaming_secs: 0 as u64,
399                bytes_per_second_current: 5_000_000_000 as u64,
400            }
401        });
402    }
403
404    #[test]
405    fn data_stream_inspect_records_correct_throughput() {
406        let (mut exec, inspector, mut d) = setup_inspect(5_678900000);
407
408        d.start();
409
410        assert_data_tree!(@executor exec, inspector, root: {
411            data_stream: {
412                start_time: 5_678900000i64,
413                total_bytes: 0 as u64,
414                streaming_secs: 0 as u64,
415                bytes_per_second_current: 0 as u64,
416            }
417        });
418
419        // A half second passes.
420        exec.set_fake_time(zx::MonotonicDuration::from_millis(500).after_now());
421
422        // If we transferred 500 bytes then, we should have 1000 bytes per second.
423        d.record_transferred(500, fasync::MonotonicInstant::now());
424        assert_data_tree!(@executor exec, inspector, root: {
425            data_stream: {
426                start_time: 5_678900000i64,
427                total_bytes: 500 as u64,
428                streaming_secs: 0 as u64,
429                bytes_per_second_current: 1000 as u64,
430            }
431        });
432
433        // In 5 seconds, we transfer 500 more bytes which is much slower.
434        exec.set_fake_time(zx::MonotonicDuration::from_seconds(5).after_now());
435        d.record_transferred(500, fasync::MonotonicInstant::now());
436        assert_data_tree!(@executor exec, inspector, root: {
437            data_stream: {
438                start_time: 5_678900000i64,
439                total_bytes: 1000 as u64,
440                streaming_secs: 5 as u64,
441                bytes_per_second_current: 100 as u64,
442            }
443        });
444
445        // Receiving another update at the same time is OK.
446        d.record_transferred(900, fasync::MonotonicInstant::now());
447        assert_data_tree!(@executor exec, inspector, root: {
448            data_stream: {
449                start_time: 5_678900000i64,
450                total_bytes: 1900 as u64,
451                streaming_secs: 5 as u64,
452                bytes_per_second_current: 280 as u64,
453            }
454        });
455    }
456
457    #[test]
458    fn test_calculate_throughput() {
459        let time = fasync::MonotonicInstant::from_nanos(1_000_000_000);
460        // No throughput.
461        let bytes = 0;
462        let elapsed = std::num::NonZeroU64::new(1_000_000).unwrap();
463        let transfer1 = DataTransferStats { time, elapsed, bytes };
464        assert_eq!(transfer1.calculate_throughput(), 0);
465
466        // Fractional throughput in bytes per nano.
467        let bytes = 1;
468        let elapsed = std::num::NonZeroU64::new(1_000_000).unwrap();
469        let transfer2 = DataTransferStats { time, elapsed, bytes };
470        assert_eq!(transfer2.calculate_throughput(), 1000);
471
472        // Fractional throughput in bytes per nano.
473        let bytes = 5;
474        let elapsed = std::num::NonZeroU64::new(9_502_241).unwrap();
475        let transfer3 = DataTransferStats { time, elapsed, bytes };
476        let expected = 526; // Calculated using calculator.
477        assert_eq!(transfer3.calculate_throughput(), expected);
478
479        // Very small fractional throughput in bytes per nano. Should truncate to 0.
480        let bytes = 19;
481        let elapsed = std::num::NonZeroU64::new(5_213_999_642_004).unwrap();
482        let transfer4 = DataTransferStats { time, elapsed, bytes };
483        assert_eq!(transfer4.calculate_throughput(), 0);
484
485        // Throughput of 1 in bytes per nano.
486        let bytes = 100;
487        let elapsed = std::num::NonZeroU64::new(100).unwrap();
488        let transfer5 = DataTransferStats { time, elapsed, bytes };
489        assert_eq!(transfer5.calculate_throughput(), 1_000_000_000);
490
491        // Large throughput in bytes per nano.
492        let bytes = 100;
493        let elapsed = std::num::NonZeroU64::new(1).unwrap();
494        let transfer6 = DataTransferStats { time, elapsed, bytes };
495        assert_eq!(transfer6.calculate_throughput(), 100_000_000_000);
496
497        // Large fractional throughput in bytes per nano.
498        let bytes = 987_432_002_999;
499        let elapsed = std::num::NonZeroU64::new(453).unwrap();
500        let transfer7 = DataTransferStats { time, elapsed, bytes };
501        let expected = 2_179_761_596_024_282_368; // Calculated using calculator.
502        assert_eq!(transfer7.calculate_throughput(), expected);
503    }
504}