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_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
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_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 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 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 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 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 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 exec.set_fake_time(zx::MonotonicDuration::from_millis(500).after_now());
419
420 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 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 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 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 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 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; assert_eq!(transfer3.calculate_throughput(), expected);
476
477 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 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 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 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; assert_eq!(transfer7.calculate_throughput(), expected);
501 }
502}