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};
23use std::marker::PhantomData;
24
25pub trait Metadata {
26    fn record(&self, node: &Node);
27
28    fn record_with_parent(&self, node: &Node) {
29        node.record_child("metadata", |node| {
30            self.record(node);
31        });
32    }
33}
34
35impl Metadata for () {
36    fn record(&self, _: &Node) {}
37}
38
39/// A textual label for a bit in a [`BitSet`] aggregation.
40///
41/// [`BitSet`]: crate::experimental::series::BitSet
42#[derive(Clone, Debug)]
43pub struct BitLabel(Cow<'static, str>);
44
45impl BitLabel {}
46
47impl AsRef<str> for BitLabel {
48    fn as_ref(&self) -> &str {
49        self.0.as_ref()
50    }
51}
52
53impl Display for BitLabel {
54    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
55        write!(formatter, "{}", self.0)
56    }
57}
58
59impl From<Cow<'static, str>> for BitLabel {
60    fn from(label: Cow<'static, str>) -> Self {
61        BitLabel(label)
62    }
63}
64
65impl From<&'static str> for BitLabel {
66    fn from(label: &'static str) -> Self {
67        BitLabel(label.into())
68    }
69}
70
71impl From<String> for BitLabel {
72    fn from(label: String) -> Self {
73        BitLabel(label.into())
74    }
75}
76
77// TODO(https://fxbug.dev/375475120): It is easiest to construct a `BitSetMap` inline with a
78//                                    `Reactor`. This means that the mapping is most likely defined
79//                                    far from the data type that represents the bit set. Staleness
80//                                    is likely to occur if the mapping or data type change.
81//
82//                                    Provide an additional mechanism for defining this mapping
83//                                    locally to the sampled data type.
84/// A map from index to [`BitLabel`]s that indexes a [`BitSet`] aggregation.
85///
86/// A `BitSetMap` maps labels to particular bits in a bit set. These labels cannot change and are
87/// recorded directly in the metadata for a [`TimeMatrix`].
88///
89/// [`BitLabel`]: crate::experimental::series::metadata::BitLabel
90/// [`BitSet`]: crate::experimental::series::BitSet
91/// [`TimeMatrix`]: crate::experimental::series::TimeMatrix
92#[derive(Clone, Debug)]
93pub struct BitSetMap {
94    labels: BTreeMap<usize, BitLabel>,
95}
96
97impl BitSetMap {
98    // TODO(https://fxbug.dev/460232058): Consider merging `BitSetMap` and
99    // `DenseBitSetMap`. `DenseBitSetMap` provides behavior similar to
100    // `from_ordered` but avoids the hashing and allocations needed to create a
101    // BitSetMap.
102    pub fn from_ordered<I>(labels: I) -> Self
103    where
104        I: IntoIterator,
105        I::Item: Into<BitLabel>,
106    {
107        BitSetMap { labels: labels.into_iter().map(Into::into).enumerate().collect() }
108    }
109
110    pub fn from_indexed<T, I>(labels: I) -> Self
111    where
112        T: Into<BitLabel>,
113        I: IntoIterator<Item = (usize, T)>,
114    {
115        BitSetMap {
116            labels: labels
117                .into_iter()
118                .unique_by(|(index, _)| *index)
119                .map(|(index, label)| (index, label.into()))
120                .collect(),
121        }
122    }
123
124    pub fn record(&self, node: &Node) {
125        record_bit_labels_inner(node, self.labels().map(|(index, label)| (*index, label)));
126    }
127
128    pub fn labels(&self) -> impl '_ + Iterator<Item = (&usize, &BitLabel)> {
129        self.labels.iter()
130    }
131
132    pub fn label(&self, index: usize) -> Option<&BitLabel> {
133        self.labels.get(&index)
134    }
135}
136
137/// The actual implementation of recording bit labels.
138///
139/// This is crate-private so only types in this crate can call it, ensuring
140/// uniqueness of each label index.
141fn record_bit_labels_inner<I: Iterator<Item = (usize, L)>, L: AsRef<str>>(node: &Node, labels: I) {
142    node.record_child("index", |node| {
143        for (index, label) in labels
144            .filter_map(|(index, label)| u64::try_from(index).ok().map(|index| (index, label)))
145        {
146            // The index is used as the key, mapping from index to label in the Inspect tree.
147            // Inspect sorts keys numerically, so there is no need to format the index.
148            node.record_string(index.to_string(), label.as_ref());
149        }
150    });
151}
152
153/// Provides implementation of [`Metadata`]  similar to [`BitSetMap`] that
154/// requires a dense (i.e. not sparse) set of bit indices.
155pub struct DenseBitSetMap<I, F>(F, PhantomData<I>);
156
157impl<I, F> DenseBitSetMap<I, F> {
158    /// Creates a new [`DenseBitSetMap`] from a function that generates labels.
159    pub fn new(labels: F) -> Self {
160        DenseBitSetMap(labels, PhantomData)
161    }
162}
163
164impl<I, F> Metadata for DenseBitSetMap<I, F>
165where
166    I: Iterator,
167    I::Item: Into<BitLabel>,
168    F: Fn() -> I,
169{
170    fn record(&self, node: &Node) {
171        record_bit_labels_inner(node, self.0().map(Into::into).enumerate());
172    }
173}
174
175/// A reference to an Inspect node that indexes a [`BitSet`] aggregation.
176///
177/// A `BitSetNode` provides a path to an Inspect node in which each key represents a bit index and
178/// each value represents a label. Unlike [`BitSetMap`], this index is not tightly coupled to a
179/// served [`TimeMatrix`] and the index may be dynamic.
180///
181/// Only the path to the Inspect node is recorded in the metadata for a [`TimeMatrix`].
182///
183/// [`BitSet`]: crate::experimental::series::BitSet
184/// [`BitSetMap`]: crate::experimental::series::metadata::BitSetMap
185/// [`TimeMatrix`]: crate::experimental::series::TimeMatrix
186#[derive(Clone, Debug)]
187pub struct BitSetNode {
188    path: Cow<'static, str>,
189}
190
191impl BitSetNode {
192    pub fn from_path(path: impl Into<Cow<'static, str>>) -> Self {
193        BitSetNode { path: path.into() }
194    }
195
196    fn record(&self, node: &Node) {
197        node.record_string("index_node_path", self.path.as_ref());
198    }
199}
200
201/// Metadata for a [`BitSet`] aggregation that indexes bits with textual labels.
202///
203/// [`BitSet`]: crate::experimental::series::BitSet
204#[derive(Clone, Debug)]
205pub enum BitSetIndex {
206    Map(BitSetMap),
207    Node(BitSetNode),
208}
209
210impl From<BitSetMap> for BitSetIndex {
211    fn from(metadata: BitSetMap) -> Self {
212        BitSetIndex::Map(metadata)
213    }
214}
215
216impl From<BitSetNode> for BitSetIndex {
217    fn from(metadata: BitSetNode) -> Self {
218        BitSetIndex::Node(metadata)
219    }
220}
221
222impl Metadata for BitSetIndex {
223    fn record(&self, node: &Node) {
224        match self {
225            BitSetIndex::Map(metadata) => metadata.record(node),
226            BitSetIndex::Node(metadata) => metadata.record(node),
227        }
228    }
229}