1use base64::display::Base64Display;
11use fidl_fuchsia_diagnostics::{
12 PropertySelector, Selector, StringSelector, StringSelectorUnknown, SubtreeSelector,
13 TreeSelector,
14};
15use num_derive::{FromPrimitive, ToPrimitive};
16use num_traits::bounds::Bounded;
17use selectors::ValidateExt;
18use serde::{Deserialize, Serialize};
19use std::borrow::{Borrow, Cow};
20use std::cmp::Ordering;
21use std::collections::BTreeMap;
22use std::fmt::{Display, Formatter, Result as FmtResult};
23use std::ops::{Add, AddAssign, MulAssign};
24use thiserror::Error;
25
26pub mod macros;
27pub mod serialization;
28
29pub const LINEAR_HISTOGRAM_EXTRA_SLOTS: usize = 4;
32
33pub const EXPONENTIAL_HISTOGRAM_EXTRA_SLOTS: usize = 5;
36
37#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
39#[repr(u8)]
40pub enum ArrayFormat {
41 Default = 0,
43
44 LinearHistogram = 1,
51
52 ExponentialHistogram = 2,
60}
61
62#[derive(Clone, Debug, PartialEq)]
67pub struct DiagnosticsHierarchy<Key = String> {
68 pub name: String,
70
71 pub properties: Vec<Property<Key>>,
73
74 pub children: Vec<DiagnosticsHierarchy<Key>>,
76
77 pub missing: Vec<MissingValue>,
79}
80
81#[derive(Clone, Debug, PartialEq)]
83pub struct MissingValue {
84 pub reason: MissingValueReason,
86
87 pub name: String,
89}
90
91#[derive(Clone, Debug, PartialEq)]
93pub enum MissingValueReason {
94 LinkNotFound,
96
97 LinkParseFailure,
99
100 LinkInvalid,
102
103 LinkNeverExpanded,
105
106 Timeout,
108}
109
110fn name_partial_cmp(a: &str, b: &str) -> Ordering {
113 match (a.parse::<u64>(), b.parse::<u64>()) {
114 (Ok(n), Ok(m)) => n.partial_cmp(&m).unwrap(),
115 _ => a.partial_cmp(b).unwrap(),
116 }
117}
118
119impl<Key> DiagnosticsHierarchy<Key>
120where
121 Key: AsRef<str>,
122{
123 pub fn sort(&mut self) {
125 self.properties.sort_by(|p1, p2| name_partial_cmp(p1.name(), p2.name()));
126 self.children.sort_by(|c1, c2| name_partial_cmp(&c1.name, &c2.name));
127 for child in self.children.iter_mut() {
128 child.sort();
129 }
130 }
131
132 pub fn new_root() -> Self {
134 DiagnosticsHierarchy::new("root", vec![], vec![])
135 }
136
137 pub fn new(
140 name: impl Into<String>,
141 properties: Vec<Property<Key>>,
142 children: Vec<DiagnosticsHierarchy<Key>>,
143 ) -> Self {
144 Self { name: name.into(), properties, children, missing: vec![] }
145 }
146
147 pub fn get_or_add_child_mut<T>(&mut self, name: T) -> &mut DiagnosticsHierarchy<Key>
150 where
151 T: AsRef<str>,
152 {
153 match (0..self.children.len()).find(|&i| self.children[i].name == name.as_ref()) {
158 Some(matching_index) => &mut self.children[matching_index],
159 None => {
160 self.children.push(DiagnosticsHierarchy::new(name.as_ref(), vec![], vec![]));
161 self.children
162 .last_mut()
163 .expect("We just added an entry so we cannot get None here.")
164 }
165 }
166 }
167
168 pub fn add_child(&mut self, insert: DiagnosticsHierarchy<Key>) {
173 self.children.push(insert);
174 }
175
176 pub fn get_or_add_node<T>(&mut self, node_path: &[T]) -> &mut DiagnosticsHierarchy<Key>
186 where
187 T: AsRef<str>,
188 {
189 assert!(!node_path.is_empty());
190 let mut iter = node_path.iter();
191 let first_path_string = iter.next().unwrap().as_ref();
192 assert_eq!(first_path_string, &self.name);
195 let mut curr_node = self;
196 for node_path_entry in iter {
197 curr_node = curr_node.get_or_add_child_mut(node_path_entry);
198 }
199 curr_node
200 }
201
202 pub fn add_property(&mut self, property: Property<Key>) {
204 self.properties.push(property);
205 }
206
207 pub fn add_property_at_path<T>(&mut self, node_path: &[T], property: Property<Key>)
217 where
218 T: AsRef<str>,
219 {
220 self.get_or_add_node(node_path).properties.push(property);
221 }
222
223 pub fn property_iter(&self) -> DiagnosticsHierarchyIterator<'_, Key> {
225 DiagnosticsHierarchyIterator::new(self)
226 }
227
228 pub fn add_missing(&mut self, reason: MissingValueReason, name: String) {
230 self.missing.push(MissingValue { reason, name });
231 }
232 pub fn get_property(&self, name: &str) -> Option<&Property<Key>> {
234 self.properties.iter().find(|prop| prop.name() == name)
235 }
236
237 pub fn get_child(&self, name: &str) -> Option<&DiagnosticsHierarchy<Key>> {
239 self.children.iter().find(|node| node.name == name)
240 }
241
242 pub fn get_child_mut(&mut self, name: &str) -> Option<&mut DiagnosticsHierarchy<Key>> {
244 self.children.iter_mut().find(|node| node.name == name)
245 }
246
247 pub fn get_child_by_path(&self, path: &[&str]) -> Option<&DiagnosticsHierarchy<Key>> {
249 let mut result = Some(self);
250 for name in path {
251 result = result.and_then(|node| node.get_child(name));
252 }
253 result
254 }
255
256 pub fn get_child_by_path_mut(
258 &mut self,
259 path: &[&str],
260 ) -> Option<&mut DiagnosticsHierarchy<Key>> {
261 let mut result = Some(self);
262 for name in path {
263 result = result.and_then(|node| node.get_child_mut(name));
264 }
265 result
266 }
267
268 pub fn get_property_by_path(&self, path: &[&str]) -> Option<&Property<Key>> {
270 let node = self.get_child_by_path(&path[..path.len() - 1]);
271 node.and_then(|node| node.get_property(path[path.len() - 1]))
272 }
273}
274
275macro_rules! property_type_getters_ref {
276 ($([$variant:ident, $fn_name:ident, $type:ty]),*) => {
277 paste::item! {
278 impl<Key> Property<Key> {
279 $(
280 #[doc = "Returns the " $variant " value or `None` if the property isn't of that type"]
281 pub fn $fn_name(&self) -> Option<&$type> {
282 match self {
283 Property::$variant(_, value) => Some(value),
284 _ => None,
285 }
286 }
287 )*
288 }
289 }
290 }
291}
292
293macro_rules! property_type_getters_copy {
294 ($([$variant:ident, $fn_name:ident, $type:ty]),*) => {
295 paste::item! {
296 impl<Key> Property<Key> {
297 $(
298 #[doc = "Returns the " $variant " value or `None` if the property isn't of that type"]
299 pub fn $fn_name(&self) -> Option<$type> {
300 match self {
301 Property::$variant(_, value) => Some(*value),
302 _ => None,
303 }
304 }
305 )*
306 }
307 }
308 }
309}
310
311property_type_getters_copy!(
312 [Int, int, i64],
313 [Uint, uint, u64],
314 [Double, double, f64],
315 [Bool, boolean, bool]
316);
317
318property_type_getters_ref!(
319 [String, string, str],
320 [Bytes, bytes, [u8]],
321 [DoubleArray, double_array, ArrayContent<f64>],
322 [IntArray, int_array, ArrayContent<i64>],
323 [UintArray, uint_array, ArrayContent<u64>],
324 [StringList, string_list, [String]]
325);
326
327struct WorkStackEntry<'a, Key> {
328 node: &'a DiagnosticsHierarchy<Key>,
329 key: Vec<&'a str>,
330}
331
332pub struct DiagnosticsHierarchyIterator<'a, Key> {
333 work_stack: Vec<WorkStackEntry<'a, Key>>,
334 current_key: Vec<&'a str>,
335 current_node: Option<&'a DiagnosticsHierarchy<Key>>,
336 current_property_index: usize,
337}
338
339impl<'a, Key> DiagnosticsHierarchyIterator<'a, Key> {
340 fn new(hierarchy: &'a DiagnosticsHierarchy<Key>) -> Self {
342 DiagnosticsHierarchyIterator {
343 work_stack: vec![WorkStackEntry { node: hierarchy, key: vec![&hierarchy.name] }],
344 current_key: vec![],
345 current_node: None,
346 current_property_index: 0,
347 }
348 }
349}
350
351impl<'a, Key> Iterator for DiagnosticsHierarchyIterator<'a, Key> {
352 type Item = (Vec<&'a str>, Option<&'a Property<Key>>);
357
358 fn next(&mut self) -> Option<Self::Item> {
359 loop {
360 let node = match self.current_node {
361 Some(node) => node,
363 None => {
364 let WorkStackEntry { node, key } = self.work_stack.pop()?;
367
368 for child in node.children.iter() {
370 let mut child_key = key.clone();
371 child_key.push(&child.name);
372 self.work_stack.push(WorkStackEntry { node: child, key: child_key })
373 }
374
375 if node.properties.is_empty() {
378 return Some((key.clone(), None));
379 }
380
381 self.current_property_index = 0;
382 self.current_key = key;
383
384 node
385 }
386 };
387
388 if self.current_property_index == node.properties.len() {
390 self.current_node = None;
391 continue;
392 }
393
394 let property = &node.properties[self.current_property_index];
397 self.current_property_index += 1;
398 self.current_node = Some(node);
399
400 return Some((self.current_key.clone(), Some(property)));
401 }
402 }
403}
404
405#[derive(Debug, PartialEq, Clone)]
411pub enum Property<Key = String> {
412 String(Key, String),
414
415 Bytes(Key, Vec<u8>),
417
418 Int(Key, i64),
420
421 Uint(Key, u64),
423
424 Double(Key, f64),
426
427 Bool(Key, bool),
429
430 DoubleArray(Key, ArrayContent<f64>),
432
433 IntArray(Key, ArrayContent<i64>),
435
436 UintArray(Key, ArrayContent<u64>),
438
439 StringList(Key, Vec<String>),
441}
442
443impl<K> Property<K> {
444 pub fn key(&self) -> &K {
446 match self {
447 Property::String(k, _) => k,
448 Property::Bytes(k, _) => k,
449 Property::Int(k, _) => k,
450 Property::Uint(k, _) => k,
451 Property::Double(k, _) => k,
452 Property::Bool(k, _) => k,
453 Property::DoubleArray(k, _) => k,
454 Property::IntArray(k, _) => k,
455 Property::UintArray(k, _) => k,
456 Property::StringList(k, _) => k,
457 }
458 }
459
460 pub fn discriminant_name(&self) -> &'static str {
463 match self {
464 Property::String(_, _) => "String",
465 Property::Bytes(_, _) => "Bytes",
466 Property::Int(_, _) => "Int",
467 Property::IntArray(_, _) => "IntArray",
468 Property::Uint(_, _) => "Uint",
469 Property::UintArray(_, _) => "UintArray",
470 Property::Double(_, _) => "Double",
471 Property::DoubleArray(_, _) => "DoubleArray",
472 Property::Bool(_, _) => "Bool",
473 Property::StringList(_, _) => "StringList",
474 }
475 }
476
477 pub fn number_as_int(&self) -> Option<i64> {
483 match self {
484 Property::Int(_, i) => Some(*i),
485 Property::Uint(_, u) => i64::try_from(*u).ok(),
486 Property::String(..)
487 | Property::Bytes(..)
488 | Property::Double(..)
489 | Property::Bool(..)
490 | Property::DoubleArray(..)
491 | Property::IntArray(..)
492 | Property::UintArray(..)
493 | Property::StringList(..) => None,
494 }
495 }
496}
497
498impl<K> Display for Property<K>
499where
500 K: AsRef<str>,
501{
502 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
503 macro_rules! pair {
504 ($fmt:literal, $val:expr) => {
505 write!(f, "{}={}", self.key().as_ref(), format_args!($fmt, $val))
506 };
507 }
508 match self {
509 Property::String(_, v) => pair!("{}", v),
510 Property::Bytes(_, v) => {
511 pair!("b64:{}", Base64Display::new(v, &base64::engine::general_purpose::STANDARD))
512 }
513 Property::Int(_, v) => pair!("{}", v),
514 Property::Uint(_, v) => pair!("{}", v),
515 Property::Double(_, v) => pair!("{}", v),
516 Property::Bool(_, v) => pair!("{}", v),
517 Property::DoubleArray(_, v) => pair!("{:?}", v),
518 Property::IntArray(_, v) => pair!("{:?}", v),
519 Property::UintArray(_, v) => pair!("{:?}", v),
520 Property::StringList(_, v) => pair!("{:?}", v),
521 }
522 }
523}
524
525#[derive(Debug, Error)]
527pub enum Error {
528 #[error(
529 "Missing elements for {histogram_type:?} histogram. Expected {expected}, got {actual}"
530 )]
531 MissingHistogramElements { histogram_type: ArrayFormat, expected: usize, actual: usize },
532
533 #[error("TreeSelector only supports property and subtree selection.")]
534 InvalidTreeSelector,
535
536 #[error(transparent)]
537 Selectors(#[from] selectors::Error),
538
539 #[error(transparent)]
540 InvalidSelector(#[from] selectors::ValidationError),
541}
542
543impl Error {
544 fn missing_histogram_elements(
545 histogram_type: ArrayFormat,
546 actual: usize,
547 expected: usize,
548 ) -> Self {
549 Self::MissingHistogramElements { histogram_type, actual, expected }
550 }
551}
552
553#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
555#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
556pub struct LinearHistogram<T> {
557 pub size: usize,
559
560 pub floor: T,
562
563 pub step: T,
565
566 pub counts: Vec<T>,
568
569 #[serde(skip_serializing_if = "Option::is_none")]
571 pub indexes: Option<Vec<usize>>,
572}
573
574#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
576#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
577pub struct ExponentialHistogram<T> {
578 pub size: usize,
580
581 pub floor: T,
583
584 pub initial_step: T,
586
587 pub step_multiplier: T,
589
590 pub counts: Vec<T>,
592
593 #[serde(skip_serializing_if = "Option::is_none")]
595 pub indexes: Option<Vec<usize>>,
596}
597
598#[derive(Debug, PartialEq, Clone)]
601pub enum ArrayContent<T> {
602 Values(Vec<T>),
604
605 LinearHistogram(LinearHistogram<T>),
607
608 ExponentialHistogram(ExponentialHistogram<T>),
610}
611
612impl<T> ArrayContent<T>
613where
614 T: Add<Output = T> + num_traits::Zero + AddAssign + Copy + MulAssign + PartialEq + Bounded,
615{
616 pub fn new(values: Vec<T>, format: ArrayFormat) -> Result<Self, Error> {
618 match format {
619 ArrayFormat::Default => Ok(Self::Values(values)),
620 ArrayFormat::LinearHistogram => {
621 if values.len() < 5 {
624 return Err(Error::missing_histogram_elements(
625 ArrayFormat::LinearHistogram,
626 values.len(),
627 5,
628 ));
629 }
630 let original_counts = &values[2..];
631 let (counts, indexes) =
632 match serialization::maybe_condense_histogram(original_counts, &None) {
633 None => (original_counts.to_vec(), None),
634 Some((counts, indexes)) => (counts, Some(indexes)),
635 };
636 Ok(Self::LinearHistogram(LinearHistogram {
637 floor: values[0],
638 step: values[1],
639 counts,
640 indexes,
641 size: values.len() - 2,
642 }))
643 }
644 ArrayFormat::ExponentialHistogram => {
645 if values.len() < 6 {
648 return Err(Error::missing_histogram_elements(
649 ArrayFormat::LinearHistogram,
650 values.len(),
651 5,
652 ));
653 }
654 let original_counts = &values[3..];
655 let (counts, indexes) =
656 match serialization::maybe_condense_histogram(original_counts, &None) {
657 None => (original_counts.to_vec(), None),
658 Some((counts, indexes)) => (counts, Some(indexes)),
659 };
660 Ok(Self::ExponentialHistogram(ExponentialHistogram {
661 floor: values[0],
662 initial_step: values[1],
663 step_multiplier: values[2],
664 counts,
665 indexes,
666 size: values.len() - 3,
667 }))
668 }
669 }
670 }
671
672 pub fn len(&self) -> usize {
674 match self {
675 Self::Values(vals) => vals.len(),
676 Self::LinearHistogram(LinearHistogram { size, .. })
677 | Self::ExponentialHistogram(ExponentialHistogram { size, .. }) => *size,
678 }
679 }
680
681 pub fn is_empty(&self) -> bool {
683 self.len() == 0
684 }
685
686 pub fn raw_values(&self) -> Cow<'_, Vec<T>> {
689 match self {
690 Self::Values(values) => Cow::Borrowed(values),
691 Self::LinearHistogram(LinearHistogram { size, counts, indexes, .. })
692 | Self::ExponentialHistogram(ExponentialHistogram { size, counts, indexes, .. }) => {
693 if let Some(indexes) = indexes {
694 let mut values = vec![T::zero(); *size];
695 for (count, index) in counts.iter().zip(indexes.iter()) {
696 if index <= size {
697 values[*index] = *count;
698 }
699 }
700 Cow::Owned(values)
701 } else {
702 Cow::Borrowed(counts)
703 }
704 }
705 }
706 }
707}
708
709pub mod testing {
710 use crate::ArrayContent;
711 use num_traits::bounds::Bounded;
712 use std::ops::{Add, AddAssign, MulAssign};
713
714 pub trait CondensableOnDemand {
717 fn condense_histogram(&mut self);
718 }
719
720 fn condense_counts<T: num_traits::Zero + Copy + PartialEq>(
721 counts: &[T],
722 ) -> (Vec<T>, Vec<usize>) {
723 let mut condensed_counts = vec![];
724 let mut indexes = vec![];
725 for (index, count) in counts.iter().enumerate() {
726 if *count != T::zero() {
727 condensed_counts.push(*count);
728 indexes.push(index);
729 }
730 }
731 (condensed_counts, indexes)
732 }
733
734 impl<T> CondensableOnDemand for ArrayContent<T>
735 where
736 T: Add<Output = T> + num_traits::Zero + AddAssign + Copy + MulAssign + PartialEq + Bounded,
737 {
738 fn condense_histogram(&mut self) {
739 match self {
740 Self::Values(_) => (),
741 Self::LinearHistogram(histogram) => {
742 if histogram.indexes.is_some() {
743 return;
744 }
745 let (counts, indexes) = condense_counts(&histogram.counts);
746 histogram.counts = counts;
747 histogram.indexes = Some(indexes);
748 }
749 Self::ExponentialHistogram(histogram) => {
750 if histogram.indexes.is_some() {
751 return;
752 }
753 let (counts, indexes) = condense_counts(&histogram.counts);
754 histogram.counts = counts;
755 histogram.indexes = Some(indexes);
756 }
757 }
758 }
759 }
760}
761
762impl<Key> Property<Key>
763where
764 Key: AsRef<str>,
765{
766 pub fn name(&self) -> &str {
768 match self {
769 Property::String(name, _)
770 | Property::Bytes(name, _)
771 | Property::Int(name, _)
772 | Property::IntArray(name, _)
773 | Property::Uint(name, _)
774 | Property::UintArray(name, _)
775 | Property::Double(name, _)
776 | Property::Bool(name, _)
777 | Property::DoubleArray(name, _)
778 | Property::StringList(name, _) => name.as_ref(),
779 }
780 }
781}
782
783impl<T: Borrow<Selector>> TryFrom<&[T]> for HierarchyMatcher {
784 type Error = Error;
785
786 fn try_from(selectors: &[T]) -> Result<Self, Self::Error> {
787 let mut matcher = HierarchyMatcher::default();
790 for selector in selectors {
791 let selector = selector.borrow();
792 selector.validate().map_err(|e| Error::Selectors(e.into()))?;
793
794 match selector.tree_selector.clone().unwrap() {
798 TreeSelector::SubtreeSelector(subtree_selector) => {
799 matcher.insert_subtree(subtree_selector.clone());
800 }
801 TreeSelector::PropertySelector(property_selector) => {
802 matcher.insert_property(property_selector.clone());
803 }
804 _ => return Err(Error::Selectors(selectors::Error::InvalidTreeSelector)),
805 }
806 }
807 Ok(matcher)
808 }
809}
810
811impl<T: Borrow<Selector>> TryFrom<Vec<T>> for HierarchyMatcher {
812 type Error = Error;
813
814 fn try_from(selectors: Vec<T>) -> Result<Self, Self::Error> {
815 selectors[..].try_into()
816 }
817}
818
819#[derive(Debug)]
820struct OrdStringSelector(StringSelector);
821
822impl From<StringSelector> for OrdStringSelector {
823 fn from(selector: StringSelector) -> Self {
824 Self(selector)
825 }
826}
827
828impl Ord for OrdStringSelector {
829 fn cmp(&self, other: &OrdStringSelector) -> Ordering {
830 match (&self.0, &other.0) {
831 (StringSelector::ExactMatch(s), StringSelector::ExactMatch(o)) => s.cmp(o),
832 (StringSelector::StringPattern(s), StringSelector::StringPattern(o)) => s.cmp(o),
833 (StringSelector::ExactMatch(_), StringSelector::StringPattern(_)) => Ordering::Less,
834 (StringSelector::StringPattern(_), StringSelector::ExactMatch(_)) => Ordering::Greater,
835 (StringSelectorUnknown!(), StringSelector::ExactMatch(_)) => Ordering::Less,
836 (StringSelectorUnknown!(), StringSelector::StringPattern(_)) => Ordering::Less,
837 (StringSelectorUnknown!(), StringSelectorUnknown!()) => Ordering::Equal,
838 }
839 }
840}
841
842impl PartialOrd for OrdStringSelector {
843 fn partial_cmp(&self, other: &OrdStringSelector) -> Option<Ordering> {
844 Some(self.cmp(other))
845 }
846}
847
848impl PartialEq for OrdStringSelector {
849 fn eq(&self, other: &OrdStringSelector) -> bool {
850 match (&self.0, &other.0) {
851 (StringSelector::ExactMatch(s), StringSelector::ExactMatch(o)) => s.eq(o),
852 (StringSelector::StringPattern(s), StringSelector::StringPattern(o)) => s.eq(o),
853 (StringSelector::ExactMatch(_), StringSelector::StringPattern(_)) => false,
854 (StringSelector::StringPattern(_), StringSelector::ExactMatch(_)) => false,
855 (StringSelectorUnknown!(), StringSelectorUnknown!()) => true,
856 }
857 }
858}
859
860impl Eq for OrdStringSelector {}
861
862#[derive(Default, Debug)]
863pub struct HierarchyMatcher {
864 nodes: BTreeMap<OrdStringSelector, HierarchyMatcher>,
865 properties: Vec<OrdStringSelector>,
866 subtree: bool,
867}
868
869impl HierarchyMatcher {
870 pub fn new<I>(selectors: I) -> Result<Self, Error>
871 where
872 I: Iterator<Item = Selector>,
873 {
874 let mut matcher = HierarchyMatcher::default();
875 for selector in selectors {
876 selector.validate().map_err(|e| Error::Selectors(e.into()))?;
877
878 match selector.tree_selector.unwrap() {
880 TreeSelector::SubtreeSelector(subtree_selector) => {
881 matcher.insert_subtree(subtree_selector);
882 }
883 TreeSelector::PropertySelector(property_selector) => {
884 matcher.insert_property(property_selector);
885 }
886 _ => return Err(Error::Selectors(selectors::Error::InvalidTreeSelector)),
887 }
888 }
889 Ok(matcher)
890 }
891
892 fn insert_subtree(&mut self, selector: SubtreeSelector) {
893 self.insert(selector.node_path, None);
894 }
895
896 fn insert_property(&mut self, selector: PropertySelector) {
897 self.insert(selector.node_path, Some(selector.target_properties));
898 }
899
900 fn insert(&mut self, node_path: Vec<StringSelector>, property: Option<StringSelector>) {
901 let mut matcher = self;
905 for node in node_path {
906 matcher = matcher.nodes.entry(node.into()).or_default();
907 }
908 match property {
909 Some(property) => {
910 matcher.properties.push(property.into());
911 }
912 None => matcher.subtree = true,
913 }
914 }
915}
916
917#[derive(Debug, PartialEq)]
918pub enum SelectResult<'a, Key> {
919 Properties(Vec<&'a Property<Key>>),
920 Nodes(Vec<&'a DiagnosticsHierarchy<Key>>),
921}
922
923impl<'a, Key> SelectResult<'a, Key> {
924 fn add_property(&mut self, prop: &'a Property<Key>) {
926 let Self::Properties(v) = self else {
927 panic!("must be Self::Properties to call add_property");
928 };
929 v.push(prop);
930 }
931
932 fn add_node(&mut self, node: &'a DiagnosticsHierarchy<Key>) {
934 let Self::Nodes(v) = self else {
935 panic!("must be Self::Nodes to call add_node");
936 };
937 v.push(node);
938 }
939}
940
941pub fn select_from_hierarchy<'a, 'b, Key>(
942 root_node: &'a DiagnosticsHierarchy<Key>,
943 selector: &'b Selector,
944) -> Result<SelectResult<'a, Key>, Error>
945where
946 Key: AsRef<str>,
947 'a: 'b,
948{
949 selector.validate()?;
950
951 struct StackEntry<'a, Key> {
952 node: &'a DiagnosticsHierarchy<Key>,
953 node_path_index: usize,
954 explored_path: Vec<&'a str>,
955 }
956
957 let (node_path, property_selector, stack_entry) = match selector.tree_selector.as_ref().unwrap()
959 {
960 TreeSelector::SubtreeSelector(ref subtree_selector) => (
961 &subtree_selector.node_path,
962 None,
963 StackEntry { node: root_node, node_path_index: 0, explored_path: vec![] },
964 ),
965 TreeSelector::PropertySelector(ref property_selector) => (
966 &property_selector.node_path,
967 Some(&property_selector.target_properties),
968 StackEntry { node: root_node, node_path_index: 0, explored_path: vec![] },
969 ),
970 _ => return Err(Error::InvalidTreeSelector),
971 };
972
973 let mut stack = vec![stack_entry];
974 let mut result = if property_selector.is_some() {
975 SelectResult::Properties(vec![])
976 } else {
977 SelectResult::Nodes(vec![])
978 };
979
980 while let Some(StackEntry { node, node_path_index, mut explored_path }) = stack.pop() {
981 if !selectors::match_string(&node_path[node_path_index], &node.name) {
983 continue;
984 }
985 explored_path.push(&node.name);
986
987 if node_path_index != node_path.len() - 1 {
990 for child in node.children.iter() {
992 stack.push(StackEntry {
993 node: child,
994 node_path_index: node_path_index + 1,
995 explored_path: explored_path.clone(),
996 });
997 }
998 } else if let Some(s) = property_selector {
999 for property in &node.properties {
1001 if selectors::match_string(s, property.key()) {
1002 result.add_property(property);
1003 }
1004 }
1005 } else {
1006 result.add_node(node);
1009 }
1010 }
1011
1012 Ok(result)
1013}
1014
1015pub fn filter_tree<Key>(
1017 root_node: DiagnosticsHierarchy<Key>,
1018 selectors: &[TreeSelector],
1019) -> Option<DiagnosticsHierarchy<Key>>
1020where
1021 Key: AsRef<str>,
1022{
1023 let mut matcher = HierarchyMatcher::default();
1024 for selector in selectors {
1025 match selector {
1026 TreeSelector::SubtreeSelector(subtree_selector) => {
1027 matcher.insert_subtree(subtree_selector.clone());
1028 }
1029 TreeSelector::PropertySelector(property_selector) => {
1030 matcher.insert_property(property_selector.clone());
1031 }
1032 _ => {}
1033 }
1034 }
1035 filter_hierarchy(root_node, &matcher)
1036}
1037
1038pub fn filter_hierarchy<Key>(
1044 mut root_node: DiagnosticsHierarchy<Key>,
1045 hierarchy_matcher: &HierarchyMatcher,
1046) -> Option<DiagnosticsHierarchy<Key>>
1047where
1048 Key: AsRef<str>,
1049{
1050 let starts_empty = root_node.children.is_empty() && root_node.properties.is_empty();
1051 if filter_hierarchy_helper(&mut root_node, &[hierarchy_matcher]) {
1052 if !starts_empty && root_node.children.is_empty() && root_node.properties.is_empty() {
1053 return None;
1054 }
1055 return Some(root_node);
1056 }
1057 None
1058}
1059
1060fn filter_hierarchy_helper<Key>(
1061 node: &mut DiagnosticsHierarchy<Key>,
1062 hierarchy_matchers: &[&HierarchyMatcher],
1063) -> bool
1064where
1065 Key: AsRef<str>,
1066{
1067 let child_matchers = eval_matchers_on_node_name(&node.name, hierarchy_matchers);
1068 if child_matchers.is_empty() {
1069 node.children.clear();
1070 node.properties.clear();
1071 return false;
1072 }
1073
1074 if child_matchers.iter().any(|m| m.subtree) {
1075 return true;
1076 }
1077
1078 node.children.retain_mut(|child| filter_hierarchy_helper(child, &child_matchers));
1079 node.properties.retain_mut(|prop| eval_matchers_on_property(prop.name(), &child_matchers));
1080
1081 !(node.children.is_empty() && node.properties.is_empty())
1082}
1083
1084fn eval_matchers_on_node_name<'a>(
1085 node_name: &'a str,
1086 matchers: &'a [&'a HierarchyMatcher],
1087) -> Vec<&'a HierarchyMatcher> {
1088 let mut result = vec![];
1089 for matcher in matchers {
1090 for (node_pattern, tree_matcher) in matcher.nodes.iter() {
1091 if selectors::match_string(&node_pattern.0, node_name) {
1092 result.push(tree_matcher);
1093 }
1094 }
1095 }
1096 result
1097}
1098
1099fn eval_matchers_on_property(property_name: &str, matchers: &[&HierarchyMatcher]) -> bool {
1100 matchers.iter().any(|matcher| {
1101 matcher
1102 .properties
1103 .iter()
1104 .any(|property_pattern| selectors::match_string(&property_pattern.0, property_name))
1105 })
1106}
1107
1108#[derive(Clone)]
1110pub struct ExponentialHistogramParams<T: Clone> {
1111 pub floor: T,
1113
1114 pub initial_step: T,
1116
1117 pub step_multiplier: T,
1119
1120 pub buckets: usize,
1123}
1124
1125#[derive(Clone)]
1127pub struct LinearHistogramParams<T: Clone> {
1128 pub floor: T,
1130
1131 pub step_size: T,
1133
1134 pub buckets: usize,
1137}
1138
1139pub trait DiagnosticsHierarchyGetter<K: Clone> {
1142 fn get_diagnostics_hierarchy(&self) -> Cow<'_, DiagnosticsHierarchy<K>>;
1143}
1144
1145impl<K: Clone> DiagnosticsHierarchyGetter<K> for DiagnosticsHierarchy<K> {
1146 fn get_diagnostics_hierarchy(&self) -> Cow<'_, DiagnosticsHierarchy<K>> {
1147 Cow::Borrowed(self)
1148 }
1149}
1150
1151#[cfg(test)]
1152mod tests {
1153 use super::*;
1154 use crate::testing::CondensableOnDemand;
1155 use test_case::test_case;
1156
1157 use assert_matches::assert_matches;
1158 use selectors::VerboseError;
1159 use std::sync::Arc;
1160
1161 fn validate_hierarchy_iteration(
1162 mut results_vec: Vec<(Vec<String>, Option<Property>)>,
1163 test_hierarchy: DiagnosticsHierarchy,
1164 ) {
1165 let expected_num_entries = results_vec.len();
1166 let mut num_entries = 0;
1167 for (key, val) in test_hierarchy.property_iter() {
1168 num_entries += 1;
1169 let (expected_key, expected_property) = results_vec.pop().unwrap();
1170 assert_eq!(key.to_vec().join("/"), expected_key.to_vec().join("/"));
1171 assert_eq!(val, expected_property.as_ref());
1172 }
1173
1174 assert_eq!(num_entries, expected_num_entries);
1175 }
1176
1177 #[fuchsia::test]
1178 fn test_diagnostics_hierarchy_iteration() {
1179 let double_array_data = vec![-1.2, 2.3, 3.4, 4.5, -5.6];
1180 let chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
1181 let string_data = chars.iter().cycle().take(6000).collect::<String>();
1182 let bytes_data = (0u8..=9u8).cycle().take(5000).collect::<Vec<u8>>();
1183
1184 let test_hierarchy = DiagnosticsHierarchy::new(
1185 "root".to_string(),
1186 vec![
1187 Property::Int("int-root".to_string(), 3),
1188 Property::DoubleArray(
1189 "property-double-array".to_string(),
1190 ArrayContent::Values(double_array_data.clone()),
1191 ),
1192 ],
1193 vec![DiagnosticsHierarchy::new(
1194 "child-1".to_string(),
1195 vec![
1196 Property::Uint("property-uint".to_string(), 10),
1197 Property::Double("property-double".to_string(), -3.4),
1198 Property::String("property-string".to_string(), string_data.clone()),
1199 Property::IntArray(
1200 "property-int-array".to_string(),
1201 ArrayContent::new(vec![1, 2, 1, 1, 1, 1, 1], ArrayFormat::LinearHistogram)
1202 .unwrap(),
1203 ),
1204 ],
1205 vec![DiagnosticsHierarchy::new(
1206 "child-1-1".to_string(),
1207 vec![
1208 Property::Int("property-int".to_string(), -9),
1209 Property::Bytes("property-bytes".to_string(), bytes_data.clone()),
1210 Property::UintArray(
1211 "property-uint-array".to_string(),
1212 ArrayContent::new(
1213 vec![1, 1, 2, 0, 1, 1, 2, 0, 0],
1214 ArrayFormat::ExponentialHistogram,
1215 )
1216 .unwrap(),
1217 ),
1218 ],
1219 vec![],
1220 )],
1221 )],
1222 );
1223
1224 let results_vec = vec![
1225 (
1226 vec!["root".to_string(), "child-1".to_string(), "child-1-1".to_string()],
1227 Some(Property::UintArray(
1228 "property-uint-array".to_string(),
1229 ArrayContent::new(
1230 vec![1, 1, 2, 0, 1, 1, 2, 0, 0],
1231 ArrayFormat::ExponentialHistogram,
1232 )
1233 .unwrap(),
1234 )),
1235 ),
1236 (
1237 vec!["root".to_string(), "child-1".to_string(), "child-1-1".to_string()],
1238 Some(Property::Bytes("property-bytes".to_string(), bytes_data)),
1239 ),
1240 (
1241 vec!["root".to_string(), "child-1".to_string(), "child-1-1".to_string()],
1242 Some(Property::Int("property-int".to_string(), -9)),
1243 ),
1244 (
1245 vec!["root".to_string(), "child-1".to_string()],
1246 Some(Property::IntArray(
1247 "property-int-array".to_string(),
1248 ArrayContent::new(vec![1, 2, 1, 1, 1, 1, 1], ArrayFormat::LinearHistogram)
1249 .unwrap(),
1250 )),
1251 ),
1252 (
1253 vec!["root".to_string(), "child-1".to_string()],
1254 Some(Property::String("property-string".to_string(), string_data)),
1255 ),
1256 (
1257 vec!["root".to_string(), "child-1".to_string()],
1258 Some(Property::Double("property-double".to_string(), -3.4)),
1259 ),
1260 (
1261 vec!["root".to_string(), "child-1".to_string()],
1262 Some(Property::Uint("property-uint".to_string(), 10)),
1263 ),
1264 (
1265 vec!["root".to_string()],
1266 Some(Property::DoubleArray(
1267 "property-double-array".to_string(),
1268 ArrayContent::Values(double_array_data),
1269 )),
1270 ),
1271 (vec!["root".to_string()], Some(Property::Int("int-root".to_string(), 3))),
1272 ];
1273
1274 validate_hierarchy_iteration(results_vec, test_hierarchy);
1275 }
1276
1277 #[fuchsia::test]
1278 fn test_getters() {
1279 let a_prop = Property::Int("a".to_string(), 1);
1280 let b_prop = Property::Uint("b".to_string(), 2);
1281 let child2 = DiagnosticsHierarchy::new("child2".to_string(), vec![], vec![]);
1282 let child = DiagnosticsHierarchy::new(
1283 "child".to_string(),
1284 vec![b_prop.clone()],
1285 vec![child2.clone()],
1286 );
1287 let mut hierarchy = DiagnosticsHierarchy::new(
1288 "root".to_string(),
1289 vec![a_prop.clone()],
1290 vec![child.clone()],
1291 );
1292 assert_matches!(hierarchy.get_child("child"), Some(node) if *node == child);
1293 assert_matches!(hierarchy.get_child_mut("child"), Some(node) if *node == child);
1294 assert_matches!(hierarchy.get_child_by_path(&["child", "child2"]),
1295 Some(node) if *node == child2);
1296 assert_matches!(hierarchy.get_child_by_path_mut(&["child", "child2"]),
1297 Some(node) if *node == child2);
1298 assert_matches!(hierarchy.get_property("a"), Some(prop) if *prop == a_prop);
1299 assert_matches!(hierarchy.get_property_by_path(&["child", "b"]),
1300 Some(prop) if *prop == b_prop);
1301 }
1302
1303 #[fuchsia::test]
1304 fn test_edge_case_hierarchy_iteration() {
1305 let root_only_with_one_property_hierarchy = DiagnosticsHierarchy::new(
1306 "root".to_string(),
1307 vec![Property::Int("property-int".to_string(), -9)],
1308 vec![],
1309 );
1310
1311 let results_vec =
1312 vec![(vec!["root".to_string()], Some(Property::Int("property-int".to_string(), -9)))];
1313
1314 validate_hierarchy_iteration(results_vec, root_only_with_one_property_hierarchy);
1315
1316 let empty_hierarchy = DiagnosticsHierarchy::new("root".to_string(), vec![], vec![]);
1317
1318 let results_vec = vec![(vec!["root".to_string()], None)];
1319
1320 validate_hierarchy_iteration(results_vec, empty_hierarchy);
1321
1322 let empty_root_populated_child = DiagnosticsHierarchy::new(
1323 "root",
1324 vec![],
1325 vec![DiagnosticsHierarchy::new(
1326 "foo",
1327 vec![Property::Int("11".to_string(), -4)],
1328 vec![],
1329 )],
1330 );
1331
1332 let results_vec = vec![
1333 (
1334 vec!["root".to_string(), "foo".to_string()],
1335 Some(Property::Int("11".to_string(), -4)),
1336 ),
1337 (vec!["root".to_string()], None),
1338 ];
1339
1340 validate_hierarchy_iteration(results_vec, empty_root_populated_child);
1341
1342 let empty_root_empty_child = DiagnosticsHierarchy::new(
1343 "root",
1344 vec![],
1345 vec![DiagnosticsHierarchy::new("foo", vec![], vec![])],
1346 );
1347
1348 let results_vec = vec![
1349 (vec!["root".to_string(), "foo".to_string()], None),
1350 (vec!["root".to_string()], None),
1351 ];
1352
1353 validate_hierarchy_iteration(results_vec, empty_root_empty_child);
1354 }
1355
1356 #[fuchsia::test]
1357 fn array_value() {
1358 let values = vec![1, 2, 5, 7, 9, 11, 13];
1359 let array = ArrayContent::<u64>::new(values.clone(), ArrayFormat::Default);
1360 assert_matches!(array, Ok(ArrayContent::Values(vals)) if vals == values);
1361 }
1362
1363 #[fuchsia::test]
1364 fn linear_histogram_array_value() {
1365 let values = vec![1, 2, 5, 7, 9, 11, 13];
1366 let array = ArrayContent::<i64>::new(values, ArrayFormat::LinearHistogram);
1367 assert_matches!(array, Ok(ArrayContent::LinearHistogram(hist))
1368 if hist == LinearHistogram {
1369 floor: 1,
1370 step: 2,
1371 counts: vec![5, 7, 9, 11, 13],
1372 indexes: None,
1373 size: 5,
1374 }
1375 );
1376 }
1377
1378 #[fuchsia::test]
1379 fn exponential_histogram_array_value() {
1380 let values = vec![1.0, 2.0, 5.0, 7.0, 9.0, 11.0, 15.0];
1381 let array = ArrayContent::<f64>::new(values, ArrayFormat::ExponentialHistogram);
1382 assert_matches!(array, Ok(ArrayContent::ExponentialHistogram(hist))
1383 if hist == ExponentialHistogram {
1384 floor: 1.0,
1385 initial_step: 2.0,
1386 step_multiplier: 5.0,
1387 counts: vec![7.0, 9.0, 11.0, 15.0],
1388 indexes: None,
1389 size: 4,
1390 }
1391 );
1392 }
1393
1394 #[fuchsia::test]
1395 fn deserialize_linear_int_histogram() -> Result<(), serde_json::Error> {
1396 let json = r#"{
1397 "root": {
1398 "histogram": {
1399 "floor": -2,
1400 "step": 3,
1401 "counts": [4, 5, 6],
1402 "size": 3
1403 }
1404 }
1405 }"#;
1406 let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1407 let expected = DiagnosticsHierarchy::new(
1408 "root".to_string(),
1409 vec![Property::IntArray(
1410 "histogram".to_string(),
1411 ArrayContent::new(vec![-2, 3, 4, 5, 6], ArrayFormat::LinearHistogram).unwrap(),
1412 )],
1413 vec![],
1414 );
1415 assert_eq!(parsed, expected);
1416 Ok(())
1417 }
1418
1419 #[fuchsia::test]
1420 fn deserialize_exponential_int_histogram() -> Result<(), serde_json::Error> {
1421 let json = r#"{
1422 "root": {
1423 "histogram": {
1424 "floor": 1,
1425 "initial_step": 3,
1426 "step_multiplier": 2,
1427 "counts": [4, 5, 6],
1428 "size": 3
1429 }
1430 }
1431 }"#;
1432 let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1433 let expected = DiagnosticsHierarchy::new(
1434 "root".to_string(),
1435 vec![Property::IntArray(
1436 "histogram".to_string(),
1437 ArrayContent::new(vec![1, 3, 2, 4, 5, 6], ArrayFormat::ExponentialHistogram)
1438 .unwrap(),
1439 )],
1440 vec![],
1441 );
1442 assert_eq!(parsed, expected);
1443 Ok(())
1444 }
1445
1446 #[fuchsia::test]
1447 fn deserialize_linear_uint_histogram() -> Result<(), serde_json::Error> {
1448 let json = r#"{
1449 "root": {
1450 "histogram": {
1451 "floor": 2,
1452 "step": 3,
1453 "counts": [4, 9223372036854775808, 6],
1454 "size": 3
1455 }
1456 }
1457 }"#;
1458 let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1459 let expected = DiagnosticsHierarchy::new(
1460 "root".to_string(),
1461 vec![Property::UintArray(
1462 "histogram".to_string(),
1463 ArrayContent::new(
1464 vec![2, 3, 4, 9_223_372_036_854_775_808, 6],
1465 ArrayFormat::LinearHistogram,
1466 )
1467 .unwrap(),
1468 )],
1469 vec![],
1470 );
1471 assert_eq!(parsed, expected);
1472 Ok(())
1473 }
1474
1475 #[fuchsia::test]
1476 fn deserialize_linear_double_histogram() -> Result<(), serde_json::Error> {
1477 let json = r#"{
1478 "root": {
1479 "histogram": {
1480 "floor": 2.0,
1481 "step": 3.0,
1482 "counts": [4.0, 5.0, 6.0],
1483 "size": 3
1484 }
1485 }
1486 }"#;
1487 let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1488 let expected = DiagnosticsHierarchy::new(
1489 "root".to_string(),
1490 vec![Property::DoubleArray(
1491 "histogram".to_string(),
1492 ArrayContent::new(vec![2.0, 3.0, 4.0, 5.0, 6.0], ArrayFormat::LinearHistogram)
1493 .unwrap(),
1494 )],
1495 vec![],
1496 );
1497 assert_eq!(parsed, expected);
1498 Ok(())
1499 }
1500
1501 #[fuchsia::test]
1502 fn deserialize_sparse_histogram() -> Result<(), serde_json::Error> {
1503 let json = r#"{
1504 "root": {
1505 "histogram": {
1506 "floor": 2,
1507 "step": 3,
1508 "counts": [4, 5, 6],
1509 "indexes": [1, 2, 4],
1510 "size": 8
1511 }
1512 }
1513 }"#;
1514 let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1515
1516 let mut histogram =
1517 ArrayContent::new(vec![2, 3, 0, 4, 5, 0, 6, 0, 0, 0], ArrayFormat::LinearHistogram)
1518 .unwrap();
1519 histogram.condense_histogram();
1520 let expected = DiagnosticsHierarchy::new(
1521 "root".to_string(),
1522 vec![Property::IntArray("histogram".to_string(), histogram)],
1523 vec![],
1524 );
1525 assert_eq!(parsed, expected);
1526 Ok(())
1527 }
1528
1529 #[fuchsia::test]
1534 fn reject_histogram_incompatible_values() -> Result<(), serde_json::Error> {
1535 let json = r#"{
1536 "root": {
1537 "histogram": {
1538 "floor": -2,
1539 "step": 3,
1540 "counts": [4, 9223372036854775808, 6],
1541 "size": 3
1542 }
1543 }
1544 }"#;
1545 let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1546 assert_eq!(parsed.children.len(), 1);
1547 assert_eq!(&parsed.children[0].name, "histogram");
1548 Ok(())
1549 }
1550
1551 #[fuchsia::test]
1552 fn reject_histogram_bad_sparse_list() -> Result<(), serde_json::Error> {
1553 let json = r#"{
1554 "root": {
1555 "histogram": {
1556 "floor": -2,
1557 "step": 3,
1558 "counts": [4, 5, 6],
1559 "indexes": [0, 1, 2, 3],
1560 "size": 8
1561 }
1562 }
1563 }"#;
1564 let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1565 assert_eq!(parsed.children.len(), 1);
1566 assert_eq!(&parsed.children[0].name, "histogram");
1567 Ok(())
1568 }
1569
1570 #[fuchsia::test]
1571 fn reject_histogram_bad_index() -> Result<(), serde_json::Error> {
1572 let json = r#"{
1573 "root": {
1574 "histogram": {
1575 "floor": -2,
1576 "step": 3,
1577 "counts": [4, 5, 6],
1578 "indexes": [0, 1, 4],
1579 "size": 4
1580 }
1581 }
1582 }"#;
1583 let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1584 assert_eq!(parsed.children.len(), 1);
1585 assert_eq!(&parsed.children[0].name, "histogram");
1586 Ok(())
1587 }
1588
1589 #[fuchsia::test]
1590 fn reject_histogram_wrong_field() -> Result<(), serde_json::Error> {
1591 let json = r#"{
1592 "root": {
1593 "histogram": {
1594 "floor": 2,
1595 "step": 3,
1596 "counts": [4, 5, 6],
1597 "incorrect": [0, 1, 3],
1598 "size": 4
1599 }
1600 }
1601 }"#;
1602 let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1603 assert_eq!(parsed.children.len(), 1);
1604 assert_eq!(&parsed.children[0].name, "histogram");
1605 Ok(())
1606 }
1607
1608 #[fuchsia::test]
1609 fn exponential_histogram() {
1610 let values = vec![0, 2, 4, 0, 1, 2, 3, 4, 5];
1611 let array = ArrayContent::new(values, ArrayFormat::ExponentialHistogram);
1612 assert_matches!(array, Ok(ArrayContent::ExponentialHistogram(hist))
1613 if hist == ExponentialHistogram {
1614 floor: 0,
1615 initial_step: 2,
1616 step_multiplier: 4,
1617 counts: vec![0, 1, 2, 3, 4, 5],
1618 indexes: None,
1619 size: 6,
1620 }
1621 );
1622 }
1623
1624 #[fuchsia::test]
1625 fn add_to_hierarchy() {
1626 let mut hierarchy = DiagnosticsHierarchy::new_root();
1627 let prop_1 = Property::String("x".to_string(), "foo".to_string());
1628 let path_1 = vec!["root", "one"];
1629 let prop_2 = Property::Uint("c".to_string(), 3);
1630 let path_2 = vec!["root", "two"];
1631 let prop_2_prime = Property::Int("z".to_string(), -4);
1632 hierarchy.add_property_at_path(&path_1, prop_1.clone());
1633 hierarchy.add_property_at_path(&path_2.clone(), prop_2.clone());
1634 hierarchy.add_property_at_path(&path_2, prop_2_prime.clone());
1635
1636 assert_eq!(
1637 hierarchy,
1638 DiagnosticsHierarchy {
1639 name: "root".to_string(),
1640 children: vec![
1641 DiagnosticsHierarchy {
1642 name: "one".to_string(),
1643 properties: vec![prop_1],
1644 children: vec![],
1645 missing: vec![],
1646 },
1647 DiagnosticsHierarchy {
1648 name: "two".to_string(),
1649 properties: vec![prop_2, prop_2_prime],
1650 children: vec![],
1651 missing: vec![],
1652 }
1653 ],
1654 properties: vec![],
1655 missing: vec![],
1656 }
1657 );
1658 }
1659
1660 #[fuchsia::test]
1661 fn string_lists() {
1662 let mut hierarchy = DiagnosticsHierarchy::new_root();
1663 let prop_1 =
1664 Property::StringList("x".to_string(), vec!["foo".to_string(), "bar".to_string()]);
1665 let path_1 = vec!["root", "one"];
1666 hierarchy.add_property_at_path(&path_1, prop_1.clone());
1667
1668 assert_eq!(
1669 hierarchy,
1670 DiagnosticsHierarchy {
1671 name: "root".to_string(),
1672 children: vec![DiagnosticsHierarchy {
1673 name: "one".to_string(),
1674 properties: vec![prop_1],
1675 children: vec![],
1676 missing: vec![],
1677 },],
1678 properties: vec![],
1679 missing: vec![],
1680 }
1681 );
1682 }
1683
1684 #[fuchsia::test]
1685 #[cfg_attr(feature = "variant_asan", ignore)]
1687 #[cfg_attr(feature = "variant_hwasan", ignore)]
1688 #[should_panic]
1689 fn no_empty_paths_allowed() {
1691 let mut hierarchy = DiagnosticsHierarchy::<String>::new_root();
1692 let path_1: Vec<&String> = vec![];
1693 hierarchy.get_or_add_node(&path_1);
1694 }
1695
1696 #[fuchsia::test]
1697 #[should_panic]
1698 fn path_must_start_at_self() {
1701 let mut hierarchy = DiagnosticsHierarchy::<String>::new_root();
1702 let path_1 = vec!["not_root", "a"];
1703 hierarchy.get_or_add_node(&path_1);
1704 }
1705
1706 #[fuchsia::test]
1707 fn sort_hierarchy() {
1708 let mut hierarchy = DiagnosticsHierarchy::new(
1709 "root",
1710 vec![
1711 Property::String("x".to_string(), "foo".to_string()),
1712 Property::Uint("c".to_string(), 3),
1713 Property::Int("z".to_string(), -4),
1714 ],
1715 vec![
1716 DiagnosticsHierarchy::new(
1717 "foo",
1718 vec![
1719 Property::Int("11".to_string(), -4),
1720 Property::Bytes("123".to_string(), "foo".bytes().collect()),
1721 Property::Double("0".to_string(), 8.1),
1722 ],
1723 vec![],
1724 ),
1725 DiagnosticsHierarchy::new("bar", vec![], vec![]),
1726 ],
1727 );
1728
1729 hierarchy.sort();
1730
1731 let sorted_hierarchy = DiagnosticsHierarchy::new(
1732 "root",
1733 vec![
1734 Property::Uint("c".to_string(), 3),
1735 Property::String("x".to_string(), "foo".to_string()),
1736 Property::Int("z".to_string(), -4),
1737 ],
1738 vec![
1739 DiagnosticsHierarchy::new("bar", vec![], vec![]),
1740 DiagnosticsHierarchy::new(
1741 "foo",
1742 vec![
1743 Property::Double("0".to_string(), 8.1),
1744 Property::Int("11".to_string(), -4),
1745 Property::Bytes("123".to_string(), "foo".bytes().collect()),
1746 ],
1747 vec![],
1748 ),
1749 ],
1750 );
1751 assert_eq!(sorted_hierarchy, hierarchy);
1752 }
1753
1754 fn parse_selectors_and_filter_hierarchy(
1755 hierarchy: DiagnosticsHierarchy,
1756 test_selectors: Vec<&str>,
1757 ) -> Option<DiagnosticsHierarchy> {
1758 let parsed_test_selectors = test_selectors
1759 .into_iter()
1760 .map(|selector_string| {
1761 Arc::new(
1762 selectors::parse_selector::<VerboseError>(selector_string)
1763 .expect("All test selectors are valid and parsable."),
1764 )
1765 })
1766 .collect::<Vec<Arc<Selector>>>();
1767
1768 let hierarchy_matcher: HierarchyMatcher = parsed_test_selectors.try_into().unwrap();
1769
1770 filter_hierarchy(hierarchy, &hierarchy_matcher).map(|mut hierarchy| {
1771 hierarchy.sort();
1772 hierarchy
1773 })
1774 }
1775
1776 fn get_test_hierarchy() -> DiagnosticsHierarchy {
1777 DiagnosticsHierarchy::new(
1778 "root",
1779 vec![
1780 Property::String("x".to_string(), "foo".to_string()),
1781 Property::Uint("c".to_string(), 3),
1782 Property::Int("z".to_string(), -4),
1783 ],
1784 vec![
1785 make_foo(),
1786 DiagnosticsHierarchy::new(
1787 "bar",
1788 vec![Property::Int("12".to_string(), -4)],
1789 vec![DiagnosticsHierarchy::new(
1790 "zed",
1791 vec![Property::Int("13/:".to_string(), -4)],
1792 vec![],
1793 )],
1794 ),
1795 ],
1796 )
1797 }
1798
1799 fn make_all_foo_props() -> Vec<Property> {
1800 vec![
1801 Property::Int("11".to_string(), -4),
1802 Property::Bytes("123".to_string(), b"foo".to_vec()),
1803 Property::Double("0".to_string(), 8.1),
1804 ]
1805 }
1806
1807 fn make_zed() -> Vec<DiagnosticsHierarchy> {
1808 vec![DiagnosticsHierarchy::new("zed", vec![Property::Int("13".to_string(), -4)], vec![])]
1809 }
1810
1811 fn make_foo() -> DiagnosticsHierarchy {
1812 DiagnosticsHierarchy::new("foo", make_all_foo_props(), make_zed())
1813 }
1814
1815 #[fuchsia::test]
1816 fn test_filter_hierarchy() {
1817 let test_selectors = vec!["*:root/foo:11", "*:root:z", r#"*:root/bar/zed:13\/\:"#];
1818
1819 assert_eq!(
1820 parse_selectors_and_filter_hierarchy(get_test_hierarchy(), test_selectors),
1821 Some(DiagnosticsHierarchy::new(
1822 "root",
1823 vec![Property::Int("z".to_string(), -4),],
1824 vec![
1825 DiagnosticsHierarchy::new(
1826 "bar",
1827 vec![],
1828 vec![DiagnosticsHierarchy::new(
1829 "zed",
1830 vec![Property::Int("13/:".to_string(), -4)],
1831 vec![],
1832 )],
1833 ),
1834 DiagnosticsHierarchy::new(
1835 "foo",
1836 vec![Property::Int("11".to_string(), -4),],
1837 vec![],
1838 )
1839 ],
1840 ))
1841 );
1842
1843 let test_selectors = vec!["*:root"];
1844 let mut sorted_expected = get_test_hierarchy();
1845 sorted_expected.sort();
1846 assert_eq!(
1847 parse_selectors_and_filter_hierarchy(get_test_hierarchy(), test_selectors),
1848 Some(sorted_expected)
1849 );
1850 }
1851
1852 #[fuchsia::test]
1853 fn test_filter_does_not_include_empty_node() {
1854 let test_selectors = vec!["*:root/foo:blorg"];
1855
1856 assert_eq!(
1857 parse_selectors_and_filter_hierarchy(get_test_hierarchy(), test_selectors),
1858 None,
1859 );
1860 }
1861
1862 #[fuchsia::test]
1863 fn test_filter_empty_hierarchy() {
1864 let test_selectors = vec!["*:root"];
1865
1866 assert_eq!(
1867 parse_selectors_and_filter_hierarchy(
1868 DiagnosticsHierarchy::new("root", vec![], vec![]),
1869 test_selectors
1870 ),
1871 Some(DiagnosticsHierarchy::new("root", vec![], vec![])),
1872 );
1873 }
1874
1875 #[fuchsia::test]
1876 fn test_full_filtering() {
1877 let test_selectors = vec!["*:non-existent-root"];
1879 assert_eq!(
1880 parse_selectors_and_filter_hierarchy(get_test_hierarchy(), test_selectors),
1881 None,
1882 );
1883
1884 let test_selectors = vec!["*:root/i-dont-exist:foo"];
1886 assert_eq!(
1887 parse_selectors_and_filter_hierarchy(get_test_hierarchy(), test_selectors),
1888 None,
1889 );
1890
1891 let test_selectors = vec!["*:root:i-dont-exist"];
1894 assert_eq!(
1895 parse_selectors_and_filter_hierarchy(get_test_hierarchy(), test_selectors),
1896 None,
1897 );
1898 }
1899
1900 #[fuchsia::test]
1901 fn test_subtree_selection_includes_empty_nodes() {
1902 let test_selectors = vec!["*:root"];
1903 let mut empty_hierarchy = DiagnosticsHierarchy::new(
1904 "root",
1905 vec![],
1906 vec![
1907 DiagnosticsHierarchy::new(
1908 "foo",
1909 vec![],
1910 vec![DiagnosticsHierarchy::new("zed", vec![], vec![])],
1911 ),
1912 DiagnosticsHierarchy::new(
1913 "bar",
1914 vec![],
1915 vec![DiagnosticsHierarchy::new("zed", vec![], vec![])],
1916 ),
1917 ],
1918 );
1919
1920 empty_hierarchy.sort();
1921
1922 assert_eq!(
1923 parse_selectors_and_filter_hierarchy(empty_hierarchy.clone(), test_selectors),
1924 Some(empty_hierarchy)
1925 );
1926 }
1927
1928 #[fuchsia::test]
1929 fn test_empty_tree_filtering() {
1930 let mut empty_hierarchy = DiagnosticsHierarchy::new("root", vec![], vec![]);
1932 empty_hierarchy.sort();
1933
1934 let subtree_selector = vec!["*:root"];
1935 assert_eq!(
1936 parse_selectors_and_filter_hierarchy(empty_hierarchy.clone(), subtree_selector),
1937 Some(empty_hierarchy.clone())
1938 );
1939
1940 let fake_property_selector = vec!["*:root:blorp"];
1942 assert_eq!(
1943 parse_selectors_and_filter_hierarchy(empty_hierarchy.clone(), fake_property_selector),
1944 None,
1945 );
1946 }
1947
1948 #[test_case(vec![Property::Int("11".to_string(), -4)], "root/foo:11" ; "specific_property")]
1949 #[test_case(make_all_foo_props(), "root/foo:*" ; "many_properties")]
1950 #[test_case(vec![], "root/foo:none" ; "property_not_there")]
1951 #[fuchsia::test]
1952 fn test_select_from_hierarchy_property_selectors(expected: Vec<Property>, tree_selector: &str) {
1953 let hierarchy = get_test_hierarchy();
1954 let parsed_selector =
1955 selectors::parse_selector::<VerboseError>(&format!("*:{tree_selector}"))
1956 .expect("All test selectors are valid and parsable.");
1957 let Ok(SelectResult::Properties(mut property_entry_vec)) =
1958 select_from_hierarchy(&hierarchy, &parsed_selector)
1959 else {
1960 panic!("must be properties");
1961 };
1962
1963 property_entry_vec.sort_by(|p1, p2| p1.name().cmp(p2.name()));
1964 let mut expected = expected.iter().map(Borrow::borrow).collect::<Vec<_>>();
1965 expected.sort_by(|p1, p2| p1.name().cmp(p2.name()));
1966
1967 assert_eq!(property_entry_vec, expected);
1968 }
1969
1970 #[test_case(vec![], "root/none" ; "node_not_there")]
1971 #[test_case(make_zed(), "root/foo/zed" ; "properties_only")]
1972 #[test_case(vec![make_foo()], "root/foo" ; "nodes_and_properties")]
1973 #[test_case(vec![get_test_hierarchy()], "root" ; "select_root")]
1974 #[fuchsia::test]
1975 fn test_select_from_hierarchy_tree_selectors(
1976 expected: Vec<DiagnosticsHierarchy>,
1977 tree_selector: &str,
1978 ) {
1979 let hierarchy = get_test_hierarchy();
1980 let parsed_selector =
1981 selectors::parse_selector::<VerboseError>(&format!("*:{tree_selector}"))
1982 .expect("All test selectors are valid and parsable.");
1983 let Ok(SelectResult::Nodes(node_vec)) = select_from_hierarchy(&hierarchy, &parsed_selector)
1984 else {
1985 panic!("must be nodes");
1986 };
1987
1988 let expected = expected.iter().map(Borrow::borrow).collect::<Vec<_>>();
1989
1990 assert_eq!(node_vec, expected);
1991 }
1992
1993 #[fuchsia::test]
1994 fn sort_numerical_value() {
1995 let mut diagnostics_hierarchy = DiagnosticsHierarchy::new(
1996 "root",
1997 vec![
1998 Property::Double("2".to_string(), 2.3),
1999 Property::Int("0".to_string(), -4),
2000 Property::Uint("10".to_string(), 3),
2001 Property::String("1".to_string(), "test".to_string()),
2002 ],
2003 vec![
2004 DiagnosticsHierarchy::new("123", vec![], vec![]),
2005 DiagnosticsHierarchy::new("34", vec![], vec![]),
2006 DiagnosticsHierarchy::new("4", vec![], vec![]),
2007 DiagnosticsHierarchy::new("023", vec![], vec![]),
2008 DiagnosticsHierarchy::new("12", vec![], vec![]),
2009 DiagnosticsHierarchy::new("1", vec![], vec![]),
2010 ],
2011 );
2012 diagnostics_hierarchy.sort();
2013 assert_eq!(
2014 diagnostics_hierarchy,
2015 DiagnosticsHierarchy::new(
2016 "root",
2017 vec![
2018 Property::Int("0".to_string(), -4),
2019 Property::String("1".to_string(), "test".to_string()),
2020 Property::Double("2".to_string(), 2.3),
2021 Property::Uint("10".to_string(), 3),
2022 ],
2023 vec![
2024 DiagnosticsHierarchy::new("1", vec![], vec![]),
2025 DiagnosticsHierarchy::new("4", vec![], vec![]),
2026 DiagnosticsHierarchy::new("12", vec![], vec![]),
2027 DiagnosticsHierarchy::new("023", vec![], vec![]),
2028 DiagnosticsHierarchy::new("34", vec![], vec![]),
2029 DiagnosticsHierarchy::new("123", vec![], vec![]),
2030 ]
2031 )
2032 );
2033 }
2034
2035 #[fuchsia::test]
2036 fn filter_hierarchy_doesnt_return_partial_matches() {
2037 let hierarchy = DiagnosticsHierarchy::new(
2038 "root",
2039 vec![],
2040 vec![DiagnosticsHierarchy::new("session_started_at", vec![], vec![])],
2041 );
2042 let test_selectors = vec!["*:root/session_started_at/0"];
2043 assert_eq!(parse_selectors_and_filter_hierarchy(hierarchy, test_selectors), None);
2044 }
2045
2046 #[fuchsia::test]
2047 fn test_filter_tree() {
2048 let test_selectors = vec!["root/foo:11", "root:z", r#"root/bar/zed:13\/\:"#];
2049 let parsed_test_selectors = test_selectors
2050 .into_iter()
2051 .map(|s| {
2052 selectors::parse_tree_selector::<VerboseError>(s)
2053 .expect("All test selectors are valid and parsable.")
2054 })
2055 .collect::<Vec<_>>();
2056
2057 let result =
2058 filter_tree(get_test_hierarchy(), &parsed_test_selectors).map(|mut hierarchy| {
2059 hierarchy.sort();
2060 hierarchy
2061 });
2062 assert_eq!(
2063 result,
2064 Some(DiagnosticsHierarchy::new(
2065 "root",
2066 vec![Property::Int("z".to_string(), -4),],
2067 vec![
2068 DiagnosticsHierarchy::new(
2069 "bar",
2070 vec![],
2071 vec![DiagnosticsHierarchy::new(
2072 "zed",
2073 vec![Property::Int("13/:".to_string(), -4)],
2074 vec![],
2075 )],
2076 ),
2077 DiagnosticsHierarchy::new(
2078 "foo",
2079 vec![Property::Int("11".to_string(), -4),],
2080 vec![],
2081 )
2082 ],
2083 ))
2084 );
2085 }
2086
2087 #[fuchsia::test]
2088 fn test_matcher_from_iterator() {
2089 let matcher = HierarchyMatcher::new(
2090 ["*:root/foo:11", "*:root:z", r#"*:root/bar/zed:13\/\:"#].into_iter().map(|s| {
2091 selectors::parse_selector::<VerboseError>(s)
2092 .expect("All test selectors are valid and parsable.")
2093 }),
2094 )
2095 .expect("create matcher from iterator of selectors");
2096 let result = filter_hierarchy(get_test_hierarchy(), &matcher).map(|mut hierarchy| {
2097 hierarchy.sort();
2098 hierarchy
2099 });
2100 assert_eq!(
2101 result,
2102 Some(DiagnosticsHierarchy::new(
2103 "root",
2104 vec![Property::Int("z".to_string(), -4),],
2105 vec![
2106 DiagnosticsHierarchy::new(
2107 "bar",
2108 vec![],
2109 vec![DiagnosticsHierarchy::new(
2110 "zed",
2111 vec![Property::Int("13/:".to_string(), -4)],
2112 vec![],
2113 )],
2114 ),
2115 DiagnosticsHierarchy::new(
2116 "foo",
2117 vec![Property::Int("11".to_string(), -4),],
2118 vec![],
2119 )
2120 ],
2121 ))
2122 );
2123 }
2124}