1use std::borrow::Cow;
6
7use crate::fetcher::TagData;
8use crate::file_handler::{self, Timestamps};
9use diagnostics_data::{Data, DiagnosticsHierarchy, InspectMetadata, Property};
10use fuchsia_inspect::hierarchy::{ExponentialHistogram, LinearHistogram, MissingValue};
11use fuchsia_inspect::reader::ArrayContent;
12use fuchsia_inspect::{
13 ArrayProperty, ExponentialHistogramParams, HistogramProperty, LinearHistogramParams, Node,
14 component,
15};
16use itertools::Either;
17
18fn store_data(node: &fuchsia_inspect::Node, data: TagData) {
19 let TagData { max_bytes: _, total_bytes, timestamps, selectors: _, data, errors } = data;
20
21 node.record_uint("@persist_size", total_bytes as u64);
23
24 let array = node.create_string_array("@errors", errors.len());
26 for (index, err) in errors.iter().enumerate() {
27 array.set(index, err);
28 }
29 node.record(array);
30
31 node.record_child("@timestamps", |timestamps_node| {
33 let Timestamps { last_sample_boot, last_sample_utc } = timestamps;
34 timestamps_node.record_int("last_sample_boot", last_sample_boot.into_nanos());
35 timestamps_node.record_int("last_sample_utc", last_sample_utc.into_nanos());
36 });
37
38 for (moniker, data) in data {
40 node.record_child(moniker.to_string(), |node| {
41 let Data { data_source: _, metadata, moniker: _, payload, version: _ } = data;
42 let InspectMetadata { errors, name: _, component_url: _, timestamp: _, escrowed: _ } =
43 metadata;
44
45 if let Some(errs) = errors
47 && !errs.is_empty()
48 {
49 let array = node.create_string_array("@errors", errs.len());
50 for (index, err) in errs.into_iter().enumerate() {
51 array.set(index, err.message);
52 }
53 node.record(array);
54 }
55
56 if let Some(payload) = payload {
57 if payload.name == "root" {
59 let DiagnosticsHierarchy { name: _, properties, children, missing } = payload;
60 store_hierarchy_inner(node, properties, children, missing);
61 } else {
62 store_hierarchy(node, payload);
63 }
64 }
65 })
66 }
67}
68
69fn store_hierarchy(node: &fuchsia_inspect::Node, data: DiagnosticsHierarchy) {
70 let DiagnosticsHierarchy { name, properties, children, missing } = data;
71 node.record_child(name, |node| {
74 store_hierarchy_inner(node, properties, children, missing);
75 });
76}
77
78fn store_hierarchy_inner(
79 node: &fuchsia_inspect::Node,
80 properties: Vec<Property>,
81 children: Vec<DiagnosticsHierarchy>,
82 missing: Vec<MissingValue>,
83) {
84 if !missing.is_empty() {
86 node.record_child("@missing", |missing_node| {
87 for MissingValue { name, reason } in missing {
88 missing_node.record_string(name.clone(), format!("{reason:?}"));
89 }
90 })
91 }
92
93 for property in properties {
95 match property {
96 Property::String(k, v) => {
97 node.record_string(k, v);
98 }
99 Property::Bytes(k, v) => {
100 node.record_bytes(k, v);
101 }
102 Property::Int(k, v) => {
103 node.record_int(k, v);
104 }
105 Property::Uint(k, v) => {
106 node.record_uint(k, v);
107 }
108 Property::Double(k, v) => {
109 node.record_double(k, v);
110 }
111 Property::Bool(k, v) => {
112 node.record_bool(k, v);
113 }
114 Property::DoubleArray(k, v) => v.record(node, k),
115 Property::IntArray(k, v) => v.record(node, k),
116 Property::UintArray(k, v) => v.record(node, k),
117 Property::StringList(k, v) => {
118 let array = node.create_string_array(k, v.len());
119 for (i, v) in v.iter().enumerate() {
120 array.set(i, v);
121 }
122 node.record(array);
123 }
124 }
125 }
126
127 for child in children {
129 store_hierarchy(node, child);
130 }
131}
132
133trait InspectData<'a> {
135 fn record(self, node: &Node, name: impl Into<Cow<'a, str>>);
137}
138
139fn get_index_counts<T>(
140 indexes: Option<Vec<usize>>,
141 counts: Vec<T>,
142) -> impl Iterator<Item = (usize, T)> {
143 match indexes {
144 None => Either::Left(counts.into_iter().enumerate()),
145 Some(indexes) => Either::Right(indexes.into_iter().zip(counts)),
146 }
147}
148
149impl<'a> InspectData<'a> for ArrayContent<f64>
150where
151 Self: 'a,
152{
153 fn record(self, node: &Node, name: impl Into<Cow<'a, str>>) {
154 match self {
155 Self::Values(values) => {
156 let array = node.create_double_array(name, values.len());
157 for (index, value) in values.iter().enumerate() {
158 array.set(index, *value);
159 }
160 node.record(array);
161 }
162 Self::LinearHistogram(LinearHistogram { size, floor, step, counts, indexes }) => {
163 let name = name.into();
164
165 let array = node.create_double_linear_histogram(
166 name,
167 LinearHistogramParams {
168 floor,
169 step_size: step,
170 buckets: size.saturating_sub(2),
172 },
173 );
174
175 for (bucket_index, count) in get_index_counts(indexes, counts) {
176 if count < 1.0 {
177 continue;
178 }
179 let value = if bucket_index == 0 {
180 if floor == f64::NEG_INFINITY {
181 continue;
184 }
185 f64::NEG_INFINITY
186 } else {
187 floor + step * (bucket_index - 1) as f64
188 };
189 array.insert_multiple(value, count.round() as usize);
190 }
191
192 node.record(array);
193 }
194 Self::ExponentialHistogram(ExponentialHistogram {
195 size,
196 floor,
197 initial_step,
198 step_multiplier,
199 counts,
200 indexes,
201 }) => {
202 let name = name.into();
203
204 let array = node.create_double_exponential_histogram(
205 name,
206 ExponentialHistogramParams {
207 floor,
208 initial_step,
209 step_multiplier,
210 buckets: size.saturating_sub(2),
212 },
213 );
214
215 for (bucket_index, count) in get_index_counts(indexes, counts) {
216 if count < 1.0 {
217 continue;
218 }
219 let value = if bucket_index == 0 {
220 if floor == f64::NEG_INFINITY {
221 continue;
224 }
225 f64::NEG_INFINITY
226 } else if bucket_index == 1 {
227 floor
228 } else {
229 let multiplier = step_multiplier.powi((bucket_index - 2) as i32);
230 initial_step.mul_add(multiplier, floor)
231 };
232 array.insert_multiple(value, count.round() as usize);
233 }
234
235 node.record(array);
236 }
237 }
238 }
239}
240
241impl<'a> InspectData<'a> for ArrayContent<i64>
242where
243 Self: 'a,
244{
245 fn record(self, node: &Node, name: impl Into<Cow<'a, str>>) {
246 match self {
247 Self::Values(values) => {
248 let array = node.create_int_array(name, values.len());
249 for (index, value) in values.iter().enumerate() {
250 array.set(index, *value);
251 }
252 node.record(array);
253 }
254 Self::LinearHistogram(LinearHistogram { size, floor, step, counts, indexes }) => {
255 let name = name.into();
256
257 let array = node.create_int_linear_histogram(
258 name,
259 LinearHistogramParams {
260 floor,
261 step_size: step,
262 buckets: size.saturating_sub(2),
264 },
265 );
266
267 for (bucket_index, count) in get_index_counts(indexes, counts) {
268 if count == 0 {
269 continue;
270 }
271 let value = if bucket_index == 0 {
272 if let Some(res) = floor.checked_sub(step.signum()) {
273 res
274 } else {
275 continue;
278 }
279 } else {
280 let step = step as i128;
283 let floor = floor as i128;
284
285 let value =
287 floor.saturating_add(step.saturating_mul((bucket_index - 1) as i128));
288
289 if value > i64::MAX as i128 {
290 i64::MAX
291 } else if value < i64::MIN as i128 {
292 i64::MIN
293 } else {
294 value as i64
295 }
296 };
297 array.insert_multiple(value, count as usize);
298 }
299
300 node.record(array);
301 }
302 Self::ExponentialHistogram(ExponentialHistogram {
303 size,
304 floor,
305 initial_step,
306 step_multiplier,
307 counts,
308 indexes,
309 }) => {
310 let name = name.into();
311
312 let array = node.create_int_exponential_histogram(
313 name,
314 ExponentialHistogramParams {
315 floor,
316 initial_step,
317 step_multiplier,
318 buckets: size.saturating_sub(2),
320 },
321 );
322
323 for (bucket_index, count) in get_index_counts(indexes, counts) {
324 if count == 0 {
325 continue;
326 }
327 let value = if bucket_index == 0 {
328 if let Some(res) = floor.checked_sub(initial_step.signum()) {
329 res
330 } else {
331 continue;
334 }
335 } else if bucket_index == 1 {
336 floor
337 } else {
338 let step_multiplier = step_multiplier as i128;
341 let initial_step = initial_step as i128;
342 let floor = floor as i128;
343
344 let value = floor.saturating_add(initial_step.saturating_mul(
346 step_multiplier.saturating_pow((bucket_index - 2) as u32),
347 ));
348
349 if value > i64::MAX as i128 {
350 i64::MAX
351 } else if value < i64::MIN as i128 {
352 i64::MIN
353 } else {
354 value as i64
355 }
356 };
357 array.insert_multiple(value, count as usize);
358 }
359
360 node.record(array);
361 }
362 }
363 }
364}
365
366impl<'a> InspectData<'a> for ArrayContent<u64>
367where
368 Self: 'a,
369{
370 fn record(self, node: &Node, name: impl Into<Cow<'a, str>>) {
371 match self {
372 Self::Values(values) => {
373 let array = node.create_uint_array(name, values.len());
374 for (index, value) in values.iter().enumerate() {
375 array.set(index, *value);
376 }
377 node.record(array);
378 }
379 Self::LinearHistogram(LinearHistogram { size, floor, step, counts, indexes }) => {
380 let name = name.into();
381
382 let array = node.create_uint_linear_histogram(
383 name,
384 LinearHistogramParams {
385 floor,
386 step_size: step,
387 buckets: size.saturating_sub(2),
389 },
390 );
391
392 for (bucket_index, count) in get_index_counts(indexes, counts) {
393 if count == 0 {
394 continue;
395 }
396 let value = if bucket_index == 0 {
397 if floor == u64::MIN {
398 continue;
401 }
402 u64::MIN
403 } else {
404 floor.saturating_add(step.saturating_mul((bucket_index - 1) as u64))
406 };
407 array.insert_multiple(value, count as usize);
408 }
409
410 node.record(array);
411 }
412 Self::ExponentialHistogram(ExponentialHistogram {
413 size,
414 floor,
415 initial_step,
416 step_multiplier,
417 counts,
418 indexes,
419 }) => {
420 let name = name.into();
421
422 let array = node.create_uint_exponential_histogram(
423 name,
424 ExponentialHistogramParams {
425 floor,
426 initial_step,
427 step_multiplier,
428 buckets: size.saturating_sub(2),
430 },
431 );
432
433 for (bucket_index, count) in get_index_counts(indexes, counts) {
434 if count == 0 {
435 continue;
436 }
437 let value = if bucket_index == 0 {
438 if floor == u64::MIN {
439 continue;
442 }
443 u64::MIN
444 } else if bucket_index == 1 {
445 floor
446 } else {
447 floor.saturating_add(initial_step.saturating_mul(
449 step_multiplier.saturating_pow((bucket_index - 2) as u32),
450 ))
451 };
452 array.insert_multiple(value, count as usize);
453 }
454
455 node.record(array);
456 }
457 }
458 }
459}
460
461pub async fn record_persist_node(name: &str) -> Result<(), anyhow::Error> {
462 if let Some(data) = file_handler::previous_data().await? {
463 component::inspector().root().record_child(name, |persist_node| {
464 for (service, service_data) in data.0 {
465 persist_node.record_child(service.to_string(), |service_node| {
466 for (tag, tag_data) in service_data.0 {
467 service_node.record_child(tag.to_string(), |tag_node| {
468 store_data(tag_node, tag_data);
469 })
470 }
471 });
472 }
473 });
474 }
475 Ok(())
476}
477
478#[cfg(test)]
479mod tests {
480 use std::collections::VecDeque;
481 use std::str::FromStr;
482
483 use super::*;
484 use anyhow::Error;
485 use diagnostics_assertions::{PropertyAssertion, assert_data_tree};
486 use diagnostics_data::{InspectError, InspectHandleName, hierarchy};
487 use flyweights::FlyStr;
488 use fuchsia_inspect::Inspector;
489 use hashbrown::HashMap;
490 use test_case::test_case;
491 use zx::Instant;
492
493 #[fuchsia::test]
495 async fn store_data_works() -> Result<(), Error> {
496 let inspector = Inspector::default();
497 let inspect = inspector.root();
498 assert_data_tree!(
499 inspector,
500 root: contains {
501 }
502 );
503
504 let moniker = diagnostics_data::ExtendedMoniker::from_str("types").unwrap();
505
506 let tag_data = TagData {
507 max_bytes: 1,
508 total_bytes: 2,
509 timestamps: Timestamps {
510 last_sample_boot: zx::BootInstant::from_nanos(3),
511 last_sample_utc: fuchsia_runtime::UtcInstant::from_nanos(4),
512 },
513 selectors: vec![],
514 data: HashMap::from([(
515 moniker.clone().into(),
516 Data {
517 data_source: diagnostics_data::DataSource::Inspect,
518 metadata: InspectMetadata {
519 errors: Some(vec![InspectError {
520 message: "test InspectError".to_string(),
521 }]),
522 name: InspectHandleName::Name(FlyStr::new("inspect_handle_name")),
523 component_url: FlyStr::new("component_url"),
524 timestamp: Instant::from_nanos(0),
525 escrowed: false,
526 },
527 moniker,
528 payload: Some(hierarchy! {
529 root: {
530 negint: -5,
531 int: 42,
532 unsigned: 9223372036854775808u64,
533 float: 45.6f64,
534 bool: true,
535 obj: {
536 child: "child",
537 grandchild: {
538 hello: "world",
539 }
540 }
541 }
542 }),
543 version: 1,
544 },
545 )]),
546 errors: VecDeque::from(["test TagData error".to_string()]),
547 };
548
549 store_data(inspect, tag_data);
550
551 assert_data_tree!(
552 inspector,
553 root: {
554 "@errors": vec!["test TagData error"],
555 "@persist_size": 2u64,
556 "@timestamps": {
557 last_sample_boot: 3,
558 last_sample_utc: 4,
559 },
560 types: {
561 "@errors": vec!["test InspectError"],
562 negint: -5i64,
563 int: 42i64,
564 unsigned: 9223372036854775808u64,
565 float: 45.6f64,
566 bool: true,
567 obj: {
568 child: "child",
569 grandchild: {
570 hello: "world",
571 }
572 }
573 }
574 }
575 );
576 Ok(())
577 }
578
579 #[test_case(vec![1.0, 2.0, 3.0, 4.0] ; "f64_many")]
580 #[test_case(vec![1i64, 2i64, 3i64, 4i64] ; "i64_many")]
581 #[test_case(vec![1u64, 2u64, 3u64, 4u64] ; "u64_many")]
582 #[test_case(vec![1.0] ; "f64_one")]
583 #[test_case(vec![1i64] ; "i64_one")]
584 #[test_case(vec![1u64] ; "u64_one")]
585 #[test_case(Vec::<f64>::new() ; "f64_none")]
586 #[test_case(Vec::<i64>::new() ; "i64_none")]
587 #[test_case(Vec::<u64>::new() ; "u64_none")]
588 #[fuchsia::test]
589 async fn record_values<'a, T>(values: Vec<T>)
590 where
591 ArrayContent<T>: InspectData<'a>,
592 Vec<T>: PropertyAssertion,
593 T: Clone + 'static,
594 {
595 let inspector = Inspector::default();
596 ArrayContent::Values(values.clone()).record(inspector.root(), "child");
597 assert_data_tree!(
598 inspector,
599 root: {
600 child: values,
601 }
602 );
603 }
604
605 #[test_case(
606 LinearHistogram {
607 size: 4,
608 floor: 0.0,
609 step: 10.0,
610 counts: vec![1.0, 2.0, 3.0, 4.0],
611 indexes: None,
612 } ;
613 "f64_dense"
614 )]
615 #[test_case(
616 LinearHistogram {
617 size: 4,
618 floor: 0i64,
619 step: 10i64,
620 counts: vec![1i64, 2i64, 3i64, 4i64],
621 indexes: None,
622 } ;
623 "i64_dense"
624 )]
625 #[test_case(
626 LinearHistogram {
627 size: 4,
628 floor: 0u64,
629 step: 10u64,
630 counts: vec![0u64, 2u64, 3u64, 4u64],
632 indexes: None,
633 } ;
634 "u64_dense"
635 )]
636 #[test_case(
637 LinearHistogram {
638 size: 4,
639 floor: 0.0,
640 step: 10.0,
641 counts: vec![5.0],
642 indexes: Some(vec![1]),
643 } ;
644 "f64_sparse"
645 )]
646 #[test_case(
647 LinearHistogram {
648 size: 4,
649 floor: 0i64,
650 step: 10i64,
651 counts: vec![5i64],
652 indexes: Some(vec![1]),
653 } ;
654 "i64_sparse"
655 )]
656 #[test_case(
657 LinearHistogram {
658 size: 4,
659 floor: 0u64,
660 step: 10u64,
661 counts: vec![5u64],
662 indexes: Some(vec![1]),
663 } ;
664 "u64_sparse"
665 )]
666 #[test_case(
673 LinearHistogram {
676 size: 4,
677 floor: f64::MIN,
678 step: f64::MAX,
679 counts: vec![1.0, 2.0, 3.0, 4.0],
680 indexes: None,
681 } ;
682 "f64_bounds"
683 )]
684 #[test_case(
690 LinearHistogram {
692 size: 3,
693 floor: i64::MIN,
694 step: 1,
695 counts: vec![0i64, 1i64, 2i64],
696 indexes: None,
697 } ;
698 "i64_bounds_min"
699 )]
700 #[test_case(
707 LinearHistogram {
708 size: 4,
709 floor: i64::MIN+1,
712 step: i64::MAX,
713 counts: vec![1i64, 2i64, 3i64, 4i64],
714 indexes: None,
715 } ;
716 "i64_bounds_max"
717 )]
718 #[test_case(
719 LinearHistogram {
720 size: 3,
721 floor: u64::MIN,
722 step: u64::MAX / 2,
723 counts: vec![0u64, 1u64, 2u64],
725 indexes: None,
726 } ;
727 "u64_bounds"
728 )]
729 #[fuchsia::test]
730 async fn record_linear_histogram<'a, T>(histogram: LinearHistogram<T>)
731 where
732 ArrayContent<T>: InspectData<'a>,
733 LinearHistogram<T>: PropertyAssertion + Clone,
734 T: 'static,
735 {
736 let inspector = Inspector::default();
737 ArrayContent::LinearHistogram(histogram.clone()).record(inspector.root(), "child");
738 assert_data_tree!(
739 inspector,
740 root: {
741 child: histogram,
742 }
743 );
744 }
745
746 #[test_case(
757 ExponentialHistogram {
758 size: 6,
759 floor: 1.0,
760 initial_step: 1.0,
761 step_multiplier: 2.0,
762 counts: vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
763 indexes: None,
764 } ;
765 "f64_dense"
766 )]
767 #[test_case(
768 ExponentialHistogram {
769 size: 6,
770 floor: 1i64,
771 initial_step: 1i64,
772 step_multiplier: 2i64,
773 counts: vec![1i64, 2i64, 3i64, 4i64, 5i64, 6i64],
774 indexes: None,
775 } ;
776 "i64_dense"
777 )]
778 #[test_case(
779 ExponentialHistogram {
780 size: 6,
781 floor: 1u64,
782 initial_step: 1u64,
783 step_multiplier: 2u64,
784 counts: vec![1u64, 2u64, 3u64, 4u64, 5u64, 6u64],
785 indexes: None,
786 } ;
787 "u64_dense"
788 )]
789 #[test_case(
790 ExponentialHistogram {
791 size: 10,
792 floor: 1.0,
793 initial_step: 1.0,
794 step_multiplier: 2.0,
795 counts: vec![5.0],
796 indexes: Some(vec![5]),
797 } ;
798 "f64_sparse"
799 )]
800 #[test_case(
801 ExponentialHistogram {
802 size: 10,
803 floor: 1i64,
804 initial_step: 1i64,
805 step_multiplier: 2i64,
806 counts: vec![5i64],
807 indexes: Some(vec![5]),
808 } ;
809 "i64_sparse"
810 )]
811 #[test_case(
812 ExponentialHistogram {
813 size: 10,
814 floor: 1u64,
815 initial_step: 1u64,
816 step_multiplier: 2u64,
817 counts: vec![5u64],
818 indexes: Some(vec![5]),
819 } ;
820 "u64_sparse"
821 )]
822 #[test_case(
830 ExponentialHistogram {
831 size: 4,
832 floor: f64::MIN,
833 initial_step: f64::MAX,
834 step_multiplier: 2.0,
835 counts: vec![1.0, 2.0, 3.0, 4.0],
836 indexes: None,
837 } ;
838 "f64_bounds"
839 )]
840 #[test_case(
847 ExponentialHistogram {
848 size: 4,
849 floor: i64::MIN + 1,
852 initial_step: i64::MAX,
853 step_multiplier: 2i64,
854 counts: vec![1i64, 2i64, 3i64, 4i64],
855 indexes: None,
856 } ;
857 "i64_bounds"
858 )]
859 #[test_case(
865 ExponentialHistogram {
866 size: 3,
867 floor: u64::MIN,
868 initial_step: u64::MAX,
869 step_multiplier: 2u64,
870 counts: vec![0u64, 1u64, 2u64],
872 indexes: None,
873 } ;
874 "u64_bounds"
875 )]
876 #[fuchsia::test]
877 async fn record_exponential_histogram<'a, T>(histogram: ExponentialHistogram<T>)
878 where
879 ArrayContent<T>: InspectData<'a>,
880 ExponentialHistogram<T>: PropertyAssertion + Clone,
881 T: 'static,
882 {
883 let inspector = Inspector::default();
884 ArrayContent::ExponentialHistogram(histogram.clone()).record(inspector.root(), "child");
885 assert_data_tree!(
886 inspector,
887 root: {
888 child: histogram,
889 }
890 );
891 }
892}