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}