1use serde::{Deserialize, Serialize};
4use std::time::Duration;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8#[serde(untagged)]
9pub enum MetricValue {
10 Counter(u64),
11 Gauge(f64),
12 Histogram(HistogramValue),
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct HistogramValue {
18 pub count: u64,
19 pub sum: f64,
20 pub buckets: Vec<(f64, u64)>,
21}
22
23impl HistogramValue {
24 pub fn new() -> Self {
25 Self {
26 count: 0,
27 sum: 0.0,
28 buckets: vec![
29 (0.005, 0), (0.01, 0), (0.025, 0), (0.05, 0), (0.1, 0), (0.25, 0), (0.5, 0), (1.0, 0), (2.5, 0), (5.0, 0), (10.0, 0), (f64::INFINITY, 0),
41 ],
42 }
43 }
44
45 pub fn observe(&mut self, value: f64) {
46 self.count += 1;
47 self.sum += value;
48
49 for (bound, count) in &mut self.buckets {
50 if value <= *bound {
51 *count += 1;
52 }
53 }
54 }
55}
56
57impl Default for HistogramValue {
58 fn default() -> Self {
59 Self::new()
60 }
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct Metric {
66 pub name: String,
67 pub help: String,
68 pub metric_type: MetricType,
69 pub value: MetricValue,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 pub labels: Option<Vec<(String, String)>>,
72}
73
74impl Metric {
75 pub fn counter(name: impl Into<String>, help: impl Into<String>, value: u64) -> Self {
76 Self {
77 name: name.into(),
78 help: help.into(),
79 metric_type: MetricType::Counter,
80 value: MetricValue::Counter(value),
81 labels: None,
82 }
83 }
84
85 pub fn gauge(name: impl Into<String>, help: impl Into<String>, value: f64) -> Self {
86 Self {
87 name: name.into(),
88 help: help.into(),
89 metric_type: MetricType::Gauge,
90 value: MetricValue::Gauge(value),
91 labels: None,
92 }
93 }
94
95 pub fn histogram(name: impl Into<String>, help: impl Into<String>, histogram: HistogramValue) -> Self {
96 Self {
97 name: name.into(),
98 help: help.into(),
99 metric_type: MetricType::Histogram,
100 value: MetricValue::Histogram(histogram),
101 labels: None,
102 }
103 }
104
105 pub fn with_label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
106 let labels = self.labels.get_or_insert_with(Vec::new);
107 labels.push((key.into(), value.into()));
108 self
109 }
110
111 pub fn with_labels(mut self, labels: Vec<(String, String)>) -> Self {
112 self.labels = Some(labels);
113 self
114 }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
119#[serde(rename_all = "lowercase")]
120pub enum MetricType {
121 Counter,
122 Gauge,
123 Histogram,
124 Summary,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct TimeSeriesPoint {
130 pub timestamp: i64,
131 pub value: f64,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct TimeSeries {
137 pub name: String,
138 pub points: Vec<TimeSeriesPoint>,
139}
140
141impl TimeSeries {
142 pub fn new(name: impl Into<String>) -> Self {
143 Self {
144 name: name.into(),
145 points: Vec::new(),
146 }
147 }
148
149 pub fn add_point(&mut self, timestamp: i64, value: f64) {
150 self.points.push(TimeSeriesPoint { timestamp, value });
151 }
152}
153
154pub trait DurationExt {
156 fn as_millis_f64(&self) -> f64;
157 fn as_micros_f64(&self) -> f64;
158}
159
160impl DurationExt for Duration {
161 fn as_millis_f64(&self) -> f64 {
162 self.as_secs_f64() * 1000.0
163 }
164
165 fn as_micros_f64(&self) -> f64 {
166 self.as_secs_f64() * 1_000_000.0
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn test_histogram() {
176 let mut hist = HistogramValue::new();
177 hist.observe(0.001); hist.observe(0.050); hist.observe(0.500); assert_eq!(hist.count, 3);
182 }
183
184 #[test]
185 fn test_metric_with_labels() {
186 let metric = Metric::counter("http_requests_total", "Total HTTP requests", 100)
187 .with_label("method", "GET")
188 .with_label("status", "200");
189
190 assert_eq!(metric.labels.as_ref().unwrap().len(), 2);
191 }
192
193 #[test]
194 fn test_duration_ext() {
195 let duration = Duration::from_millis(150);
196 assert_eq!(duration.as_millis_f64(), 150.0);
197 }
198}
199