use fuchsia_async as fasync;
use fuchsia_inspect::{self as inspect, Node, NumericProperty, Property};
use fuchsia_inspect_contrib::nodes::NodeTimeExt;
use fuchsia_inspect_derive::Inspect;
use std::fmt;
const FALSE_VALUE: u64 = 0;
const TRUE_VALUE: u64 = 1;
pub trait ToProperty {
type PropertyType;
fn to_property(&self) -> Self::PropertyType;
}
impl ToProperty for bool {
type PropertyType = u64;
fn to_property(&self) -> Self::PropertyType {
if *self {
TRUE_VALUE
} else {
FALSE_VALUE
}
}
}
impl ToProperty for Option<bool> {
type PropertyType = u64;
fn to_property(&self) -> Self::PropertyType {
self.as_ref().map(bool::to_property).unwrap_or(FALSE_VALUE)
}
}
impl ToProperty for String {
type PropertyType = String;
fn to_property(&self) -> Self::PropertyType {
self.to_string()
}
}
impl<T, V> ToProperty for Vec<T>
where
T: ToProperty<PropertyType = V>,
V: ToString,
{
type PropertyType = String;
fn to_property(&self) -> Self::PropertyType {
self.iter()
.map(|t| <T as ToProperty>::to_property(t).to_string())
.collect::<Vec<String>>()
.join(", ")
}
}
impl<T, V> ToProperty for Option<Vec<T>>
where
T: ToProperty<PropertyType = V>,
V: ToString,
{
type PropertyType = String;
fn to_property(&self) -> Self::PropertyType {
self.as_ref().map(ToProperty::to_property).unwrap_or_else(String::new)
}
}
pub trait DebugExt {
fn debug(&self) -> String;
}
impl<T: fmt::Debug> DebugExt for T {
fn debug(&self) -> String {
format!("{:?}", self)
}
}
pub trait InspectData<T> {
fn new(object: &T, inspect: inspect::Node) -> Self;
}
pub trait IsInspectable
where
Self: Sized + Send + Sync + 'static,
{
type I: InspectData<Self>;
}
#[derive(Debug)]
pub struct Inspectable<T: IsInspectable> {
pub(crate) inner: T,
pub(crate) inspect: T::I,
}
impl<T: IsInspectable> Inspectable<T> {
pub fn new(object: T, inspect: inspect::Node) -> Inspectable<T> {
Inspectable { inspect: T::I::new(&object, inspect), inner: object }
}
}
impl<T: IsInspectable> std::ops::Deref for Inspectable<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
pub trait ImmutableDataInspect<T> {
fn new(data: &T, manager: Node) -> Self;
}
pub struct ImmutableDataInspectManager {
pub(crate) _manager: Node,
}
impl<T, I: ImmutableDataInspect<T>> InspectData<T> for I {
fn new(data: &T, inspect: inspect::Node) -> I {
I::new(data, inspect)
}
}
struct DataTransferStats {
time: fasync::MonotonicInstant,
elapsed: std::num::NonZeroU64,
bytes: usize,
}
impl DataTransferStats {
fn calculate_throughput(&self) -> u64 {
let bytes_per_nano = self.bytes as f64 / self.elapsed.get() as f64;
let bytes_per_second =
zx::MonotonicDuration::from_seconds(1).into_nanos() as f64 * bytes_per_nano;
bytes_per_second as u64
}
}
#[derive(Inspect, Default)]
pub struct DataStreamInspect {
total_bytes: inspect::UintProperty,
bytes_per_second_current: inspect::UintProperty,
#[inspect(skip)]
start_time_prop: Option<fuchsia_inspect_contrib::nodes::MonotonicTimeProperty>,
#[inspect(skip)]
started: Option<fasync::MonotonicInstant>,
streaming_secs: inspect::UintProperty,
#[inspect(skip)]
last_update: Option<DataTransferStats>,
inspect_node: inspect::Node,
}
impl DataStreamInspect {
pub fn start(&mut self) {
let now = fasync::MonotonicInstant::now();
if let Some(prop) = &self.start_time_prop {
prop.set_at(now.into());
} else {
self.start_time_prop = Some(self.inspect_node.create_time_at("start_time", now.into()));
}
self.started = Some(now);
self.last_update = Some(DataTransferStats {
time: now,
elapsed: std::num::NonZeroU64::new(1).unwrap(), bytes: 0,
});
}
pub fn record_transferred(&mut self, bytes: usize, at: fasync::MonotonicInstant) {
let (elapsed, current_bytes) = match self.last_update {
Some(DataTransferStats { time: last, .. }) if at > last => {
let elapsed = (at - last).into_nanos() as u64;
(std::num::NonZeroU64::new(elapsed).unwrap(), bytes)
}
Some(DataTransferStats { time: last, elapsed, bytes: last_bytes }) if at == last => {
(elapsed, last_bytes + bytes)
}
_ => return, };
let transfer = DataTransferStats { time: at, elapsed, bytes: current_bytes };
let _ = self.total_bytes.add(bytes as u64);
self.bytes_per_second_current.set(transfer.calculate_throughput());
self.last_update = Some(transfer);
if let Some(started) = &self.started {
let secs: u64 = (at - *started).into_seconds().try_into().unwrap_or(0);
self.streaming_secs.set(secs);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use diagnostics_assertions::assert_data_tree;
use fuchsia_async::DurationExt;
use fuchsia_inspect_derive::WithInspect;
#[test]
fn bool_to_property() {
let b = false.to_property();
assert_eq!(b, FALSE_VALUE);
let b = true.to_property();
assert_eq!(b, TRUE_VALUE);
}
#[test]
fn optional_bool_to_property() {
let b: u64 = None::<bool>.to_property();
assert_eq!(b, FALSE_VALUE);
let b = Some(false).to_property();
assert_eq!(b, FALSE_VALUE);
let b = Some(true).to_property();
assert_eq!(b, TRUE_VALUE);
}
#[test]
fn string_vec_to_property() {
let s = Vec::<String>::new().to_property();
assert_eq!(s, "");
let s = vec!["foo".to_string()].to_property();
assert_eq!(s, "foo");
let s = vec!["foo".to_string(), "bar".to_string(), "baz".to_string()].to_property();
assert_eq!(s, "foo, bar, baz");
}
#[test]
fn optional_string_vec_to_property() {
let s = Some(vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]).to_property();
assert_eq!(s, "foo, bar, baz");
}
#[test]
fn debug_string() {
#[derive(Debug)]
struct Foo {
#[allow(unused)]
bar: u8,
#[allow(unused)]
baz: &'static str,
}
let foo = Foo { bar: 1, baz: "baz value" };
assert_eq!(format!("{:?}", foo), foo.debug());
}
fn setup_inspect(
curr_time: i64,
) -> (fasync::TestExecutor, fuchsia_inspect::Inspector, DataStreamInspect) {
let exec = fasync::TestExecutor::new_with_fake_time();
exec.set_fake_time(fasync::MonotonicInstant::from_nanos(curr_time));
let inspector = fuchsia_inspect::Inspector::default();
let d = DataStreamInspect::default()
.with_inspect(inspector.root(), "data_stream")
.expect("attach to tree");
(exec, inspector, d)
}
#[test]
fn data_stream_inspect_data_transfer_before_start_has_no_effect() {
let (_exec, inspector, mut d) = setup_inspect(5_123400000);
assert_data_tree!(inspector, root: {
data_stream: {
total_bytes: 0 as u64,
streaming_secs: 0 as u64,
bytes_per_second_current: 0 as u64,
}
});
d.record_transferred(1, fasync::MonotonicInstant::now());
assert_data_tree!(inspector, root: {
data_stream: {
total_bytes: 0 as u64,
streaming_secs: 0 as u64,
bytes_per_second_current: 0 as u64,
}
});
}
#[test]
fn data_stream_inspect_record_past_time_has_no_effect() {
let curr_time = 5_678900000;
let (_exec, inspector, mut d) = setup_inspect(curr_time);
d.start();
assert_data_tree!(inspector, root: {
data_stream: {
start_time: 5_678900000i64,
total_bytes: 0 as u64,
streaming_secs: 0 as u64,
bytes_per_second_current: 0 as u64,
}
});
let time_from_past = curr_time - 10;
d.record_transferred(1, fasync::MonotonicInstant::from_nanos(time_from_past));
assert_data_tree!(inspector, root: {
data_stream: {
start_time: 5_678900000i64,
total_bytes: 0 as u64,
streaming_secs: 0 as u64,
bytes_per_second_current: 0 as u64,
}
});
}
#[test]
fn data_stream_inspect_data_transfer_immediately_after_start_is_ok() {
let curr_time = 5_678900000;
let (_exec, inspector, mut d) = setup_inspect(curr_time);
d.start();
assert_data_tree!(inspector, root: {
data_stream: {
start_time: 5_678900000i64,
total_bytes: 0 as u64,
streaming_secs: 0 as u64,
bytes_per_second_current: 0 as u64,
}
});
d.record_transferred(5, fasync::MonotonicInstant::from_nanos(curr_time));
assert_data_tree!(inspector, root: {
data_stream: {
start_time: 5_678900000i64,
total_bytes: 5 as u64,
streaming_secs: 0 as u64,
bytes_per_second_current: 5_000_000_000 as u64,
}
});
}
#[test]
fn data_stream_inspect_records_correct_throughput() {
let (exec, inspector, mut d) = setup_inspect(5_678900000);
d.start();
assert_data_tree!(inspector, root: {
data_stream: {
start_time: 5_678900000i64,
total_bytes: 0 as u64,
streaming_secs: 0 as u64,
bytes_per_second_current: 0 as u64,
}
});
exec.set_fake_time(zx::MonotonicDuration::from_millis(500).after_now());
d.record_transferred(500, fasync::MonotonicInstant::now());
assert_data_tree!(inspector, root: {
data_stream: {
start_time: 5_678900000i64,
total_bytes: 500 as u64,
streaming_secs: 0 as u64,
bytes_per_second_current: 1000 as u64,
}
});
exec.set_fake_time(zx::MonotonicDuration::from_seconds(5).after_now());
d.record_transferred(500, fasync::MonotonicInstant::now());
assert_data_tree!(inspector, root: {
data_stream: {
start_time: 5_678900000i64,
total_bytes: 1000 as u64,
streaming_secs: 5 as u64,
bytes_per_second_current: 100 as u64,
}
});
d.record_transferred(900, fasync::MonotonicInstant::now());
assert_data_tree!(inspector, root: {
data_stream: {
start_time: 5_678900000i64,
total_bytes: 1900 as u64,
streaming_secs: 5 as u64,
bytes_per_second_current: 280 as u64,
}
});
}
#[test]
fn test_calculate_throughput() {
let time = fasync::MonotonicInstant::from_nanos(1_000_000_000);
let bytes = 0;
let elapsed = std::num::NonZeroU64::new(1_000_000).unwrap();
let transfer1 = DataTransferStats { time, elapsed, bytes };
assert_eq!(transfer1.calculate_throughput(), 0);
let bytes = 1;
let elapsed = std::num::NonZeroU64::new(1_000_000).unwrap();
let transfer2 = DataTransferStats { time, elapsed, bytes };
assert_eq!(transfer2.calculate_throughput(), 1000);
let bytes = 5;
let elapsed = std::num::NonZeroU64::new(9_502_241).unwrap();
let transfer3 = DataTransferStats { time, elapsed, bytes };
let expected = 526; assert_eq!(transfer3.calculate_throughput(), expected);
let bytes = 19;
let elapsed = std::num::NonZeroU64::new(5_213_999_642_004).unwrap();
let transfer4 = DataTransferStats { time, elapsed, bytes };
assert_eq!(transfer4.calculate_throughput(), 0);
let bytes = 100;
let elapsed = std::num::NonZeroU64::new(100).unwrap();
let transfer5 = DataTransferStats { time, elapsed, bytes };
assert_eq!(transfer5.calculate_throughput(), 1_000_000_000);
let bytes = 100;
let elapsed = std::num::NonZeroU64::new(1).unwrap();
let transfer6 = DataTransferStats { time, elapsed, bytes };
assert_eq!(transfer6.calculate_throughput(), 100_000_000_000);
let bytes = 987_432_002_999;
let elapsed = std::num::NonZeroU64::new(453).unwrap();
let transfer7 = DataTransferStats { time, elapsed, bytes };
let expected = 2_179_761_596_024_282_368; assert_eq!(transfer7.calculate_throughput(), expected);
}
}