subtr_actor/stats/
labels.rs1use 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}