1use bincode::{Decode, Encode};
7use serde::{Deserialize, Serialize};
8use smartstring::alias::String as SmartString;
9use std::fmt;
10
11pub type MetricName = SmartString;
13
14pub type SerializableMetricName = String;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
19pub enum MetricType {
20 Counter,
22 Gauge,
24 Histogram,
26 Latency,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct Counter {
33 pub name: MetricName,
35 pub value: u64,
37 pub timestamp_ns: u64,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Gauge {
44 pub name: MetricName,
46 pub value: f64,
48 pub timestamp_ns: u64,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct Histogram {
55 pub name: MetricName,
57 pub value: f64,
59 pub timestamp_ns: u64,
61 pub bucket: Option<usize>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct LatencyTracker {
68 pub name: MetricName,
70 pub latency_ns: u64,
72 pub timestamp_ns: u64,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub enum Metric {
79 Counter(Counter),
81 Gauge(Gauge),
83 Histogram(Histogram),
85 Latency(LatencyTracker),
87}
88
89impl Metric {
90 pub const fn metric_type(&self) -> MetricType {
92 match self {
93 Metric::Counter(_) => MetricType::Counter,
94 Metric::Gauge(_) => MetricType::Gauge,
95 Metric::Histogram(_) => MetricType::Histogram,
96 Metric::Latency(_) => MetricType::Latency,
97 }
98 }
99
100 #[must_use]
102 pub const fn name(&self) -> &MetricName {
103 match self {
104 Metric::Counter(c) => &c.name,
105 Metric::Gauge(g) => &g.name,
106 Metric::Histogram(h) => &h.name,
107 Metric::Latency(l) => &l.name,
108 }
109 }
110
111 #[must_use]
113 pub const fn timestamp_ns(&self) -> u64 {
114 match self {
115 Metric::Counter(c) => c.timestamp_ns,
116 Metric::Gauge(g) => g.timestamp_ns,
117 Metric::Histogram(h) => h.timestamp_ns,
118 Metric::Latency(l) => l.timestamp_ns,
119 }
120 }
121
122 #[must_use]
124 pub const fn value_as_f64(&self) -> f64 {
125 match self {
126 Metric::Counter(c) => c.value as f64,
127 Metric::Gauge(g) => g.value,
128 Metric::Histogram(h) => h.value,
129 Metric::Latency(l) => l.latency_ns as f64,
130 }
131 }
132}
133
134impl fmt::Display for Metric {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 match self {
138 Metric::Counter(c) => {
139 write!(f, "Counter({}: {} @ {})", c.name, c.value, c.timestamp_ns)
140 }
141 Metric::Gauge(g) => write!(f, "Gauge({}: {} @ {})", g.name, g.value, g.timestamp_ns),
142 Metric::Histogram(h) => {
143 write!(f, "Histogram({}: {} @ {})", h.name, h.value, h.timestamp_ns)
144 }
145 Metric::Latency(l) => write!(
146 f,
147 "Latency({}: {}ns @ {})",
148 l.name, l.latency_ns, l.timestamp_ns
149 ),
150 }
151 }
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
156pub struct AggregatedMetric {
157 pub name: SerializableMetricName,
159 pub metric_type: MetricType,
161 pub count: u64,
163 pub sum: f64,
165 pub min: f64,
167 pub max: f64,
169 pub last_value: f64,
171 pub start_timestamp_ns: u64,
173 pub end_timestamp_ns: u64,
175}
176
177impl AggregatedMetric {
178 #[must_use]
180 pub fn from_metric(metric: &Metric) -> Self {
181 let value = metric.value_as_f64();
182 Self {
183 name: metric.name().to_string(),
184 metric_type: metric.metric_type(),
185 count: 1,
186 sum: value,
187 min: value,
188 max: value,
189 last_value: value,
190 start_timestamp_ns: metric.timestamp_ns(),
191 end_timestamp_ns: metric.timestamp_ns(),
192 }
193 }
194
195 pub fn update(&mut self, metric: &Metric) {
197 let value = metric.value_as_f64();
198 self.count += 1;
199 self.sum += value;
200 self.min = self.min.min(value);
201 self.max = self.max.max(value);
202 self.last_value = value;
203 self.end_timestamp_ns = metric.timestamp_ns();
204 }
205
206 #[must_use]
208 pub fn average(&self) -> f64 {
209 if self.count > 0 {
210 self.sum / self.count as f64
211 } else {
212 0.0
213 }
214 }
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
219pub struct MetricsSnapshot {
220 pub timestamp_ns: u64,
222 pub metrics: Vec<AggregatedMetric>,
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_counter_metric() {
232 let counter = Counter {
233 name: "orders_placed".into(),
234 value: 100,
235 timestamp_ns: 123456789,
236 };
237
238 let metric = Metric::Counter(counter.clone());
239 assert_eq!(metric.metric_type(), MetricType::Counter);
240 assert_eq!(metric.name().as_str(), "orders_placed");
241 assert_eq!(metric.timestamp_ns(), 123456789);
242 assert_eq!(metric.value_as_f64(), 100.0);
243 }
244
245 #[test]
246 fn test_gauge_metric() {
247 let gauge = Gauge {
248 name: "position_pnl".into(),
249 value: -1234.56,
250 timestamp_ns: 987654321,
251 };
252
253 let metric = Metric::Gauge(gauge.clone());
254 assert_eq!(metric.metric_type(), MetricType::Gauge);
255 assert_eq!(metric.name().as_str(), "position_pnl");
256 assert_eq!(metric.value_as_f64(), -1234.56);
257 }
258
259 #[test]
260 fn test_latency_metric() {
261 let latency = LatencyTracker {
262 name: "order_rtt".into(),
263 latency_ns: 150_000, timestamp_ns: 1234567890,
265 };
266
267 let metric = Metric::Latency(latency.clone());
268 assert_eq!(metric.metric_type(), MetricType::Latency);
269 assert_eq!(metric.value_as_f64(), 150_000.0);
270 }
271
272 #[test]
273 fn test_aggregated_metric() {
274 let counter1 = Metric::Counter(Counter {
275 name: "test".into(),
276 value: 10,
277 timestamp_ns: 1000,
278 });
279
280 let counter2 = Metric::Counter(Counter {
281 name: "test".into(),
282 value: 20,
283 timestamp_ns: 2000,
284 });
285
286 let counter3 = Metric::Counter(Counter {
287 name: "test".into(),
288 value: 5,
289 timestamp_ns: 3000,
290 });
291
292 let mut agg = AggregatedMetric::from_metric(&counter1);
293 agg.update(&counter2);
294 agg.update(&counter3);
295
296 assert_eq!(agg.count, 3);
297 assert_eq!(agg.sum, 35.0);
298 assert_eq!(agg.min, 5.0);
299 assert_eq!(agg.max, 20.0);
300 assert_eq!(agg.average(), 35.0 / 3.0);
301 assert_eq!(agg.last_value, 5.0);
302 assert_eq!(agg.start_timestamp_ns, 1000);
303 assert_eq!(agg.end_timestamp_ns, 3000);
304 }
305
306 #[test]
307 fn test_metric_display() {
308 let counter = Metric::Counter(Counter {
309 name: "test_counter".into(),
310 value: 42,
311 timestamp_ns: 1234,
312 });
313
314 let display = format!("{counter}");
315 assert!(display.contains("Counter"));
316 assert!(display.contains("test_counter"));
317 assert!(display.contains("42"));
318 assert!(display.contains("1234"));
319 }
320}