1use prometheus::{Gauge, GaugeVec, Opts, Registry};
4
5#[derive(Clone)]
8pub struct PrometheusMetrics {
9 pub registry: Registry,
10 pub sv2_uptime_seconds: Gauge,
12 pub sv2_server_channels: Option<GaugeVec>,
14 pub sv2_server_hashrate_total: Option<Gauge>,
15 pub sv2_server_channel_hashrate: Option<GaugeVec>,
16 pub sv2_server_shares_accepted_total: Option<GaugeVec>,
17 pub sv2_server_shares_rejected_total: Option<GaugeVec>,
18 pub sv2_server_blocks_found_total: Option<Gauge>,
19 pub sv2_clients_total: Option<Gauge>,
21 pub sv2_client_channels: Option<GaugeVec>,
22 pub sv2_client_hashrate_total: Option<Gauge>,
23 pub sv2_client_channel_hashrate: Option<GaugeVec>,
24 pub sv2_client_shares_accepted_total: Option<GaugeVec>,
25 pub sv2_client_shares_rejected_total: Option<GaugeVec>,
26 pub sv2_client_blocks_found_total: Option<Gauge>,
27 pub sv1_clients_total: Option<Gauge>,
29 pub sv1_hashrate_total: Option<Gauge>,
30}
31
32impl PrometheusMetrics {
33 pub fn new(
34 enable_server_metrics: bool,
35 enable_clients_metrics: bool,
36 enable_sv1_metrics: bool,
37 ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
38 let registry = Registry::new();
39
40 let sv2_uptime_seconds = Gauge::new("sv2_uptime_seconds", "Server uptime in seconds")?;
42 registry.register(Box::new(sv2_uptime_seconds.clone()))?;
43
44 let (
46 sv2_server_channels,
47 sv2_server_hashrate_total,
48 sv2_server_channel_hashrate,
49 sv2_server_shares_accepted_total,
50 sv2_server_shares_rejected_total,
51 sv2_server_blocks_found_total,
52 ) = if enable_server_metrics {
53 let channels = GaugeVec::new(
54 Opts::new("sv2_server_channels", "Number of server channels by type"),
55 &["channel_type"],
56 )?;
57 registry.register(Box::new(channels.clone()))?;
58
59 let hashrate = Gauge::new(
60 "sv2_server_hashrate_total",
61 "Total hashrate for channels opened with the server",
62 )?;
63 registry.register(Box::new(hashrate.clone()))?;
64
65 let channel_hashrate = GaugeVec::new(
66 Opts::new(
67 "sv2_server_channel_hashrate",
68 "Hashrate for individual server channels",
69 ),
70 &["channel_id", "user_identity"],
71 )?;
72 registry.register(Box::new(channel_hashrate.clone()))?;
73
74 let shares_accepted = GaugeVec::new(
75 Opts::new(
76 "sv2_server_shares_accepted_total",
77 "Total shares accepted per server channel",
78 ),
79 &["channel_id", "user_identity"],
80 )?;
81 registry.register(Box::new(shares_accepted.clone()))?;
82
83 let shares_rejected = GaugeVec::new(
84 Opts::new(
85 "sv2_server_shares_rejected_total",
86 "Total shares rejected per server channel by error code",
87 ),
88 &["channel_id", "user_identity", "error_code"],
89 )?;
90 registry.register(Box::new(shares_rejected.clone()))?;
91
92 let blocks_found = Gauge::new(
93 "sv2_server_blocks_found_total",
94 "Total blocks found across all current server channels",
95 )?;
96 registry.register(Box::new(blocks_found.clone()))?;
97
98 (
99 Some(channels),
100 Some(hashrate),
101 Some(channel_hashrate),
102 Some(shares_accepted),
103 Some(shares_rejected),
104 Some(blocks_found),
105 )
106 } else {
107 (None, None, None, None, None, None)
108 };
109
110 let (
112 sv2_clients_total,
113 sv2_client_channels,
114 sv2_client_hashrate_total,
115 sv2_client_channel_hashrate,
116 sv2_client_shares_accepted_total,
117 sv2_client_shares_rejected_total,
118 sv2_client_blocks_found_total,
119 ) = if enable_clients_metrics {
120 let clients_total =
121 Gauge::new("sv2_clients_total", "Total number of connected clients")?;
122 registry.register(Box::new(clients_total.clone()))?;
123
124 let channels = GaugeVec::new(
125 Opts::new("sv2_client_channels", "Number of client channels by type"),
126 &["channel_type"],
127 )?;
128 registry.register(Box::new(channels.clone()))?;
129
130 let hashrate = Gauge::new(
131 "sv2_client_hashrate_total",
132 "Total hashrate for channels opened with clients",
133 )?;
134 registry.register(Box::new(hashrate.clone()))?;
135
136 let channel_hashrate = GaugeVec::new(
137 Opts::new(
138 "sv2_client_channel_hashrate",
139 "Hashrate for individual client channels",
140 ),
141 &["client_id", "channel_id", "user_identity"],
142 )?;
143 registry.register(Box::new(channel_hashrate.clone()))?;
144
145 let shares_accepted = GaugeVec::new(
146 Opts::new(
147 "sv2_client_shares_accepted_total",
148 "Total shares accepted per client channel",
149 ),
150 &["client_id", "channel_id", "user_identity"],
151 )?;
152 registry.register(Box::new(shares_accepted.clone()))?;
153
154 let shares_rejected = GaugeVec::new(
155 Opts::new(
156 "sv2_client_shares_rejected_total",
157 "Total shares rejected per client channel by error code",
158 ),
159 &["client_id", "channel_id", "user_identity", "error_code"],
160 )?;
161 registry.register(Box::new(shares_rejected.clone()))?;
162
163 let blocks_found = Gauge::new(
164 "sv2_client_blocks_found_total",
165 "Total blocks found across all current client channels",
166 )?;
167 registry.register(Box::new(blocks_found.clone()))?;
168
169 (
170 Some(clients_total),
171 Some(channels),
172 Some(hashrate),
173 Some(channel_hashrate),
174 Some(shares_accepted),
175 Some(shares_rejected),
176 Some(blocks_found),
177 )
178 } else {
179 (None, None, None, None, None, None, None)
180 };
181
182 let (sv1_clients_total, sv1_hashrate_total) = if enable_sv1_metrics {
184 let clients = Gauge::new("sv1_clients_total", "Total number of SV1 clients")?;
185 registry.register(Box::new(clients.clone()))?;
186
187 let hashrate = Gauge::new("sv1_hashrate_total", "Total hashrate from SV1 clients")?;
188 registry.register(Box::new(hashrate.clone()))?;
189
190 (Some(clients), Some(hashrate))
191 } else {
192 (None, None)
193 };
194
195 Ok(Self {
196 registry,
197 sv2_uptime_seconds,
198 sv2_server_channels,
199 sv2_server_hashrate_total,
200 sv2_server_channel_hashrate,
201 sv2_server_shares_accepted_total,
202 sv2_server_shares_rejected_total,
203 sv2_server_blocks_found_total,
204 sv2_clients_total,
205 sv2_client_channels,
206 sv2_client_hashrate_total,
207 sv2_client_channel_hashrate,
208 sv2_client_shares_accepted_total,
209 sv2_client_shares_rejected_total,
210 sv2_client_blocks_found_total,
211 sv1_clients_total,
212 sv1_hashrate_total,
213 })
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use prometheus::Encoder;
221
222 #[test]
223 fn uptime_metric_always_registered() {
224 let m = PrometheusMetrics::new(false, false, false).unwrap();
225 m.sv2_uptime_seconds.set(42.0);
226
227 let families = m.registry.gather();
228 let names: Vec<&str> = families.iter().map(|f| f.get_name()).collect();
229 assert!(names.contains(&"sv2_uptime_seconds"));
230 }
231
232 #[test]
233 fn server_metrics_enabled() {
234 let m = PrometheusMetrics::new(true, false, false).unwrap();
235 assert!(m.sv2_server_channels.is_some());
236 assert!(m.sv2_server_hashrate_total.is_some());
237 assert!(m.sv2_server_channel_hashrate.is_some());
238 assert!(m.sv2_server_shares_accepted_total.is_some());
239 assert!(m.sv2_server_shares_rejected_total.is_some());
240 assert!(m.sv2_clients_total.is_none());
242 assert!(m.sv1_clients_total.is_none());
243 }
244
245 #[test]
246 fn clients_metrics_enabled() {
247 let m = PrometheusMetrics::new(false, true, false).unwrap();
248 assert!(m.sv2_clients_total.is_some());
249 assert!(m.sv2_client_channels.is_some());
250 assert!(m.sv2_client_hashrate_total.is_some());
251 assert!(m.sv2_client_channel_hashrate.is_some());
252 assert!(m.sv2_client_shares_accepted_total.is_some());
253 assert!(m.sv2_client_shares_rejected_total.is_some());
254 assert!(m.sv2_server_channels.is_none());
256 assert!(m.sv1_clients_total.is_none());
257 }
258
259 #[test]
260 fn sv1_metrics_enabled() {
261 let m = PrometheusMetrics::new(false, false, true).unwrap();
262 assert!(m.sv1_clients_total.is_some());
263 assert!(m.sv1_hashrate_total.is_some());
264 assert!(m.sv2_server_channels.is_none());
266 assert!(m.sv2_clients_total.is_none());
267 }
268
269 #[test]
270 fn all_metrics_disabled_only_uptime() {
271 let m = PrometheusMetrics::new(false, false, false).unwrap();
272 assert!(m.sv2_server_channels.is_none());
273 assert!(m.sv2_clients_total.is_none());
274 assert!(m.sv1_clients_total.is_none());
275
276 let families = m.registry.gather();
277 assert_eq!(families.len(), 1);
279 assert_eq!(families[0].get_name(), "sv2_uptime_seconds");
280 }
281
282 #[test]
283 fn metrics_encode_to_prometheus_text_format() {
284 let m = PrometheusMetrics::new(true, true, true).unwrap();
285 m.sv2_uptime_seconds.set(123.0);
286 m.sv2_server_hashrate_total.as_ref().unwrap().set(1000.0);
287 m.sv2_clients_total.as_ref().unwrap().set(5.0);
288 m.sv1_clients_total.as_ref().unwrap().set(3.0);
289
290 let encoder = prometheus::TextEncoder::new();
291 let families = m.registry.gather();
292 let mut buf = Vec::new();
293 encoder.encode(&families, &mut buf).unwrap();
294 let output = String::from_utf8(buf).unwrap();
295
296 assert!(output.contains("sv2_uptime_seconds 123"));
297 assert!(output.contains("sv2_server_hashrate_total 1000"));
298 assert!(output.contains("sv2_clients_total 5"));
299 assert!(output.contains("sv1_clients_total 3"));
300 }
301}