sh_layer1/observability/
metrics.rs1use parking_lot::RwLock;
4use std::collections::HashMap;
5use std::sync::Arc;
6
7#[derive(Debug, Clone)]
9pub enum MetricValue {
10 Counter(u64),
11 Gauge(f64),
12 Histogram(Vec<f64>),
13}
14
15impl MetricValue {
16 pub fn as_counter(&self) -> u64 {
18 match self {
19 MetricValue::Counter(v) => *v,
20 _ => 0,
21 }
22 }
23
24 pub fn as_gauge(&self) -> f64 {
26 match self {
27 MetricValue::Gauge(v) => *v,
28 _ => 0.0,
29 }
30 }
31
32 pub fn as_histogram(&self) -> &[f64] {
34 match self {
35 MetricValue::Histogram(v) => v,
36 _ => &[],
37 }
38 }
39}
40
41#[derive(Debug, Default)]
43pub struct MetricsStorage {
44 metrics: RwLock<HashMap<String, MetricValue>>,
45}
46
47impl MetricsStorage {
48 pub fn new() -> Self {
50 Self::default()
51 }
52
53 pub fn increment_counter(&self, name: &str, delta: u64) {
55 let mut metrics = self.metrics.write();
56 let entry = metrics
57 .entry(name.to_string())
58 .or_insert(MetricValue::Counter(0));
59 if let MetricValue::Counter(v) = entry {
60 *v += delta;
61 }
62 }
63
64 pub fn set_gauge(&self, name: &str, value: f64) {
66 let mut metrics = self.metrics.write();
67 metrics.insert(name.to_string(), MetricValue::Gauge(value));
68 }
69
70 pub fn record_histogram(&self, name: &str, value: f64) {
72 let mut metrics = self.metrics.write();
73 let entry = metrics
74 .entry(name.to_string())
75 .or_insert(MetricValue::Histogram(Vec::new()));
76 if let MetricValue::Histogram(v) = entry {
77 v.push(value);
78 }
79 }
80
81 pub fn get(&self, name: &str) -> Option<MetricValue> {
83 self.metrics.read().get(name).cloned()
84 }
85
86 pub fn list_names(&self) -> Vec<String> {
88 self.metrics.read().keys().cloned().collect()
89 }
90}
91
92#[derive(Debug, Clone)]
94pub struct Counter {
95 name: String,
96 storage: Arc<MetricsStorage>,
97}
98
99impl Counter {
100 pub fn new(name: impl Into<String>, storage: Arc<MetricsStorage>) -> Self {
102 Self {
103 name: name.into(),
104 storage,
105 }
106 }
107
108 #[allow(clippy::let_unit_value)]
110 pub fn increment(&self, delta: u64) {
111 #[cfg(feature = "prometheus")]
112 {
113 let _ = metrics::counter!(self.name.clone()).increment(delta);
114 }
115
116 #[cfg(not(feature = "prometheus"))]
117 {
118 self.storage.increment_counter(&self.name, delta);
119 }
120 }
121
122 pub fn get(&self) -> u64 {
124 self.storage
125 .get(&self.name)
126 .map(|v| v.as_counter())
127 .unwrap_or(0)
128 }
129}
130
131#[derive(Debug, Clone)]
133pub struct Histogram {
134 name: String,
135 storage: Arc<MetricsStorage>,
136}
137
138impl Histogram {
139 pub fn new(name: impl Into<String>, storage: Arc<MetricsStorage>) -> Self {
141 Self {
142 name: name.into(),
143 storage,
144 }
145 }
146
147 #[allow(clippy::let_unit_value)]
149 pub fn record(&self, value: f64) {
150 #[cfg(feature = "prometheus")]
151 {
152 let _ = metrics::histogram!(self.name.clone()).record(value);
153 }
154
155 #[cfg(not(feature = "prometheus"))]
156 {
157 self.storage.record_histogram(&self.name, value);
158 }
159 }
160
161 pub fn get_values(&self) -> Vec<f64> {
163 self.storage
164 .get(&self.name)
165 .map(|v| v.as_histogram().to_vec())
166 .unwrap_or_default()
167 }
168}
169
170#[derive(Debug, Clone)]
172pub struct Gauge {
173 name: String,
174 storage: Arc<MetricsStorage>,
175}
176
177impl Gauge {
178 pub fn new(name: impl Into<String>, storage: Arc<MetricsStorage>) -> Self {
180 Self {
181 name: name.into(),
182 storage,
183 }
184 }
185
186 #[allow(clippy::let_unit_value)]
188 pub fn set(&self, value: f64) {
189 #[cfg(feature = "prometheus")]
190 {
191 let _ = metrics::gauge!(self.name.clone()).set(value);
192 }
193
194 #[cfg(not(feature = "prometheus"))]
195 {
196 self.storage.set_gauge(&self.name, value);
197 }
198 }
199
200 pub fn get(&self) -> f64 {
202 self.storage
203 .get(&self.name)
204 .map(|v| v.as_gauge())
205 .unwrap_or(0.0)
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn test_counter_increment() {
215 let storage = Arc::new(MetricsStorage::new());
216 let counter = Counter::new("test_counter", storage);
217
218 counter.increment(5);
219 assert_eq!(counter.get(), 5);
220
221 counter.increment(3);
222 assert_eq!(counter.get(), 8);
223 }
224
225 #[test]
226 fn test_gauge_set() {
227 let storage = Arc::new(MetricsStorage::new());
228 let gauge = Gauge::new("test_gauge", storage);
229
230 gauge.set(42.0);
231 assert_eq!(gauge.get(), 42.0);
232
233 gauge.set(100.0);
234 assert_eq!(gauge.get(), 100.0);
235 }
236
237 #[test]
238 fn test_histogram_record() {
239 let storage = Arc::new(MetricsStorage::new());
240 let histogram = Histogram::new("test_histogram", storage);
241
242 histogram.record(1.0);
243 histogram.record(2.0);
244 histogram.record(3.0);
245
246 let values = histogram.get_values();
247 assert_eq!(values, vec![1.0, 2.0, 3.0]);
248 }
249
250 #[test]
251 fn test_metrics_storage_list_names() {
252 let storage = Arc::new(MetricsStorage::new());
253 let counter = Counter::new("counter1", Arc::clone(&storage));
254 let gauge = Gauge::new("gauge1", Arc::clone(&storage));
255
256 counter.increment(1);
257 gauge.set(1.0);
258
259 let names = storage.list_names();
260 assert_eq!(names.len(), 2);
261 assert!(names.contains(&"counter1".to_string()));
262 assert!(names.contains(&"gauge1".to_string()));
263 }
264}