o_sfu_telemetry/metrics/
counter.rs1use std::{
8 marker::PhantomData,
9 sync::atomic::{AtomicI64, AtomicU64, Ordering},
10 time::Duration,
11};
12
13pub(super) trait MetricLabel: Copy + 'static {
14 const VARIANTS: &'static [Self];
15 const COUNT: usize;
16
17 fn as_index(self) -> usize;
18}
19
20pub(super) trait ExportedMetricLabel: MetricLabel {
21 fn label_value(self) -> &'static str;
22}
23
24pub(super) trait MetricBucketLabel: MetricLabel {
25 fn upper_bound(self) -> &'static str;
26}
27
28pub(super) trait HistogramBucketLabel: MetricBucketLabel {
29 fn from_duration(duration: Duration) -> Self;
30}
31
32#[repr(align(64))]
33#[derive(Debug, Default)]
34pub(super) struct PaddedCounter {
35 value: Counter,
36}
37
38impl PaddedCounter {
39 pub(super) fn increment(&self) {
40 self.value.increment();
41 }
42
43 pub(super) fn add(&self, value: usize) {
44 self.value.add(value);
45 }
46
47 pub(super) fn add_u64(&self, value: u64) {
48 self.value.add_u64(value);
49 }
50
51 pub(super) fn load(&self) -> u64 {
52 self.value.load()
53 }
54}
55
56#[derive(Debug, Default)]
57pub(super) struct Counter {
58 value: AtomicU64,
59}
60
61impl Counter {
62 pub(super) fn increment(&self) {
63 self.value.fetch_add(1, Ordering::Relaxed);
64 }
65
66 pub(super) fn add(&self, value: usize) {
67 if let Ok(value) = u64::try_from(value) {
68 self.value.fetch_add(value, Ordering::Relaxed);
69 }
70 }
71
72 pub(super) fn add_u64(&self, value: u64) {
73 self.value.fetch_add(value, Ordering::Relaxed);
74 }
75
76 pub(super) fn load(&self) -> u64 {
77 self.value.load(Ordering::Relaxed)
78 }
79}
80
81#[derive(Debug, Default)]
82pub(super) struct UpDownCounter {
83 value: AtomicI64,
84}
85
86impl UpDownCounter {
87 pub(super) fn add(&self, delta: i64) {
88 self.value.fetch_add(delta, Ordering::Relaxed);
89 }
90
91 pub(super) fn load(&self) -> i64 {
92 self.value.load(Ordering::Relaxed)
93 }
94}
95
96#[derive(Debug)]
97pub(super) struct UpDownCounterFamily<L: MetricLabel> {
98 counters: Box<[UpDownCounter]>,
99 _label: PhantomData<L>,
100}
101
102impl<L: MetricLabel> Default for UpDownCounterFamily<L> {
103 fn default() -> Self {
104 let counters = (0..L::COUNT)
105 .map(|_| UpDownCounter::default())
106 .collect::<Vec<_>>()
107 .into_boxed_slice();
108 Self {
109 counters,
110 _label: PhantomData,
111 }
112 }
113}
114
115impl<L: MetricLabel> UpDownCounterFamily<L> {
116 pub(super) fn add(&self, label: L, delta: i64) {
117 if let Some(counter) = self.counters.get(label.as_index()) {
118 counter.add(delta);
119 }
120 }
121
122 pub(super) fn load(&self, label: L) -> i64 {
123 self.counters
124 .get(label.as_index())
125 .map_or(0, UpDownCounter::load)
126 }
127}
128
129#[derive(Debug)]
130pub(super) struct PaddedCounterFamily<L: MetricLabel> {
131 counters: Box<[PaddedCounter]>,
132 _label: PhantomData<L>,
133}
134
135impl<L: MetricLabel> Default for PaddedCounterFamily<L> {
136 fn default() -> Self {
137 let counters = (0..L::COUNT)
138 .map(|_| PaddedCounter::default())
139 .collect::<Vec<_>>()
140 .into_boxed_slice();
141 Self {
142 counters,
143 _label: PhantomData,
144 }
145 }
146}
147
148impl<L: MetricLabel> PaddedCounterFamily<L> {
149 pub(super) fn increment(&self, label: L) {
150 if let Some(counter) = self.counters.get(label.as_index()) {
151 counter.increment();
152 }
153 }
154
155 pub(super) fn add(&self, label: L, value: usize) {
156 if let Some(counter) = self.counters.get(label.as_index()) {
157 counter.add(value);
158 }
159 }
160
161 pub(super) fn add_u64(&self, label: L, value: u64) {
162 if let Some(counter) = self.counters.get(label.as_index()) {
163 counter.add_u64(value);
164 }
165 }
166
167 pub(super) fn load(&self, label: L) -> u64 {
168 self.counters
169 .get(label.as_index())
170 .map_or(0, PaddedCounter::load)
171 }
172}
173
174#[derive(Debug)]
175pub(super) struct CounterFamily<L: MetricLabel> {
176 counters: Box<[Counter]>,
177 _label: PhantomData<L>,
178}
179
180impl<L: MetricLabel> Default for CounterFamily<L> {
181 fn default() -> Self {
182 let counters = (0..L::COUNT)
183 .map(|_| Counter::default())
184 .collect::<Vec<_>>()
185 .into_boxed_slice();
186 Self {
187 counters,
188 _label: PhantomData,
189 }
190 }
191}
192
193impl<L: MetricLabel> CounterFamily<L> {
194 pub(super) fn increment(&self, label: L) {
195 if let Some(counter) = self.counters.get(label.as_index()) {
196 counter.increment();
197 }
198 }
199
200 pub(super) fn add(&self, label: L, value: usize) {
201 if let Some(counter) = self.counters.get(label.as_index()) {
202 counter.add(value);
203 }
204 }
205
206 pub(super) fn add_u64(&self, label: L, value: u64) {
207 if let Some(counter) = self.counters.get(label.as_index()) {
208 counter.add_u64(value);
209 }
210 }
211
212 pub(super) fn load(&self, label: L) -> u64 {
213 self.counters.get(label.as_index()).map_or(0, Counter::load)
214 }
215}
216
217#[derive(Debug)]
218pub(super) struct Histogram<B: HistogramBucketLabel> {
219 buckets: Box<[Counter]>,
220 count: Counter,
221 sum_micros: Counter,
222 _bucket: PhantomData<B>,
223}
224
225impl<B: HistogramBucketLabel> Default for Histogram<B> {
226 fn default() -> Self {
227 let buckets = (0..B::COUNT)
228 .map(|_| Counter::default())
229 .collect::<Vec<_>>()
230 .into_boxed_slice();
231 Self {
232 buckets,
233 count: Counter::default(),
234 sum_micros: Counter::default(),
235 _bucket: PhantomData,
236 }
237 }
238}
239
240impl<B: HistogramBucketLabel> Histogram<B> {
241 pub(super) fn observe(&self, duration: Duration) {
246 self.count.increment();
247 self.sum_micros
248 .add_u64(u64::try_from(duration.as_micros()).unwrap_or(u64::MAX));
249 let bucket_index = B::from_duration(duration).as_index();
250 for counter in self.buckets.iter().skip(bucket_index) {
251 counter.increment();
252 }
253 }
254
255 pub(super) fn load_bucket(&self, bucket: B) -> u64 {
256 self.buckets.get(bucket.as_index()).map_or(0, Counter::load)
257 }
258
259 pub(super) fn load_count(&self) -> u64 {
260 self.count.load()
261 }
262
263 pub(super) fn load_sum_micros(&self) -> u64 {
264 self.sum_micros.load()
265 }
266}
267
268#[derive(Debug)]
269pub(super) struct HistogramFamily<L: MetricLabel, B: HistogramBucketLabel> {
270 histograms: Box<[Histogram<B>]>,
271 _label: PhantomData<L>,
272}
273
274impl<L: MetricLabel, B: HistogramBucketLabel> Default for HistogramFamily<L, B> {
275 fn default() -> Self {
276 let histograms = (0..L::COUNT)
277 .map(|_| Histogram::default())
278 .collect::<Vec<_>>()
279 .into_boxed_slice();
280 Self {
281 histograms,
282 _label: PhantomData,
283 }
284 }
285}
286
287impl<L: MetricLabel, B: HistogramBucketLabel> HistogramFamily<L, B> {
288 pub(super) fn observe(&self, label: L, duration: Duration) {
289 if let Some(histogram) = self.histograms.get(label.as_index()) {
290 histogram.observe(duration);
291 }
292 }
293
294 pub(super) fn load_bucket(&self, label: L, bucket: B) -> u64 {
295 self.histograms
296 .get(label.as_index())
297 .map_or(0, |histogram| histogram.load_bucket(bucket))
298 }
299
300 pub(super) fn load_count(&self, label: L) -> u64 {
301 self.histograms
302 .get(label.as_index())
303 .map_or(0, Histogram::load_count)
304 }
305
306 pub(super) fn load_sum_micros(&self, label: L) -> u64 {
307 self.histograms
308 .get(label.as_index())
309 .map_or(0, Histogram::load_sum_micros)
310 }
311}