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 {
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
67impl<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
80pub 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#[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#[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 pub fn new(object: T, inspect: inspect::Node) -> Inspectable<T> {
120 Inspectable { inspect: T::I::new(&object, inspect), inner: object }
121 }
122}
123
124#[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#[cfg(target_os = "fuchsia")]
139pub trait ImmutableDataInspect<T> {
140 fn new(data: &T, manager: Node) -> Self;
141}
142
143#[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 fn new(data: &T, inspect: inspect::Node) -> I {
156 I::new(data, inspect)
157 }
158}
159
160#[cfg(target_os = "fuchsia")]
162struct DataTransferStats {
163 time: fasync::MonotonicInstant,
165 elapsed: std::num::NonZeroU64,
167 bytes: usize,
169}
170
171#[cfg(target_os = "fuchsia")]
172impl DataTransferStats {
173 fn calculate_throughput(&self) -> u64 {
176 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#[cfg(target_os = "fuchsia")]
187#[derive(Inspect, Default)]
188pub struct DataStreamInspect {
189 total_bytes: inspect::UintProperty,
191 bytes_per_second_current: inspect::UintProperty,
193 #[inspect(skip)]
196 start_time_prop: Option<fuchsia_inspect_contrib::nodes::MonotonicTimeProperty>,
197 #[inspect(skip)]
199 started: Option<fasync::MonotonicInstant>,
200 streaming_secs: inspect::UintProperty,
203 #[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(), bytes: 0,
223 });
224 }
225
226 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 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 (elapsed, last_bytes + bytes)
242 }
243 _ => return, };
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 #[allow(unused)]
305 bar: u8,
306 #[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 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 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 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 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 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 exec.set_fake_time(zx::MonotonicDuration::from_millis(500).after_now());
425
426 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 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 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 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 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 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; assert_eq!(transfer3.calculate_throughput(), expected);
482
483 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 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 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 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; assert_eq!(transfer7.calculate_throughput(), expected);
507 }
508}