provide_telemetry/
metrics.rs1use std::collections::BTreeMap;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::sync::{Arc, Mutex};
9
10use crate::backpressure::{release, try_acquire};
11use crate::runtime::get_runtime_config;
12use crate::sampling::{should_sample, Signal};
13
14static METRICS_INITIALIZED: AtomicBool = AtomicBool::new(false);
15
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct Meter {
18 name: String,
19}
20
21impl Meter {
22 pub fn name(&self) -> &str {
23 &self.name
24 }
25}
26
27#[derive(Clone, Debug, Default)]
28struct CounterState {
29 value: f64,
30}
31
32#[derive(Clone, Debug)]
33pub struct Counter {
34 name: String,
35 #[allow(dead_code)]
36 description: Option<String>,
37 #[allow(dead_code)]
38 unit: Option<String>,
39 state: Arc<Mutex<CounterState>>,
40}
41
42impl Counter {
43 pub fn add(&self, value: f64, _attributes: Option<BTreeMap<String, String>>) {
44 if !metrics_enabled() {
45 return;
46 }
47 if !should_sample(Signal::Metrics, Some(&self.name)).unwrap_or(true) {
48 return;
49 }
50 let Some(ticket) = try_acquire(Signal::Metrics) else {
51 return;
52 };
53 self.state
54 .lock()
55 .expect("counter state lock poisoned")
56 .value += value;
57 release(ticket);
58 }
59
60 pub fn value(&self) -> f64 {
61 self.state
62 .lock()
63 .expect("counter state lock poisoned")
64 .value
65 }
66}
67
68#[derive(Clone, Debug, Default)]
69struct GaugeState {
70 last_value: f64,
71}
72
73#[derive(Clone, Debug)]
74pub struct Gauge {
75 name: String,
76 #[allow(dead_code)]
77 description: Option<String>,
78 #[allow(dead_code)]
79 unit: Option<String>,
80 state: Arc<Mutex<GaugeState>>,
81}
82
83impl Gauge {
84 pub fn add(&self, value: f64, _attributes: Option<BTreeMap<String, String>>) {
85 if !metrics_enabled() {
86 return;
87 }
88 if !should_sample(Signal::Metrics, Some(&self.name)).unwrap_or(true) {
89 return;
90 }
91 let Some(ticket) = try_acquire(Signal::Metrics) else {
92 return;
93 };
94 self.state
95 .lock()
96 .expect("gauge state lock poisoned")
97 .last_value += value;
98 release(ticket);
99 }
100
101 pub fn set(&self, value: f64, _attributes: Option<BTreeMap<String, String>>) {
102 if !metrics_enabled() {
103 return;
104 }
105 if !should_sample(Signal::Metrics, Some(&self.name)).unwrap_or(true) {
106 return;
107 }
108 let Some(ticket) = try_acquire(Signal::Metrics) else {
109 return;
110 };
111 self.state
112 .lock()
113 .expect("gauge state lock poisoned")
114 .last_value = value;
115 release(ticket);
116 }
117
118 pub fn value(&self) -> f64 {
119 self.state
120 .lock()
121 .expect("gauge state lock poisoned")
122 .last_value
123 }
124}
125
126#[derive(Clone, Debug, Default)]
127struct HistogramState {
128 count: usize,
129 total: f64,
130}
131
132#[derive(Clone, Debug)]
133pub struct Histogram {
134 name: String,
135 #[allow(dead_code)]
136 description: Option<String>,
137 #[allow(dead_code)]
138 unit: Option<String>,
139 state: Arc<Mutex<HistogramState>>,
140}
141
142impl Histogram {
143 pub fn record(&self, value: f64, _attributes: Option<BTreeMap<String, String>>) {
144 if !metrics_enabled() {
145 return;
146 }
147 if !should_sample(Signal::Metrics, Some(&self.name)).unwrap_or(true) {
148 return;
149 }
150 let Some(ticket) = try_acquire(Signal::Metrics) else {
151 return;
152 };
153 let mut state = self.state.lock().expect("histogram state lock poisoned");
154 state.count += 1;
155 state.total += value;
156 release(ticket);
157 }
158
159 pub fn count(&self) -> usize {
160 self.state
161 .lock()
162 .expect("histogram state lock poisoned")
163 .count
164 }
165
166 pub fn total(&self) -> f64 {
167 self.state
168 .lock()
169 .expect("histogram state lock poisoned")
170 .total
171 }
172}
173
174fn metrics_enabled() -> bool {
175 get_runtime_config()
176 .map(|config| config.metrics.enabled)
177 .unwrap_or(true)
178}
179
180pub fn get_meter(name: Option<&str>) -> Meter {
181 Meter {
182 name: name.unwrap_or("provide.telemetry").to_string(),
183 }
184}
185
186pub fn counter(name: &str, description: Option<&str>, unit: Option<&str>) -> Counter {
187 METRICS_INITIALIZED.store(true, Ordering::SeqCst);
188 Counter {
189 name: name.to_string(),
190 description: description.map(str::to_string),
191 unit: unit.map(str::to_string),
192 state: Arc::new(Mutex::new(CounterState::default())),
193 }
194}
195
196pub fn gauge(name: &str, description: Option<&str>, unit: Option<&str>) -> Gauge {
197 METRICS_INITIALIZED.store(true, Ordering::SeqCst);
198 Gauge {
199 name: name.to_string(),
200 description: description.map(str::to_string),
201 unit: unit.map(str::to_string),
202 state: Arc::new(Mutex::new(GaugeState::default())),
203 }
204}
205
206pub fn histogram(name: &str, description: Option<&str>, unit: Option<&str>) -> Histogram {
207 METRICS_INITIALIZED.store(true, Ordering::SeqCst);
208 Histogram {
209 name: name.to_string(),
210 description: description.map(str::to_string),
211 unit: unit.map(str::to_string),
212 state: Arc::new(Mutex::new(HistogramState::default())),
213 }
214}
215
216pub fn metrics_initialized_for_tests() -> bool {
217 METRICS_INITIALIZED.load(Ordering::SeqCst)
218}
219
220pub fn reset_metrics_for_tests() {
221 METRICS_INITIALIZED.store(false, Ordering::SeqCst);
222}