1use std::collections::BTreeMap;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::sync::{Arc, Mutex};
9
10use crate::backpressure::{release, try_acquire};
11use crate::consent::should_allow;
12use crate::health::increment_emitted;
13use crate::runtime::get_runtime_config;
14use crate::sampling::{should_sample, Signal};
15
16static METRICS_INITIALIZED: AtomicBool = AtomicBool::new(false);
17
18#[cfg(feature = "otel")]
19fn maybe_record_counter_add(name: &str, value: f64, attributes: Option<&BTreeMap<String, String>>) {
20 if !crate::otel::metrics::meter_provider_installed() {
21 return;
22 }
23 crate::otel::metrics::record_counter_add(name, value, attributes);
24}
25
26#[cfg(feature = "otel")]
27fn maybe_record_gauge_set(name: &str, value: f64, attributes: Option<&BTreeMap<String, String>>) {
28 if !crate::otel::metrics::meter_provider_installed() {
29 return;
30 }
31 crate::otel::metrics::record_gauge_set(name, value, attributes);
32}
33
34#[cfg(feature = "otel")]
35fn maybe_record_histogram(name: &str, value: f64, attributes: Option<&BTreeMap<String, String>>) {
36 if !crate::otel::metrics::meter_provider_installed() {
37 return;
38 }
39 crate::otel::metrics::record_histogram(name, value, attributes);
40}
41
42#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct Meter {
44 name: String,
45}
46
47impl Meter {
48 pub fn name(&self) -> &str {
49 &self.name
50 }
51}
52
53#[derive(Clone, Debug, Default)]
54struct CounterState {
55 value: f64,
56}
57
58#[derive(Clone, Debug)]
59pub struct Counter {
60 name: String,
61 #[allow(dead_code)]
62 description: Option<String>,
63 #[allow(dead_code)]
64 unit: Option<String>,
65 state: Arc<Mutex<CounterState>>,
66}
67
68impl Counter {
69 pub fn add(&self, value: f64, attributes: Option<BTreeMap<String, String>>) {
70 if !metrics_enabled() {
71 return;
72 }
73 if !should_allow("metrics", None) {
74 return;
75 }
76 if !should_sample(Signal::Metrics, Some(&self.name)).unwrap_or(true) {
77 return;
78 }
79 let acquired = try_acquire(Signal::Metrics);
80 if acquired.is_none() {
81 return;
82 }
83 let ticket = acquired.expect("metrics ticket must exist after none guard");
84 crate::_lock::lock(&self.state).value += value;
85 #[cfg(feature = "otel")]
86 {
87 maybe_record_counter_add(&self.name, value, attributes.as_ref());
88 }
89 #[cfg(not(feature = "otel"))]
90 let _ = &attributes;
91 increment_emitted(Signal::Metrics, 1);
92 release(ticket);
93 }
94
95 pub fn value(&self) -> f64 {
96 crate::_lock::lock(&self.state).value
97 }
98}
99
100#[derive(Clone, Debug, Default)]
101struct GaugeState {
102 last_value: f64,
103}
104
105#[derive(Clone, Debug)]
106pub struct Gauge {
107 name: String,
108 #[allow(dead_code)]
109 description: Option<String>,
110 #[allow(dead_code)]
111 unit: Option<String>,
112 state: Arc<Mutex<GaugeState>>,
113}
114
115impl Gauge {
116 pub fn add(&self, value: f64, attributes: Option<BTreeMap<String, String>>) {
117 if !metrics_enabled() {
118 return;
119 }
120 if !should_allow("metrics", None) {
121 return;
122 }
123 if !should_sample(Signal::Metrics, Some(&self.name)).unwrap_or(true) {
124 return;
125 }
126 let acquired = try_acquire(Signal::Metrics);
127 if acquired.is_none() {
128 return;
129 }
130 let ticket = acquired.expect("metrics ticket must exist after none guard");
131 #[cfg_attr(not(feature = "otel"), allow(unused_variables))]
132 let new_absolute = {
133 let mut state = crate::_lock::lock(&self.state);
134 state.last_value += value;
135 state.last_value
136 };
137 #[cfg(feature = "otel")]
138 {
139 maybe_record_gauge_set(&self.name, new_absolute, attributes.as_ref());
140 }
141 #[cfg(not(feature = "otel"))]
142 let _ = &attributes;
143 increment_emitted(Signal::Metrics, 1);
144 release(ticket);
145 }
146
147 pub fn set(&self, value: f64, attributes: Option<BTreeMap<String, String>>) {
148 if !metrics_enabled() {
149 return;
150 }
151 if !should_allow("metrics", None) {
152 return;
153 }
154 if !should_sample(Signal::Metrics, Some(&self.name)).unwrap_or(true) {
155 return;
156 }
157 let acquired = try_acquire(Signal::Metrics);
158 if acquired.is_none() {
159 return;
160 }
161 let ticket = acquired.expect("metrics ticket must exist after none guard");
162 crate::_lock::lock(&self.state).last_value = value;
163 #[cfg(feature = "otel")]
164 {
165 maybe_record_gauge_set(&self.name, value, attributes.as_ref());
166 }
167 #[cfg(not(feature = "otel"))]
168 let _ = &attributes;
169 increment_emitted(Signal::Metrics, 1);
170 release(ticket);
171 }
172
173 pub fn value(&self) -> f64 {
174 crate::_lock::lock(&self.state).last_value
175 }
176}
177
178#[derive(Clone, Debug, Default)]
179struct HistogramState {
180 count: usize,
181 total: f64,
182}
183
184#[derive(Clone, Debug)]
185pub struct Histogram {
186 name: String,
187 #[allow(dead_code)]
188 description: Option<String>,
189 #[allow(dead_code)]
190 unit: Option<String>,
191 state: Arc<Mutex<HistogramState>>,
192}
193
194impl Histogram {
195 pub fn record(&self, value: f64, attributes: Option<BTreeMap<String, String>>) {
196 if !metrics_enabled() {
197 return;
198 }
199 if !should_allow("metrics", None) {
200 return;
201 }
202 if !should_sample(Signal::Metrics, Some(&self.name)).unwrap_or(true) {
203 return;
204 }
205 let acquired = try_acquire(Signal::Metrics);
206 if acquired.is_none() {
207 return;
208 }
209 let ticket = acquired.expect("metrics ticket must exist after none guard");
210 let mut state = crate::_lock::lock(&self.state);
211 state.count += 1;
212 state.total += value;
213 drop(state);
214 #[cfg(feature = "otel")]
215 {
216 maybe_record_histogram(&self.name, value, attributes.as_ref());
217 }
218 #[cfg(not(feature = "otel"))]
219 let _ = &attributes;
220 increment_emitted(Signal::Metrics, 1);
221 release(ticket);
222 }
223
224 pub fn count(&self) -> usize {
225 crate::_lock::lock(&self.state).count
226 }
227
228 pub fn total(&self) -> f64 {
229 crate::_lock::lock(&self.state).total
230 }
231}
232
233fn metrics_enabled() -> bool {
234 get_runtime_config()
235 .map(|config| config.metrics.enabled)
236 .unwrap_or(true)
237}
238
239pub fn get_meter(name: Option<&str>) -> Meter {
240 Meter {
241 name: name.unwrap_or("provide.telemetry").to_string(),
242 }
243}
244
245pub fn counter(name: &str, description: Option<&str>, unit: Option<&str>) -> Counter {
246 METRICS_INITIALIZED.store(true, Ordering::SeqCst);
247 Counter {
248 name: name.to_string(),
249 description: description.map(str::to_string),
250 unit: unit.map(str::to_string),
251 state: Arc::new(Mutex::new(CounterState::default())),
252 }
253}
254
255pub fn gauge(name: &str, description: Option<&str>, unit: Option<&str>) -> Gauge {
256 METRICS_INITIALIZED.store(true, Ordering::SeqCst);
257 Gauge {
258 name: name.to_string(),
259 description: description.map(str::to_string),
260 unit: unit.map(str::to_string),
261 state: Arc::new(Mutex::new(GaugeState::default())),
262 }
263}
264
265pub fn histogram(name: &str, description: Option<&str>, unit: Option<&str>) -> Histogram {
266 METRICS_INITIALIZED.store(true, Ordering::SeqCst);
267 Histogram {
268 name: name.to_string(),
269 description: description.map(str::to_string),
270 unit: unit.map(str::to_string),
271 state: Arc::new(Mutex::new(HistogramState::default())),
272 }
273}
274
275pub fn metrics_initialized_for_tests() -> bool {
276 METRICS_INITIALIZED.load(Ordering::SeqCst)
277}
278
279pub fn reset_metrics_for_tests() {
280 METRICS_INITIALIZED.store(false, Ordering::SeqCst);
281}
282
283#[cfg(test)]
284#[path = "metrics_tests.rs"]
285mod tests;