Skip to main content

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_default()
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_default();
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            #[allow(dead_code)]
300            bar: u8,
301            #[allow(dead_code)]
302            baz: &'static str,
303        }
304        let foo = Foo { bar: 1, baz: "baz value" };
305        assert_eq!(format!("{:?}", foo), foo.debug());
306    }
307
308    /// Sets up an inspect test with an executor at timestamp `curr_time`.
309    fn setup_inspect(
310        curr_time: i64,
311    ) -> (fasync::TestExecutor, fuchsia_inspect::Inspector, DataStreamInspect) {
312        let exec = fasync::TestExecutor::new_with_fake_time();
313        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(curr_time));
314        let inspector = fuchsia_inspect::Inspector::default();
315        let d = DataStreamInspect::default()
316            .with_inspect(inspector.root(), "data_stream")
317            .expect("attach to tree");
318
319        (exec, inspector, d)
320    }
321
322    #[test]
323    fn data_stream_inspect_data_transfer_before_start_has_no_effect() {
324        let (mut exec, inspector, mut d) = setup_inspect(5_123400000);
325
326        // Default inspect tree.
327        assert_data_tree!(@executor exec, inspector, root: {
328            data_stream: {
329                total_bytes: 0 as u64,
330                streaming_secs: 0 as u64,
331                bytes_per_second_current: 0 as u64,
332            }
333        });
334
335        // Recording a data transfer before start() has no effect.
336        d.record_transferred(1, fasync::MonotonicInstant::now());
337        assert_data_tree!(@executor exec, inspector, root: {
338            data_stream: {
339                total_bytes: 0 as u64,
340                streaming_secs: 0 as u64,
341                bytes_per_second_current: 0 as u64,
342            }
343        });
344    }
345
346    #[test]
347    fn data_stream_inspect_record_past_time_has_no_effect() {
348        let curr_time = 5_678900000;
349        let (mut exec, inspector, mut d) = setup_inspect(curr_time);
350
351        d.start();
352        assert_data_tree!(@executor exec, inspector, root: {
353            data_stream: {
354                start_time: 5_678900000i64,
355                total_bytes: 0 as u64,
356                streaming_secs: 0 as u64,
357                bytes_per_second_current: 0 as u64,
358            }
359        });
360
361        // Recording a data transfer with an older time has no effect.
362        let time_from_past = curr_time - 10;
363        d.record_transferred(1, fasync::MonotonicInstant::from_nanos(time_from_past));
364        assert_data_tree!(@executor exec, inspector, root: {
365            data_stream: {
366                start_time: 5_678900000i64,
367                total_bytes: 0 as u64,
368                streaming_secs: 0 as u64,
369                bytes_per_second_current: 0 as u64,
370            }
371        });
372    }
373
374    #[test]
375    fn data_stream_inspect_data_transfer_immediately_after_start_is_ok() {
376        let curr_time = 5_678900000;
377        let (mut exec, inspector, mut d) = setup_inspect(curr_time);
378
379        d.start();
380        assert_data_tree!(@executor exec, inspector, root: {
381            data_stream: {
382                start_time: 5_678900000i64,
383                total_bytes: 0 as u64,
384                streaming_secs: 0 as u64,
385                bytes_per_second_current: 0 as u64,
386            }
387        });
388
389        // Although unlikely, recording a data transfer at the same instantaneous moment as starting
390        // is OK.
391        d.record_transferred(5, fasync::MonotonicInstant::from_nanos(curr_time));
392        assert_data_tree!(@executor exec, inspector, root: {
393            data_stream: {
394                start_time: 5_678900000i64,
395                total_bytes: 5 as u64,
396                streaming_secs: 0 as u64,
397                bytes_per_second_current: 5_000_000_000 as u64,
398            }
399        });
400    }
401
402    #[test]
403    fn data_stream_inspect_records_correct_throughput() {
404        let (mut exec, inspector, mut d) = setup_inspect(5_678900000);
405
406        d.start();
407
408        assert_data_tree!(@executor exec, inspector, root: {
409            data_stream: {
410                start_time: 5_678900000i64,
411                total_bytes: 0 as u64,
412                streaming_secs: 0 as u64,
413                bytes_per_second_current: 0 as u64,
414            }
415        });
416
417        // A half second passes.
418        exec.set_fake_time(zx::MonotonicDuration::from_millis(500).after_now());
419
420        // If we transferred 500 bytes then, we should have 1000 bytes per second.
421        d.record_transferred(500, fasync::MonotonicInstant::now());
422        assert_data_tree!(@executor exec, inspector, root: {
423            data_stream: {
424                start_time: 5_678900000i64,
425                total_bytes: 500 as u64,
426                streaming_secs: 0 as u64,
427                bytes_per_second_current: 1000 as u64,
428            }
429        });
430
431        // In 5 seconds, we transfer 500 more bytes which is much slower.
432        exec.set_fake_time(zx::MonotonicDuration::from_seconds(5).after_now());
433        d.record_transferred(500, fasync::MonotonicInstant::now());
434        assert_data_tree!(@executor exec, inspector, root: {
435            data_stream: {
436                start_time: 5_678900000i64,
437                total_bytes: 1000 as u64,
438                streaming_secs: 5 as u64,
439                bytes_per_second_current: 100 as u64,
440            }
441        });
442
443        // Receiving another update at the same time is OK.
444        d.record_transferred(900, fasync::MonotonicInstant::now());
445        assert_data_tree!(@executor exec, inspector, root: {
446            data_stream: {
447                start_time: 5_678900000i64,
448                total_bytes: 1900 as u64,
449                streaming_secs: 5 as u64,
450                bytes_per_second_current: 280 as u64,
451            }
452        });
453    }
454
455    #[test]
456    fn test_calculate_throughput() {
457        let time = fasync::MonotonicInstant::from_nanos(1_000_000_000);
458        // No throughput.
459        let bytes = 0;
460        let elapsed = std::num::NonZeroU64::new(1_000_000).unwrap();
461        let transfer1 = DataTransferStats { time, elapsed, bytes };
462        assert_eq!(transfer1.calculate_throughput(), 0);
463
464        // Fractional throughput in bytes per nano.
465        let bytes = 1;
466        let elapsed = std::num::NonZeroU64::new(1_000_000).unwrap();
467        let transfer2 = DataTransferStats { time, elapsed, bytes };
468        assert_eq!(transfer2.calculate_throughput(), 1000);
469
470        // Fractional throughput in bytes per nano.
471        let bytes = 5;
472        let elapsed = std::num::NonZeroU64::new(9_502_241).unwrap();
473        let transfer3 = DataTransferStats { time, elapsed, bytes };
474        let expected = 526; // Calculated using calculator.
475        assert_eq!(transfer3.calculate_throughput(), expected);
476
477        // Very small fractional throughput in bytes per nano. Should truncate to 0.
478        let bytes = 19;
479        let elapsed = std::num::NonZeroU64::new(5_213_999_642_004).unwrap();
480        let transfer4 = DataTransferStats { time, elapsed, bytes };
481        assert_eq!(transfer4.calculate_throughput(), 0);
482
483        // Throughput of 1 in bytes per nano.
484        let bytes = 100;
485        let elapsed = std::num::NonZeroU64::new(100).unwrap();
486        let transfer5 = DataTransferStats { time, elapsed, bytes };
487        assert_eq!(transfer5.calculate_throughput(), 1_000_000_000);
488
489        // Large throughput in bytes per nano.
490        let bytes = 100;
491        let elapsed = std::num::NonZeroU64::new(1).unwrap();
492        let transfer6 = DataTransferStats { time, elapsed, bytes };
493        assert_eq!(transfer6.calculate_throughput(), 100_000_000_000);
494
495        // Large fractional throughput in bytes per nano.
496        let bytes = 987_432_002_999;
497        let elapsed = std::num::NonZeroU64::new(453).unwrap();
498        let transfer7 = DataTransferStats { time, elapsed, bytes };
499        let expected = 2_179_761_596_024_282_368; // Calculated using calculator.
500        assert_eq!(transfer7.calculate_throughput(), expected);
501    }
502}