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 pub fn increment(&self, delta: u64) {
110 #[cfg(feature = "prometheus")]
111 {
112 let _ = metrics::counter!(self.name.clone()).increment(delta);
113 }
114
115 #[cfg(not(feature = "prometheus"))]
116 {
117 self.storage.increment_counter(&self.name, delta);
118 }
119 }
120
121 pub fn get(&self) -> u64 {
123 self.storage
124 .get(&self.name)
125 .map(|v| v.as_counter())
126 .unwrap_or(0)
127 }
128}
129
130#[derive(Debug, Clone)]
132pub struct Histogram {
133 name: String,
134 storage: Arc<MetricsStorage>,
135}
136
137impl Histogram {
138 pub fn new(name: impl Into<String>, storage: Arc<MetricsStorage>) -> Self {
140 Self {
141 name: name.into(),
142 storage,
143 }
144 }
145
146 pub fn record(&self, value: f64) {
148 #[cfg(feature = "prometheus")]
149 {
150 let _ = metrics::histogram!(self.name.clone()).record(value);
151 }
152
153 #[cfg(not(feature = "prometheus"))]
154 {
155 self.storage.record_histogram(&self.name, value);
156 }
157 }
158
159 pub fn get_values(&self) -> Vec<f64> {
161 self.storage
162 .get(&self.name)
163 .map(|v| v.as_histogram().to_vec())
164 .unwrap_or_default()
165 }
166}
167
168#[derive(Debug, Clone)]
170pub struct Gauge {
171 name: String,
172 storage: Arc<MetricsStorage>,
173}
174
175impl Gauge {
176 pub fn new(name: impl Into<String>, storage: Arc<MetricsStorage>) -> Self {
178 Self {
179 name: name.into(),
180 storage,
181 }
182 }
183
184 pub fn set(&self, value: f64) {
186 #[cfg(feature = "prometheus")]
187 {
188 let _ = metrics::gauge!(self.name.clone()).set(value);
189 }
190
191 #[cfg(not(feature = "prometheus"))]
192 {
193 self.storage.set_gauge(&self.name, value);
194 }
195 }
196
197 pub fn get(&self) -> f64 {
199 self.storage
200 .get(&self.name)
201 .map(|v| v.as_gauge())
202 .unwrap_or(0.0)
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_counter_increment() {
212 let storage = Arc::new(MetricsStorage::new());
213 let counter = Counter::new("test_counter", storage);
214
215 counter.increment(5);
216 assert_eq!(counter.get(), 5);
217
218 counter.increment(3);
219 assert_eq!(counter.get(), 8);
220 }
221
222 #[test]
223 fn test_gauge_set() {
224 let storage = Arc::new(MetricsStorage::new());
225 let gauge = Gauge::new("test_gauge", storage);
226
227 gauge.set(42.0);
228 assert_eq!(gauge.get(), 42.0);
229
230 gauge.set(100.0);
231 assert_eq!(gauge.get(), 100.0);
232 }
233
234 #[test]
235 fn test_histogram_record() {
236 let storage = Arc::new(MetricsStorage::new());
237 let histogram = Histogram::new("test_histogram", storage);
238
239 histogram.record(1.0);
240 histogram.record(2.0);
241 histogram.record(3.0);
242
243 let values = histogram.get_values();
244 assert_eq!(values, vec![1.0, 2.0, 3.0]);
245 }
246
247 #[test]
248 fn test_metrics_storage_list_names() {
249 let storage = Arc::new(MetricsStorage::new());
250 let counter = Counter::new("counter1", Arc::clone(&storage));
251 let gauge = Gauge::new("gauge1", Arc::clone(&storage));
252
253 counter.increment(1);
254 gauge.set(1.0);
255
256 let names = storage.list_names();
257 assert_eq!(names.len(), 2);
258 assert!(names.contains(&"counter1".to_string()));
259 assert!(names.contains(&"gauge1".to_string()));
260 }
261}