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