Skip to main content

cbtop/prometheus/
registry.rs

1//! Prometheus metrics registry.
2
3use std::collections::HashMap;
4
5use super::types::{
6    CounterValue, GaugeValue, HistogramValue, Labels, MetricDef, MetricType, DEFAULT_MAX_LABELS,
7};
8
9/// Prometheus metrics registry
10#[derive(Debug)]
11pub struct MetricsRegistry {
12    /// Metric definitions
13    definitions: HashMap<String, MetricDef>,
14    /// Gauge values
15    gauges: HashMap<String, Vec<GaugeValue>>,
16    /// Counter values
17    counters: HashMap<String, Vec<CounterValue>>,
18    /// Histogram values
19    histograms: HashMap<String, Vec<HistogramValue>>,
20    /// Max labels per metric
21    max_labels: usize,
22}
23
24impl Default for MetricsRegistry {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl MetricsRegistry {
31    /// Create new registry
32    pub fn new() -> Self {
33        Self {
34            definitions: HashMap::new(),
35            gauges: HashMap::new(),
36            counters: HashMap::new(),
37            histograms: HashMap::new(),
38            max_labels: DEFAULT_MAX_LABELS,
39        }
40    }
41
42    /// Set max labels
43    pub fn with_max_labels(mut self, max: usize) -> Self {
44        self.max_labels = max;
45        self
46    }
47
48    /// Register gauge metric
49    pub fn register_gauge(&mut self, name: &str, help: &str) {
50        self.definitions.insert(
51            name.to_string(),
52            MetricDef::new(name, help, MetricType::Gauge),
53        );
54        self.gauges.entry(name.to_string()).or_default();
55    }
56
57    /// Register counter metric
58    pub fn register_counter(&mut self, name: &str, help: &str) {
59        self.definitions.insert(
60            name.to_string(),
61            MetricDef::new(name, help, MetricType::Counter),
62        );
63        self.counters.entry(name.to_string()).or_default();
64    }
65
66    /// Register histogram metric
67    pub fn register_histogram(&mut self, name: &str, help: &str) {
68        self.definitions.insert(
69            name.to_string(),
70            MetricDef::new(name, help, MetricType::Histogram),
71        );
72        self.histograms.entry(name.to_string()).or_default();
73    }
74
75    /// Set gauge value
76    pub fn set_gauge(&mut self, name: &str, value: GaugeValue) -> bool {
77        if value.labels.len() > self.max_labels {
78            return false;
79        }
80
81        if let Some(values) = self.gauges.get_mut(name) {
82            // Update existing or add new
83            let label_str = value.labels.format();
84            if let Some(existing) = values.iter_mut().find(|v| v.labels.format() == label_str) {
85                existing.value = value.value;
86                existing.timestamp = value.timestamp;
87            } else {
88                values.push(value);
89            }
90            true
91        } else {
92            false
93        }
94    }
95
96    /// Increment counter
97    pub fn inc_counter(&mut self, name: &str, labels: Labels) -> bool {
98        if labels.len() > self.max_labels {
99            return false;
100        }
101
102        if let Some(values) = self.counters.get_mut(name) {
103            let label_str = labels.format();
104            if let Some(existing) = values.iter_mut().find(|v| v.labels.format() == label_str) {
105                existing.value += 1;
106            } else {
107                values.push(CounterValue::new(1).with_labels(labels));
108            }
109            true
110        } else {
111            false
112        }
113    }
114
115    /// Add to counter
116    pub fn add_counter(&mut self, name: &str, amount: u64, labels: Labels) -> bool {
117        if labels.len() > self.max_labels {
118            return false;
119        }
120
121        if let Some(values) = self.counters.get_mut(name) {
122            let label_str = labels.format();
123            if let Some(existing) = values.iter_mut().find(|v| v.labels.format() == label_str) {
124                existing.value += amount;
125            } else {
126                values.push(CounterValue::new(amount).with_labels(labels));
127            }
128            true
129        } else {
130            false
131        }
132    }
133
134    /// Observe histogram value
135    pub fn observe_histogram(&mut self, name: &str, value: f64, labels: Labels) -> bool {
136        if labels.len() > self.max_labels {
137            return false;
138        }
139
140        if let Some(values) = self.histograms.get_mut(name) {
141            let label_str = labels.format();
142            if let Some(existing) = values.iter_mut().find(|v| v.labels.format() == label_str) {
143                existing.observe(value);
144            } else {
145                let mut hist = HistogramValue::new().with_labels(labels);
146                hist.observe(value);
147                values.push(hist);
148            }
149            true
150        } else {
151            false
152        }
153    }
154
155    /// Export as Prometheus text format
156    pub fn export(&self) -> String {
157        let mut lines = Vec::new();
158
159        // Export gauges
160        for (name, values) in &self.gauges {
161            if let Some(def) = self.definitions.get(name) {
162                lines.push(def.format_help());
163                lines.push(def.format_type());
164                for value in values {
165                    lines.push(value.format(name));
166                }
167            }
168        }
169
170        // Export counters
171        for (name, values) in &self.counters {
172            if let Some(def) = self.definitions.get(name) {
173                lines.push(def.format_help());
174                lines.push(def.format_type());
175                for value in values {
176                    lines.push(value.format(name));
177                }
178            }
179        }
180
181        // Export histograms
182        for (name, values) in &self.histograms {
183            if let Some(def) = self.definitions.get(name) {
184                lines.push(def.format_help());
185                lines.push(def.format_type());
186                for value in values {
187                    lines.push(value.format(name));
188                }
189            }
190        }
191
192        lines.join("\n")
193    }
194
195    /// Get metric count
196    pub fn metric_count(&self) -> usize {
197        self.definitions.len()
198    }
199
200    /// Clear all values (keep definitions)
201    pub fn clear_values(&mut self) {
202        for values in self.gauges.values_mut() {
203            values.clear();
204        }
205        for values in self.counters.values_mut() {
206            values.clear();
207        }
208        for values in self.histograms.values_mut() {
209            values.clear();
210        }
211    }
212}