fast_telemetry/metric/
labeled_counter.rs1use crate::Counter;
4use crate::label::LabelEnum;
5use std::marker::PhantomData;
6
7pub struct LabeledCounter<L: LabelEnum> {
50 counters: Vec<Counter>,
51 _phantom: PhantomData<L>,
52}
53
54impl<L: LabelEnum> LabeledCounter<L> {
55 pub fn new(shard_count: usize) -> Self {
59 let counters = (0..L::CARDINALITY)
60 .map(|_| Counter::new(shard_count))
61 .collect();
62 Self {
63 counters,
64 _phantom: PhantomData,
65 }
66 }
67
68 #[inline]
70 pub fn inc(&self, label: L) {
71 self.add(label, 1);
72 }
73
74 #[inline]
76 pub fn add(&self, label: L, value: isize) {
77 let idx = label.as_index();
78 debug_assert!(idx < self.counters.len(), "label index out of bounds");
79 if cfg!(debug_assertions) {
81 self.counters[idx].add(value);
82 } else {
83 unsafe { self.counters.get_unchecked(idx) }.add(value);
84 }
85 }
86
87 #[inline]
89 pub fn get(&self, label: L) -> isize {
90 let idx = label.as_index();
91 if cfg!(debug_assertions) {
92 self.counters[idx].sum()
93 } else {
94 unsafe { self.counters.get_unchecked(idx) }.sum()
95 }
96 }
97
98 pub fn sum_all(&self) -> isize {
100 self.counters.iter().map(|c| c.sum()).sum()
101 }
102
103 pub fn iter(&self) -> impl Iterator<Item = (L, isize)> + '_ {
107 self.counters
108 .iter()
109 .enumerate()
110 .map(|(idx, counter)| (L::from_index(idx), counter.sum()))
111 }
112
113 pub fn swap_all(&self) -> impl Iterator<Item = (L, isize)> + '_ {
117 self.counters
118 .iter()
119 .enumerate()
120 .map(|(idx, counter)| (L::from_index(idx), counter.swap()))
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[derive(Copy, Clone, Debug, PartialEq)]
129 enum TestLabel {
130 A,
131 B,
132 C,
133 }
134
135 impl LabelEnum for TestLabel {
136 const CARDINALITY: usize = 3;
137 const LABEL_NAME: &'static str = "test";
138
139 fn as_index(self) -> usize {
140 self as usize
141 }
142
143 fn from_index(index: usize) -> Self {
144 match index {
145 0 => Self::A,
146 1 => Self::B,
147 _ => Self::C,
148 }
149 }
150
151 fn variant_name(self) -> &'static str {
152 match self {
153 Self::A => "a",
154 Self::B => "b",
155 Self::C => "c",
156 }
157 }
158 }
159
160 #[test]
161 fn test_basic_operations() {
162 let counter: LabeledCounter<TestLabel> = LabeledCounter::new(4);
163
164 counter.inc(TestLabel::A);
165 counter.add(TestLabel::B, 5);
166 counter.inc(TestLabel::A);
167
168 assert_eq!(counter.get(TestLabel::A), 2);
169 assert_eq!(counter.get(TestLabel::B), 5);
170 assert_eq!(counter.get(TestLabel::C), 0);
171 }
172
173 #[test]
174 fn test_sum_all() {
175 let counter: LabeledCounter<TestLabel> = LabeledCounter::new(4);
176
177 counter.add(TestLabel::A, 10);
178 counter.add(TestLabel::B, 20);
179 counter.add(TestLabel::C, 30);
180
181 assert_eq!(counter.sum_all(), 60);
182 }
183
184 #[test]
185 fn test_iteration() {
186 let counter: LabeledCounter<TestLabel> = LabeledCounter::new(4);
187
188 counter.add(TestLabel::A, 1);
189 counter.add(TestLabel::B, 2);
190 counter.add(TestLabel::C, 3);
191
192 let pairs: Vec<_> = counter.iter().collect();
193 assert_eq!(pairs.len(), 3);
194 assert_eq!(pairs[0], (TestLabel::A, 1));
195 assert_eq!(pairs[1], (TestLabel::B, 2));
196 assert_eq!(pairs[2], (TestLabel::C, 3));
197 }
198
199 #[test]
200 fn test_swap_all() {
201 let counter: LabeledCounter<TestLabel> = LabeledCounter::new(4);
202
203 counter.add(TestLabel::A, 10);
204 counter.add(TestLabel::B, 20);
205
206 let swapped: Vec<_> = counter.swap_all().collect();
207 assert_eq!(swapped[0], (TestLabel::A, 10));
208 assert_eq!(swapped[1], (TestLabel::B, 20));
209
210 assert_eq!(counter.get(TestLabel::A), 0);
212 assert_eq!(counter.get(TestLabel::B), 0);
213 }
214
215 #[test]
216 fn test_concurrent_access() {
217 let counter: LabeledCounter<TestLabel> = LabeledCounter::new(4);
218
219 std::thread::scope(|s| {
220 for _ in 0..4 {
221 s.spawn(|| {
222 for _ in 0..1000 {
223 counter.inc(TestLabel::A);
224 counter.inc(TestLabel::B);
225 }
226 });
227 }
228 });
229
230 assert_eq!(counter.get(TestLabel::A), 4000);
231 assert_eq!(counter.get(TestLabel::B), 4000);
232 assert_eq!(counter.get(TestLabel::C), 0);
233 }
234}