rusty_engine/monitoring/
metrics.rs

1//! Metric types and structures for the monitoring system
2//!
3//! This module defines the various metric types supported by the monitoring framework,
4//! including counters, gauges, histograms, and latency tracking.
5
6use bincode::{Decode, Encode};
7use serde::{Deserialize, Serialize};
8use smartstring::alias::String as SmartString;
9use std::fmt;
10
11/// A metric name optimized for small strings
12pub type MetricName = SmartString;
13
14/// A metric name for bincode-serialized structures (uses String for compatibility)
15pub type SerializableMetricName = String;
16
17/// The type of metric being recorded
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
19pub enum MetricType {
20    /// Counter metric that only increases (e.g., order count, trade volume)
21    Counter,
22    /// Gauge metric that can increase or decrease (e.g., P&L, position size)
23    Gauge,
24    /// Histogram metric for recording value distributions (e.g., trade sizes, spreads)
25    Histogram,
26    /// Latency metric for timing measurements (e.g., order RTT, execution time)
27    Latency,
28}
29
30/// A counter metric that only increases
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct Counter {
33    /// The name of the counter metric
34    pub name: MetricName,
35    /// The current value of the counter (monotonically increasing)
36    pub value: u64,
37    /// Timestamp when the counter was recorded (nanoseconds since epoch)
38    pub timestamp_ns: u64,
39}
40
41/// A gauge metric that can go up or down
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Gauge {
44    /// The name of the gauge metric
45    pub name: MetricName,
46    /// The current value of the gauge (can increase or decrease)
47    pub value: f64,
48    /// Timestamp when the gauge was recorded (nanoseconds since epoch)
49    pub timestamp_ns: u64,
50}
51
52/// A histogram metric for recording distributions
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct Histogram {
55    /// The name of the histogram metric
56    pub name: MetricName,
57    /// The value being recorded in the histogram
58    pub value: f64,
59    /// Timestamp when the histogram value was recorded (nanoseconds since epoch)
60    pub timestamp_ns: u64,
61    /// Optional bucket for pre-aggregation
62    pub bucket: Option<usize>,
63}
64
65/// A latency tracking metric optimized for timing measurements
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct LatencyTracker {
68    /// The name of the latency metric
69    pub name: MetricName,
70    /// The latency measurement in nanoseconds
71    pub latency_ns: u64,
72    /// Timestamp when the latency was recorded (nanoseconds since epoch)
73    pub timestamp_ns: u64,
74}
75
76/// Unified metric type that can hold any metric variant
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub enum Metric {
79    /// Counter metric variant for monotonically increasing values
80    Counter(Counter),
81    /// Gauge metric variant for values that can increase or decrease
82    Gauge(Gauge),
83    /// Histogram metric variant for recording value distributions
84    Histogram(Histogram),
85    /// Latency metric variant for timing measurements
86    Latency(LatencyTracker),
87}
88
89impl Metric {
90    /// Get the metric type
91    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    /// Get the metric name
101    #[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    /// Get the timestamp in nanoseconds
112    #[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    /// Get the metric value as f64 (for aggregation)
123    #[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    /// Format the metric for display with name, value, and timestamp
136    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/// Aggregated metrics for efficient storage and transmission
155#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
156pub struct AggregatedMetric {
157    /// The name of the aggregated metric
158    pub name: SerializableMetricName,
159    /// The type of metric being aggregated
160    pub metric_type: MetricType,
161    /// The number of individual metrics aggregated
162    pub count: u64,
163    /// The sum of all values in the aggregation
164    pub sum: f64,
165    /// The minimum value seen during aggregation
166    pub min: f64,
167    /// The maximum value seen during aggregation
168    pub max: f64,
169    /// The last value recorded in the aggregation
170    pub last_value: f64,
171    /// Timestamp of the first metric in the aggregation (nanoseconds since epoch)
172    pub start_timestamp_ns: u64,
173    /// Timestamp of the last metric in the aggregation (nanoseconds since epoch)
174    pub end_timestamp_ns: u64,
175}
176
177impl AggregatedMetric {
178    /// Create a new aggregated metric from a single metric
179    #[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    /// Update the aggregated metric with a new value
196    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    /// Calculate the average value
207    #[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/// Metrics snapshot for persistence
218#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
219pub struct MetricsSnapshot {
220    /// Timestamp when the snapshot was taken (nanoseconds since epoch)
221    pub timestamp_ns: u64,
222    /// Collection of aggregated metrics captured in this snapshot
223    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, // 150 microseconds
264            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}