windowed_stats/experimental/series/
metadata.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Metadata for [time matrices][`TimeMatrix`].
6//!
7//! The [`DataSemantic`] of the [`Statistic`] determines which metadata types can be used to
8//! annotate a [`TimeMatrix`]. For example, the [`Union`] statistic has a [`BitSet`] semantic that
9//! requires [`BitSetMetadata`].
10//!
11//! [`BitSet`]: crate::experimental::series::BitSet
12//! [`BitSetMetadata`]: crate::experimental::series::metadata::BitSetMetadata
13//! [`DataSemantic`]: crate::experimental::series::DataSemantic
14//! [`Statistic`]: crate::experimental::series::statistic::Statistic
15//! [`TimeMatrix`]: crate::experimental::series::TimeMatrix
16//! [`Union`]: crate::experimental::series::statistic::Union
17
18use fuchsia_inspect::Node;
19use itertools::Itertools;
20use std::borrow::Cow;
21use std::collections::BTreeMap;
22use std::fmt::{self, Display, Formatter};
23
24pub trait Metadata {
25    fn record(&self, node: &Node);
26}
27
28impl Metadata for () {
29    fn record(&self, _: &Node) {}
30}
31
32/// A textual label for a bit in a [`BitSet`] aggregation.
33///
34/// [`BitSet`]: crate::experimental::series::BitSet
35#[derive(Clone, Debug)]
36pub struct BitLabel(Cow<'static, str>);
37
38impl BitLabel {}
39
40impl AsRef<str> for BitLabel {
41    fn as_ref(&self) -> &str {
42        self.0.as_ref()
43    }
44}
45
46impl Display for BitLabel {
47    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
48        write!(formatter, "{}", self.0)
49    }
50}
51
52impl From<Cow<'static, str>> for BitLabel {
53    fn from(label: Cow<'static, str>) -> Self {
54        BitLabel(label)
55    }
56}
57
58impl From<&'static str> for BitLabel {
59    fn from(label: &'static str) -> Self {
60        BitLabel(label.into())
61    }
62}
63
64impl From<String> for BitLabel {
65    fn from(label: String) -> Self {
66        BitLabel(label.into())
67    }
68}
69
70// TODO(https://fxbug.dev/375475120): It is easiest to construct a `BitSetMap` inline with a
71//                                    `Reactor`. This means that the mapping is most likely defined
72//                                    far from the data type that represents the bit set. Staleness
73//                                    is likely to occur if the mapping or data type change.
74//
75//                                    Provide an additional mechanism for defining this mapping
76//                                    locally to the sampled data type.
77/// A map from index to [`BitLabel`]s that indexes a [`BitSet`] aggregation.
78///
79/// A `BitSetMap` maps labels to particular bits in a bit set. These labels cannot change and are
80/// recorded directly in the metadata for a [`TimeMatrix`].
81///
82/// [`BitLabel`]: crate::experimental::series::metadata::BitLabel
83/// [`BitSet`]: crate::experimental::series::BitSet
84/// [`TimeMatrix`]: crate::experimental::series::TimeMatrix
85#[derive(Clone, Debug)]
86pub struct BitSetMap {
87    labels: BTreeMap<usize, BitLabel>,
88}
89
90impl BitSetMap {
91    pub fn from_ordered<I>(labels: I) -> Self
92    where
93        I: IntoIterator,
94        I::Item: Into<BitLabel>,
95    {
96        BitSetMap { labels: labels.into_iter().map(Into::into).enumerate().collect() }
97    }
98
99    pub fn from_indexed<T, I>(labels: I) -> Self
100    where
101        T: Into<BitLabel>,
102        I: IntoIterator<Item = (usize, T)>,
103    {
104        BitSetMap {
105            labels: labels
106                .into_iter()
107                .unique_by(|(index, _)| *index)
108                .map(|(index, label)| (index, label.into()))
109                .collect(),
110        }
111    }
112
113    pub fn record(&self, node: &Node) {
114        node.record_child("index", |node| {
115            for (index, label) in self
116                .labels()
117                .filter_map(|(index, label)| u64::try_from(*index).ok().map(|index| (index, label)))
118            {
119                // The index is used as the key, mapping from index to label in the Inspect tree.
120                // Inspect sorts keys numerically, so there is no need to format the index.
121                node.record_string(index.to_string(), label.as_ref());
122            }
123        });
124    }
125
126    pub fn labels(&self) -> impl '_ + Iterator<Item = (&usize, &BitLabel)> {
127        self.labels.iter()
128    }
129
130    pub fn label(&self, index: usize) -> Option<&BitLabel> {
131        self.labels.get(&index)
132    }
133}
134
135/// A reference to an Inspect node that indexes a [`BitSet`] aggregation.
136///
137/// A `BitSetNode` provides a path to an Inspect node in which each key represents a bit index and
138/// each value represents a label. Unlike [`BitSetMap`], this index is not tightly coupled to a
139/// served [`TimeMatrix`] and the index may be dynamic.
140///
141/// Only the path to the Inspect node is recorded in the metadata for a [`TimeMatrix`].
142///
143/// [`BitSet`]: crate::experimental::series::BitSet
144/// [`BitSetMap`]: crate::experimental::series::metadata::BitSetMap
145/// [`TimeMatrix`]: crate::experimental::series::TimeMatrix
146#[derive(Clone, Debug)]
147pub struct BitSetNode {
148    path: Cow<'static, str>,
149}
150
151impl BitSetNode {
152    pub fn from_path(path: impl Into<Cow<'static, str>>) -> Self {
153        BitSetNode { path: path.into() }
154    }
155
156    fn record(&self, node: &Node) {
157        node.record_string("index_node_path", self.path.as_ref());
158    }
159}
160
161/// Metadata for a [`BitSet`] aggregation that indexes bits with textual labels.
162///
163/// [`BitSet`]: crate::experimental::series::BitSet
164#[derive(Clone, Debug)]
165pub enum BitSetIndex {
166    Map(BitSetMap),
167    Node(BitSetNode),
168}
169
170impl From<BitSetMap> for BitSetIndex {
171    fn from(metadata: BitSetMap) -> Self {
172        BitSetIndex::Map(metadata)
173    }
174}
175
176impl From<BitSetNode> for BitSetIndex {
177    fn from(metadata: BitSetNode) -> Self {
178        BitSetIndex::Node(metadata)
179    }
180}
181
182impl Metadata for BitSetIndex {
183    fn record(&self, node: &Node) {
184        match self {
185            BitSetIndex::Map(ref metadata) => metadata.record(node),
186            BitSetIndex::Node(ref metadata) => metadata.record(node),
187        }
188    }
189}