allframe_core/otel/
testing.rs1use std::{
7 collections::HashMap,
8 sync::{Arc, RwLock},
9};
10
11#[derive(Debug, Clone)]
13pub struct Span {
14 pub span_id: String,
16 pub parent_span_id: Option<String>,
18 pub trace_id: String,
20 pub name: String,
22 pub attributes: HashMap<String, String>,
24 pub status: String,
26 pub error_message: String,
28 pub duration_ms: f64,
30 pub layer: String,
32}
33
34#[derive(Clone, Default)]
36pub struct SpanRecorder {
37 spans: Arc<RwLock<Vec<Span>>>,
38}
39
40impl SpanRecorder {
41 pub fn new() -> Self {
43 Self {
44 spans: Arc::new(RwLock::new(Vec::new())),
45 }
46 }
47
48 pub fn record(&self, span: Span) {
50 let mut spans = self.spans.write().unwrap();
51 spans.push(span);
52 }
53
54 pub fn spans(&self) -> Vec<Span> {
56 self.spans.read().unwrap().clone()
57 }
58
59 pub fn clear(&self) {
61 let mut spans = self.spans.write().unwrap();
62 spans.clear();
63 }
64}
65
66#[derive(Clone, Default)]
68pub struct MetricsRecorder {
69 counters: Arc<RwLock<HashMap<String, u64>>>,
70 gauges: Arc<RwLock<HashMap<String, i64>>>,
71 histograms: Arc<RwLock<HashMap<String, Vec<f64>>>>,
72}
73
74impl MetricsRecorder {
75 pub fn new() -> Self {
77 Self {
78 counters: Arc::new(RwLock::new(HashMap::new())),
79 gauges: Arc::new(RwLock::new(HashMap::new())),
80 histograms: Arc::new(RwLock::new(HashMap::new())),
81 }
82 }
83
84 pub fn current() -> Self {
86 Self::new()
87 }
88
89 pub fn increment_counter(&self, name: &str, value: u64) {
91 let mut counters = self.counters.write().unwrap();
92 *counters.entry(name.to_string()).or_insert(0) += value;
93 }
94
95 pub fn set_gauge(&self, name: &str, value: i64) {
97 let mut gauges = self.gauges.write().unwrap();
98 gauges.insert(name.to_string(), value);
99 }
100
101 pub fn record_histogram(&self, name: &str, value: f64) {
103 let mut histograms = self.histograms.write().unwrap();
104 histograms
105 .entry(name.to_string())
106 .or_insert_with(Vec::new)
107 .push(value);
108 }
109
110 pub fn get_counter(&self, name: &str) -> u64 {
112 self.counters
113 .read()
114 .unwrap()
115 .get(name)
116 .copied()
117 .unwrap_or(0)
118 }
119
120 pub fn get_gauge(&self, name: &str) -> i64 {
122 self.gauges.read().unwrap().get(name).copied().unwrap_or(0)
123 }
124
125 pub fn get_histogram(&self, name: &str) -> Histogram {
127 let values = self
128 .histograms
129 .read()
130 .unwrap()
131 .get(name)
132 .cloned()
133 .unwrap_or_default();
134 Histogram::new(values)
135 }
136
137 pub fn get_counter_with_labels(&self, name: &str, _labels: &[(&str, &str)]) -> u64 {
139 self.get_counter(name)
140 }
141}
142
143#[derive(Debug, Clone)]
145pub struct SpanContext {
146 pub trace_id: String,
148 pub parent_span_id: String,
150 pub sampled: bool,
152}
153
154impl SpanContext {
155 pub fn new(trace_id: &str, parent_span_id: &str) -> Self {
157 Self {
158 trace_id: trace_id.to_string(),
159 parent_span_id: parent_span_id.to_string(),
160 sampled: true,
161 }
162 }
163}
164
165pub struct Histogram {
167 values: Vec<f64>,
168}
169
170impl Histogram {
171 pub fn new(values: Vec<f64>) -> Self {
173 Self { values }
174 }
175
176 pub fn count(&self) -> usize {
178 self.values.len()
179 }
180
181 pub fn sum(&self) -> f64 {
183 self.values.iter().sum()
184 }
185
186 pub fn p50(&self) -> f64 {
188 self.percentile(0.5)
189 }
190
191 pub fn p95(&self) -> f64 {
193 self.percentile(0.95)
194 }
195
196 pub fn p99(&self) -> f64 {
198 self.percentile(0.99)
199 }
200
201 fn percentile(&self, p: f64) -> f64 {
202 if self.values.is_empty() {
203 return 0.0;
204 }
205 let mut sorted = self.values.clone();
206 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
207 let index = ((self.values.len() as f64 - 1.0) * p) as usize;
208 sorted[index]
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_span_recorder() {
218 let recorder = SpanRecorder::new();
219
220 let span = Span {
221 span_id: "span-1".to_string(),
222 parent_span_id: None,
223 trace_id: "trace-1".to_string(),
224 name: "test".to_string(),
225 attributes: HashMap::new(),
226 status: "ok".to_string(),
227 error_message: String::new(),
228 duration_ms: 100.0,
229 layer: String::new(),
230 };
231
232 recorder.record(span.clone());
233 let spans = recorder.spans();
234
235 assert_eq!(spans.len(), 1);
236 assert_eq!(spans[0].span_id, "span-1");
237 }
238
239 #[test]
240 fn test_histogram() {
241 let hist = Histogram::new(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
242
243 assert_eq!(hist.count(), 5);
244 assert_eq!(hist.sum(), 15.0);
245 assert_eq!(hist.p50(), 3.0);
246 }
247
248 #[test]
249 fn test_metrics_recorder() {
250 let recorder = MetricsRecorder::new();
251
252 recorder.increment_counter("requests", 1);
253 recorder.increment_counter("requests", 2);
254 assert_eq!(recorder.get_counter("requests"), 3);
255
256 recorder.set_gauge("active_connections", 42);
257 assert_eq!(recorder.get_gauge("active_connections"), 42);
258
259 recorder.record_histogram("latency", 100.0);
260 recorder.record_histogram("latency", 200.0);
261 let hist = recorder.get_histogram("latency");
262 assert_eq!(hist.count(), 2);
263 }
264}