1#[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
18pub 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
63impl<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
76pub 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#[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#[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 pub fn new(object: T, inspect: inspect::Node) -> Inspectable<T> {
116 Inspectable { inspect: T::I::new(&object, inspect), inner: object }
117 }
118}
119
120#[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#[cfg(target_os = "fuchsia")]
135pub trait ImmutableDataInspect<T> {
136 fn new(data: &T, manager: Node) -> Self;
137}
138
139#[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 fn new(data: &T, inspect: inspect::Node) -> I {
152 I::new(data, inspect)
153 }
154}
155
156#[cfg(target_os = "fuchsia")]
158struct DataTransferStats {
159 time: fasync::MonotonicInstant,
161 elapsed: std::num::NonZeroU64,
163 bytes: usize,
165}
166
167#[cfg(target_os = "fuchsia")]
168impl DataTransferStats {
169 fn calculate_throughput(&self) -> u64 {
172 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#[cfg(target_os = "fuchsia")]
183#[derive(Inspect, Default)]
184pub struct DataStreamInspect {
185 total_bytes: inspect::UintProperty,
187 bytes_per_second_current: inspect::UintProperty,
189 #[inspect(skip)]
192 start_time_prop: Option<fuchsia_inspect_contrib::nodes::MonotonicTimeProperty>,
193 #[inspect(skip)]
195 started: Option<fasync::MonotonicInstant>,
196 streaming_secs: inspect::UintProperty,
199 #[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(), bytes: 0,
219 });
220 }
221
222 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 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 (elapsed, last_bytes + bytes)
238 }
239 _ => return, };
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 #[allow(unused)]
301 bar: u8,
302 #[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 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 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 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 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 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 exec.set_fake_time(zx::MonotonicDuration::from_millis(500).after_now());
421
422 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 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 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 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 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 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; assert_eq!(transfer3.calculate_throughput(), expected);
478
479 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 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 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 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; assert_eq!(transfer7.calculate_throughput(), expected);
503 }
504}