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.entry(name.to_string()).or_default().push(value);
105 }
106
107 pub fn get_counter(&self, name: &str) -> u64 {
109 self.counters
110 .read()
111 .unwrap()
112 .get(name)
113 .copied()
114 .unwrap_or(0)
115 }
116
117 pub fn get_gauge(&self, name: &str) -> i64 {
119 self.gauges.read().unwrap().get(name).copied().unwrap_or(0)
120 }
121
122 pub fn get_histogram(&self, name: &str) -> Histogram {
124 let values = self
125 .histograms
126 .read()
127 .unwrap()
128 .get(name)
129 .cloned()
130 .unwrap_or_default();
131 Histogram::new(values)
132 }
133
134 pub fn get_counter_with_labels(&self, name: &str, _labels: &[(&str, &str)]) -> u64 {
136 self.get_counter(name)
137 }
138}
139
140#[derive(Debug, Clone)]
142pub struct SpanContext {
143 pub trace_id: String,
145 pub parent_span_id: String,
147 pub sampled: bool,
149}
150
151impl SpanContext {
152 pub fn new(trace_id: &str, parent_span_id: &str) -> Self {
154 Self {
155 trace_id: trace_id.to_string(),
156 parent_span_id: parent_span_id.to_string(),
157 sampled: true,
158 }
159 }
160}
161
162pub struct Histogram {
164 values: Vec<f64>,
165}
166
167impl Histogram {
168 pub fn new(values: Vec<f64>) -> Self {
170 Self { values }
171 }
172
173 pub fn count(&self) -> usize {
175 self.values.len()
176 }
177
178 pub fn sum(&self) -> f64 {
180 self.values.iter().sum()
181 }
182
183 pub fn p50(&self) -> f64 {
185 self.percentile(0.5)
186 }
187
188 pub fn p95(&self) -> f64 {
190 self.percentile(0.95)
191 }
192
193 pub fn p99(&self) -> f64 {
195 self.percentile(0.99)
196 }
197
198 fn percentile(&self, p: f64) -> f64 {
199 if self.values.is_empty() {
200 return 0.0;
201 }
202 let mut sorted = self.values.clone();
203 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
204 let index = ((self.values.len() as f64 - 1.0) * p) as usize;
205 sorted[index]
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn test_span_recorder() {
215 let recorder = SpanRecorder::new();
216
217 let span = Span {
218 span_id: "span-1".to_string(),
219 parent_span_id: None,
220 trace_id: "trace-1".to_string(),
221 name: "test".to_string(),
222 attributes: HashMap::new(),
223 status: "ok".to_string(),
224 error_message: String::new(),
225 duration_ms: 100.0,
226 layer: String::new(),
227 };
228
229 recorder.record(span.clone());
230 let spans = recorder.spans();
231
232 assert_eq!(spans.len(), 1);
233 assert_eq!(spans[0].span_id, "span-1");
234 }
235
236 #[test]
237 fn test_histogram() {
238 let hist = Histogram::new(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
239
240 assert_eq!(hist.count(), 5);
241 assert_eq!(hist.sum(), 15.0);
242 assert_eq!(hist.p50(), 3.0);
243 }
244
245 #[test]
246 fn test_metrics_recorder() {
247 let recorder = MetricsRecorder::new();
248
249 recorder.increment_counter("requests", 1);
250 recorder.increment_counter("requests", 2);
251 assert_eq!(recorder.get_counter("requests"), 3);
252
253 recorder.set_gauge("active_connections", 42);
254 assert_eq!(recorder.get_gauge("active_connections"), 42);
255
256 recorder.record_histogram("latency", 100.0);
257 recorder.record_histogram("latency", 200.0);
258 let hist = recorder.get_histogram("latency");
259 assert_eq!(hist.count(), 2);
260 }
261}