Skip to main content

subtr_actor/stats/
labels.rs

1use serde::{Deserialize, Deserializer, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, ts_rs::TS)]
4#[ts(export)]
5pub struct StatLabel {
6    pub key: &'static str,
7    pub value: &'static str,
8}
9
10impl StatLabel {
11    pub const fn new(key: &'static str, value: &'static str) -> Self {
12        Self { key, value }
13    }
14}
15
16impl<'de> Deserialize<'de> for StatLabel {
17    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
18    where
19        D: Deserializer<'de>,
20    {
21        #[derive(Deserialize)]
22        struct OwnedStatLabel {
23            key: String,
24            value: String,
25        }
26
27        let owned = OwnedStatLabel::deserialize(deserializer)?;
28        Ok(Self {
29            key: leak_string(owned.key),
30            value: leak_string(owned.value),
31        })
32    }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
36#[ts(export)]
37pub struct LabeledCountEntry {
38    pub labels: Vec<StatLabel>,
39    pub count: u32,
40}
41
42#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
43#[ts(export)]
44pub struct LabeledCounts {
45    pub entries: Vec<LabeledCountEntry>,
46}
47
48impl LabeledCounts {
49    pub fn increment<I>(&mut self, labels: I)
50    where
51        I: IntoIterator<Item = StatLabel>,
52    {
53        let mut labels: Vec<_> = labels.into_iter().collect();
54        labels.sort();
55
56        if let Some(entry) = self.entries.iter_mut().find(|entry| entry.labels == labels) {
57            entry.count += 1;
58            return;
59        }
60
61        self.entries.push(LabeledCountEntry { labels, count: 1 });
62        self.entries
63            .sort_by(|left, right| left.labels.cmp(&right.labels));
64    }
65
66    pub fn count_matching(&self, required_labels: &[StatLabel]) -> u32 {
67        self.entries
68            .iter()
69            .filter(|entry| {
70                required_labels
71                    .iter()
72                    .all(|required_label| entry.labels.contains(required_label))
73            })
74            .map(|entry| entry.count)
75            .sum()
76    }
77
78    pub fn count_exact(&self, labels: &[StatLabel]) -> u32 {
79        let mut normalized_labels = labels.to_vec();
80        normalized_labels.sort();
81
82        self.entries
83            .iter()
84            .find(|entry| entry.labels == normalized_labels)
85            .map(|entry| entry.count)
86            .unwrap_or(0)
87    }
88
89    pub fn total(&self) -> u32 {
90        self.entries.iter().map(|entry| entry.count).sum()
91    }
92
93    pub fn complete_from_label_sets(label_sets: &[&[StatLabel]], counts: &Self) -> Self {
94        fn append_entries(
95            label_sets: &[&[StatLabel]],
96            index: usize,
97            labels: &mut Vec<StatLabel>,
98            counts: &LabeledCounts,
99            entries: &mut Vec<LabeledCountEntry>,
100        ) {
101            if index == label_sets.len() {
102                let mut normalized_labels = labels.clone();
103                normalized_labels.sort();
104                entries.push(LabeledCountEntry {
105                    count: counts.count_matching(&normalized_labels),
106                    labels: normalized_labels,
107                });
108                return;
109            }
110
111            for label in label_sets[index] {
112                labels.push(label.clone());
113                append_entries(label_sets, index + 1, labels, counts, entries);
114                labels.pop();
115            }
116        }
117
118        let mut entries = Vec::new();
119        append_entries(label_sets, 0, &mut Vec::new(), counts, &mut entries);
120        entries.sort_by(|left, right| left.labels.cmp(&right.labels));
121        Self { entries }
122    }
123
124    pub fn is_empty(&self) -> bool {
125        self.entries.is_empty()
126    }
127}
128
129#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ts_rs::TS)]
130#[ts(export)]
131pub struct LabeledFloatSumEntry {
132    pub labels: Vec<StatLabel>,
133    pub value: f32,
134}
135
136#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, ts_rs::TS)]
137#[ts(export)]
138pub struct LabeledFloatSums {
139    pub entries: Vec<LabeledFloatSumEntry>,
140}
141
142impl LabeledFloatSums {
143    pub fn add<I>(&mut self, labels: I, value: f32)
144    where
145        I: IntoIterator<Item = StatLabel>,
146    {
147        let mut labels: Vec<_> = labels.into_iter().collect();
148        labels.sort();
149
150        if let Some(entry) = self.entries.iter_mut().find(|entry| entry.labels == labels) {
151            entry.value += value;
152            return;
153        }
154
155        self.entries.push(LabeledFloatSumEntry { labels, value });
156        self.entries
157            .sort_by(|left, right| left.labels.cmp(&right.labels));
158    }
159
160    pub fn sum_matching(&self, required_labels: &[StatLabel]) -> f32 {
161        self.entries
162            .iter()
163            .filter(|entry| {
164                required_labels
165                    .iter()
166                    .all(|required_label| entry.labels.contains(required_label))
167            })
168            .map(|entry| entry.value)
169            .sum()
170    }
171
172    pub fn sum_exact(&self, labels: &[StatLabel]) -> f32 {
173        let mut normalized_labels = labels.to_vec();
174        normalized_labels.sort();
175
176        self.entries
177            .iter()
178            .find(|entry| entry.labels == normalized_labels)
179            .map(|entry| entry.value)
180            .unwrap_or(0.0)
181    }
182
183    pub fn is_empty(&self) -> bool {
184        self.entries.is_empty()
185    }
186}
187
188fn leak_string(value: String) -> &'static str {
189    Box::leak(value.into_boxed_str())
190}