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