1use std::{
2 any::Any,
3 borrow::Cow,
4 fmt::Display,
5 ops::Deref,
6 sync::{
7 atomic::{AtomicU64, Ordering},
8 Arc,
9 },
10};
11
12use helpers::RegisterableMetric;
13
14#[derive(Default, Debug)]
15pub struct IntCounter(pub AtomicU64);
16
17#[derive(Default, Debug)]
18pub struct IntGauge(pub AtomicU64);
19
20pub mod helpers;
21
22pub struct ChildMetric<T, C: 'static> {
23 arc: Arc<T>,
24 child: &'static C,
25}
26
27impl<T, C: 'static> Deref for ChildMetric<T, C> {
28 type Target = C;
29
30 fn deref(&self) -> &Self::Target {
31 self.child
32 }
33}
34
35impl<T: 'static, C: 'static> Clone for ChildMetric<T, C> {
36 fn clone(&self) -> Self {
37 Self {
38 arc: self.arc.clone(),
39 child: self.child,
40 }
41 }
42}
43
44impl<T: 'static, C: 'static> ChildMetric<T, C> {
45 pub fn create<F: Fn(&'static T) -> &'static C>(arc: &Arc<T>, get: F) -> Self {
46 let cloned = arc.clone();
47 let item = get(unsafe { std::mem::transmute::<&T, &'static T>(&cloned) });
48 Self {
49 arc: cloned,
50 child: item,
51 }
52 }
53}
54
55impl IntCounter {
56 pub fn owned_inc(&self) {
57 self.owned_inc_by(1);
58 }
59
60 pub fn inc(&self) {
61 self.shared_inc();
62 }
63
64 pub fn inc_by(&self, amount: u64) {
65 self.shared_inc_by(amount);
66 }
67
68 pub fn shared_inc(&self) {
69 self.shared_inc_by(1);
70 }
71
72 pub fn owned_inc_by(&self, amount: u64) {
73 self.0.fetch_add(amount, Ordering::Relaxed);
74 }
75
76 pub fn shared_inc_by(&self, amount: u64) {
77 self.0.fetch_add(amount, Ordering::AcqRel);
78 }
79
80 pub fn load(&self) -> u64 {
81 self.shared_load()
82 }
83
84 pub fn shared_load(&self) -> u64 {
85 self.0.load(Ordering::Acquire)
86 }
87
88 pub fn owned_load(&self) -> u64 {
89 self.0.load(Ordering::Relaxed)
90 }
91}
92
93impl IntGauge {
94 pub fn set(&self, value: u64) {
95 self.0.store(value, Ordering::Relaxed);
96 }
97
98 pub fn owned_dec(&self) {
99 self.owned_dec_by(1);
100 }
101
102 pub fn dec(&self) {
103 self.shared_dec();
104 }
105
106 pub fn shared_dec(&self) {
107 self.shared_dec_by(1);
108 }
109
110 pub fn owned_dec_by(&self, amount: u64) {
111 self.0.fetch_sub(amount, Ordering::Relaxed);
112 }
113
114 pub fn shared_dec_by(&self, amount: u64) {
115 self.0.fetch_sub(amount, Ordering::AcqRel);
116 }
117
118 pub fn inc(&self) {
119 self.shared_inc();
120 }
121
122 pub fn shared_inc(&self) {
123 self.shared_inc_by(1);
124 }
125
126 pub fn owned_inc_by(&self, amount: u64) {
127 self.0.fetch_add(amount, Ordering::Relaxed);
128 }
129
130 pub fn shared_inc_by(&self, amount: u64) {
131 self.0.fetch_add(amount, Ordering::AcqRel);
132 }
133
134 pub fn load(&self) -> u64 {
135 self.shared_load()
136 }
137
138 pub fn shared_load(&self) -> u64 {
139 self.0.load(Ordering::Acquire)
140 }
141
142 pub fn owned_load(&self) -> u64 {
143 self.0.load(Ordering::Relaxed)
144 }
145}
146
147pub struct PromMetricRegistry {
148 metric_holders: Vec<Arc<dyn Any>>,
150 metrics: Vec<RegisteredMetric>,
151 base_attributes: Vec<[Cow<'static, str>; 2]>,
152}
153
154impl Default for PromMetricRegistry {
155 fn default() -> Self {
156 let base_attributes = if let Some(details) = pkg_details::try_get() {
157 vec![
158 [Cow::Borrowed("program"), Cow::Borrowed(details.pkg_name)],
159 [
160 Cow::Borrowed("pkg_version"),
161 Cow::Borrowed(details.pkg_version),
162 ],
163 ]
164 } else {
165 Vec::new()
166 };
167
168 PromMetricRegistry {
169 metric_holders: Vec::new(),
170 metrics: Vec::new(),
171 base_attributes,
172 }
173 }
174}
175
176unsafe impl Send for PromMetricRegistry {}
177unsafe impl Sync for PromMetricRegistry {}
178
179struct RegisteredMetric {
180 metric_type: MetricType,
181 name: Cow<'static, str>,
182 value: &'static AtomicU64,
183 attributes: Vec<[Cow<'static, str>; 2]>,
184 skip_zero: bool,
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
188pub enum MetricType {
189 IntCounter,
190 IntGauge,
191}
192
193impl Display for MetricType {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 match self {
196 Self::IntCounter => write!(f, "counter"),
197 Self::IntGauge => write!(f, "gauge"),
198 }
199 }
200}
201
202impl Display for PromMetricRegistry {
203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204 let mut last = None;
205
206 for metric in &self.metrics {
207 let matches = if let Some((last, ty)) = &last {
208 last == &metric.name && *ty == metric.metric_type
209 } else {
210 false
211 };
212
213 if metric.skip_zero && metric.value.load(Ordering::Relaxed) == 0 {
214 continue;
215 }
216
217 if !matches {
218 writeln!(f, "# HELP {}", metric.name)?;
219 writeln!(f, "# TYPE {} {}", metric.name, metric.metric_type)?;
220 last = Some((metric.name.clone(), metric.metric_type));
221 }
222
223 write!(f, "{}", metric.name)?;
224 let end = metric.attributes.len();
225 for (i, [key, value]) in metric.attributes.iter().enumerate() {
226 if i == 0 {
227 write!(f, "{{{}=\"{}\"", key, value)?;
228 if end == 1 {
229 write!(f, "}}")?;
230 }
231 } else if i + 1 == end {
232 write!(f, ",{}=\"{}\"}}", key, value)?;
233 } else {
234 write!(f, ",{}=\"{}\"", key, value)?;
235 }
236 }
237
238 writeln!(f, " {}", metric.value.load(Ordering::Relaxed))?;
239 }
240
241 Ok(())
242 }
243}
244
245impl PromMetricRegistry {
246 pub fn new() -> Self {
247 Self::default()
248 }
249
250 pub fn register<M: RegisterableMetric + 'static>(&mut self, metrics: &Arc<M>) {
251 self.register_fn(metrics, |m, reg| {
252 m.register(reg);
253 });
254 }
255
256 pub fn register_fn<'a, T: 'static>(
257 &'a mut self,
258 metrics: &Arc<T>,
259 register: impl FnOnce(&'static T, &mut RegisterAction<'a>),
260 ) {
261 self.metric_holders
263 .push(Arc::clone(metrics) as Arc<dyn Any>);
264
265 let mut action = RegisterAction {
266 name_prefix: None,
267 metrics: &mut self.metrics,
268 base_attributes: self.base_attributes.clone(),
269 };
270
271 let metric_ref = unsafe { std::mem::transmute::<&T, &'static T>(metrics) };
272 register(metric_ref, &mut action);
273 }
274}
275
276pub struct RegisterAction<'a> {
277 metrics: &'a mut Vec<RegisteredMetric>,
278 name_prefix: Option<String>,
279 base_attributes: Vec<[Cow<'static, str>; 2]>,
280}
281
282impl RegisterAction<'_> {
283 pub fn child(&mut self) -> RegisterAction<'_> {
284 RegisterAction {
285 metrics: self.metrics,
286 name_prefix: self.name_prefix.clone(),
287 base_attributes: self.base_attributes.clone(),
288 }
289 }
290
291 pub fn name_prefix<S: Into<String>>(&mut self, prefix: S) -> &mut Self {
292 self.name_prefix = Some(prefix.into());
293 self
294 }
295
296 pub fn base_attr<K: Into<Cow<'static, str>>, V: Into<Cow<'static, str>>>(
297 &mut self,
298 key: K,
299 value: V,
300 ) -> &mut Self {
301 let key = key.into();
302 let value = value.into();
303 self.base_attributes.push([key, value]);
304 self
305 }
306
307 pub fn count<N: Into<Cow<'static, str>>>(
308 &mut self,
309 name: N,
310 count: &'static IntCounter,
311 ) -> RegisterHelper<'_> {
312 self.metric(name, &count.0, MetricType::IntCounter)
313 }
314
315 pub fn gauge<N: Into<Cow<'static, str>>>(
316 &mut self,
317 name: N,
318 gauge: &'static IntGauge,
319 ) -> RegisterHelper<'_> {
320 self.metric(name, &gauge.0, MetricType::IntGauge)
321 }
322
323 fn metric<N: Into<Cow<'static, str>>>(
324 &mut self,
325 name: N,
326 value: &'static AtomicU64,
327 metric_type: MetricType,
328 ) -> RegisterHelper<'_> {
329 let mut helper = self.empty();
330 helper.metric(name, value, metric_type);
331 helper
332 }
333
334 pub fn group<N: Into<Cow<'static, str>>>(&mut self, prefix: N) -> RegisterHelper<'_> {
335 self.start(Some(prefix))
336 }
337
338 pub fn empty(&mut self) -> RegisterHelper<'_> {
339 self.start::<String>(None)
340 }
341
342 fn start<N: Into<Cow<'static, str>>>(&mut self, prefix: Option<N>) -> RegisterHelper<'_> {
343 let attributes = self.base_attributes.clone();
344
345 let name_prefix = match (&self.name_prefix, prefix) {
346 (Some(prefix), None) => Some(Cow::Owned(prefix.clone())),
347 (None, Some(prefix)) => Some(prefix.into()),
348 (Some(a), Some(b)) => {
349 let b = b.into();
350 Some(Cow::Owned(format!("{}_{}", a, b)))
351 }
352 (None, None) => None,
353 };
354
355 RegisterHelper {
356 metrics: self.metrics,
357 name_prefix,
358 attributes,
359 registered: Vec::new(),
360 }
361 }
362}
363
364pub struct RegisterHelper<'a> {
365 name_prefix: Option<Cow<'static, str>>,
366 metrics: &'a mut Vec<RegisteredMetric>,
367 attributes: Vec<[Cow<'static, str>; 2]>,
368 registered: Vec<RegisteredMetric>,
369}
370
371impl RegisterHelper<'_> {
372 pub fn attr<K: Into<Cow<'static, str>>, V: Into<Cow<'static, str>>>(
373 &mut self,
374 key: K,
375 value: V,
376 ) -> &mut Self {
377 let key = key.into();
378 let value = value.into();
379 self.attributes.push([key, value]);
380 self
381 }
382
383 pub fn count<N: Into<Cow<'static, str>>>(
384 &mut self,
385 name: N,
386 count: &'static IntCounter,
387 ) -> &mut Self {
388 self.metric(name, &count.0, MetricType::IntCounter)
389 }
390
391 pub fn gauge<N: Into<Cow<'static, str>>>(
392 &mut self,
393 name: N,
394 gauge: &'static IntGauge,
395 ) -> &mut Self {
396 self.metric(name, &gauge.0, MetricType::IntGauge)
397 }
398
399 pub fn metric<N: Into<Cow<'static, str>>>(
400 &mut self,
401 name: N,
402 value: &'static AtomicU64,
403 metric_type: MetricType,
404 ) -> &mut Self {
405 self.metric_opt(name, value, metric_type, false)
406 }
407
408 pub fn metric_opt<N: Into<Cow<'static, str>>>(
409 &mut self,
410 name: N,
411 value: &'static AtomicU64,
412 metric_type: MetricType,
413 skip_zero: bool,
414 ) -> &mut Self {
415 let name = match &self.name_prefix {
416 Some(prefix) => Cow::Owned(format!("{}_{}", prefix, name.into())),
417 None => name.into(),
418 };
419
420 self.registered.push(RegisteredMetric {
421 metric_type,
422 name,
423 value,
424 attributes: Vec::new(),
425 skip_zero,
426 });
427
428 self
429 }
430}
431
432impl Drop for RegisterHelper<'_> {
433 fn drop(&mut self) {
434 for mut reg in self.registered.drain(..) {
435 reg.attributes = self.attributes.clone();
436 self.metrics.push(reg);
437 }
438 self.metrics.sort_by_key(|item| SortKey {
439 name: item.name.clone(),
440 metric: item.metric_type,
441 });
442 }
443}
444
445#[derive(PartialEq, Eq, PartialOrd, Ord)]
446struct SortKey {
447 name: Cow<'static, str>,
448 metric: MetricType,
449}
450
451#[cfg(test)]
452mod test {
453 use std::sync::Arc;
454
455 use crate::{IntCounter, IntGauge, PromMetricRegistry};
456
457 #[derive(Debug, Default)]
458 struct Met {
459 a: IntCounter,
460 b: IntCounter,
461 c: IntGauge,
462 }
463
464 #[test]
465 fn metrics_test() {
466 let met = Arc::new(Met::default());
467 let mut reg = PromMetricRegistry::new();
468 reg.base_attributes.push(["prefix".into(), "set".into()]);
469
470 reg.register_fn(&met, |m, reg| {
471 reg.name_prefix("base_prefix");
472
473 reg.group("prefix")
474 .count("a", &m.a)
475 .metric_opt("b", &m.b.0, crate::MetricType::IntCounter, true)
476 .attr("test", "2");
477
478 reg.gauge("c", &m.c);
479 });
480
481 println!("{}", reg);
482
483 met.b.inc();
484 println!("{}", reg);
485 }
486}