1use std::collections::HashMap;
35use std::sync::{Arc, Mutex};
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum MetricType {
40 Counter,
42 Gauge,
44 Histogram,
46}
47
48impl MetricType {
49 #[must_use]
51 pub fn as_str(&self) -> &'static str {
52 match self {
53 Self::Counter => "counter",
54 Self::Gauge => "gauge",
55 Self::Histogram => "histogram",
56 }
57 }
58}
59
60#[derive(Debug, Clone)]
62pub struct MetricMetadata {
63 pub name: String,
65 pub help: String,
67 pub metric_type: MetricType,
69}
70
71#[derive(Debug, Clone)]
73pub struct Counter {
74 value: Arc<Mutex<f64>>,
75}
76
77impl Counter {
78 #[must_use]
80 pub fn new() -> Self {
81 Self {
82 value: Arc::new(Mutex::new(0.0)),
83 }
84 }
85
86 #[inline]
88 pub fn inc(&self) {
89 self.add(1.0);
90 }
91
92 #[inline]
94 pub fn add(&self, value: f64) {
95 if value >= 0.0 {
96 let mut val = self.value.lock().unwrap();
97 *val += value;
98 }
99 }
100
101 #[must_use]
103 #[inline]
104 pub fn get(&self) -> f64 {
105 *self.value.lock().unwrap()
106 }
107
108 #[inline]
110 pub fn reset(&self) {
111 *self.value.lock().unwrap() = 0.0;
112 }
113}
114
115impl Default for Counter {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121#[derive(Debug, Clone)]
123pub struct Gauge {
124 value: Arc<Mutex<f64>>,
125}
126
127impl Gauge {
128 #[must_use]
130 pub fn new() -> Self {
131 Self {
132 value: Arc::new(Mutex::new(0.0)),
133 }
134 }
135
136 #[inline]
138 pub fn set(&self, value: f64) {
139 *self.value.lock().unwrap() = value;
140 }
141
142 #[inline]
144 pub fn inc(&self) {
145 self.add(1.0);
146 }
147
148 #[inline]
150 pub fn dec(&self) {
151 self.sub(1.0);
152 }
153
154 #[inline]
156 pub fn add(&self, value: f64) {
157 let mut val = self.value.lock().unwrap();
158 *val += value;
159 }
160
161 #[inline]
163 pub fn sub(&self, value: f64) {
164 let mut val = self.value.lock().unwrap();
165 *val -= value;
166 }
167
168 #[must_use]
170 #[inline]
171 pub fn get(&self) -> f64 {
172 *self.value.lock().unwrap()
173 }
174}
175
176impl Default for Gauge {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182#[derive(Debug, Clone)]
184pub struct Histogram {
185 sum: Arc<Mutex<f64>>,
186 count: Arc<Mutex<u64>>,
187}
188
189impl Histogram {
190 pub fn new() -> Self {
192 Self {
193 sum: Arc::new(Mutex::new(0.0)),
194 count: Arc::new(Mutex::new(0)),
195 }
196 }
197
198 #[inline]
200 pub fn observe(&self, value: f64) {
201 let mut sum = self.sum.lock().unwrap();
202 let mut count = self.count.lock().unwrap();
203 *sum += value;
204 *count += 1;
205 }
206
207 #[inline]
209 pub fn sum(&self) -> f64 {
210 *self.sum.lock().unwrap()
211 }
212
213 #[must_use]
215 #[inline]
216 pub fn count(&self) -> u64 {
217 *self.count.lock().unwrap()
218 }
219
220 #[inline]
222 pub fn avg(&self) -> f64 {
223 let sum = *self.sum.lock().unwrap();
224 let count = *self.count.lock().unwrap();
225 if count == 0 { 0.0 } else { sum / count as f64 }
226 }
227
228 #[inline]
230 pub fn reset(&self) {
231 *self.sum.lock().unwrap() = 0.0;
232 *self.count.lock().unwrap() = 0;
233 }
234}
235
236impl Default for Histogram {
237 fn default() -> Self {
238 Self::new()
239 }
240}
241
242pub struct MetricsRegistry {
244 metadata: HashMap<String, MetricMetadata>,
245 counters: HashMap<String, Counter>,
246 gauges: HashMap<String, Gauge>,
247 histograms: HashMap<String, Histogram>,
248}
249
250impl MetricsRegistry {
251 pub fn new() -> Self {
253 Self {
254 metadata: HashMap::new(),
255 counters: HashMap::new(),
256 gauges: HashMap::new(),
257 histograms: HashMap::new(),
258 }
259 }
260
261 pub fn counter(&mut self, name: &str, help: &str) -> Counter {
263 self.metadata.insert(
264 name.to_string(),
265 MetricMetadata {
266 name: name.to_string(),
267 help: help.to_string(),
268 metric_type: MetricType::Counter,
269 },
270 );
271
272 let counter = Counter::new();
273 self.counters.insert(name.to_string(), counter.clone());
274 counter
275 }
276
277 pub fn gauge(&mut self, name: &str, help: &str) -> Gauge {
279 self.metadata.insert(
280 name.to_string(),
281 MetricMetadata {
282 name: name.to_string(),
283 help: help.to_string(),
284 metric_type: MetricType::Gauge,
285 },
286 );
287
288 let gauge = Gauge::new();
289 self.gauges.insert(name.to_string(), gauge.clone());
290 gauge
291 }
292
293 pub fn histogram(&mut self, name: &str, help: &str) -> Histogram {
295 self.metadata.insert(
296 name.to_string(),
297 MetricMetadata {
298 name: name.to_string(),
299 help: help.to_string(),
300 metric_type: MetricType::Histogram,
301 },
302 );
303
304 let histogram = Histogram::new();
305 self.histograms.insert(name.to_string(), histogram.clone());
306 histogram
307 }
308
309 #[must_use]
311 #[inline]
312 pub fn export(&self) -> String {
313 let mut output = String::new();
314
315 for (name, counter) in &self.counters {
317 if let Some(meta) = self.metadata.get(name) {
318 output.push_str(&format!("# HELP {} {}\n", meta.name, meta.help));
319 output.push_str(&format!(
320 "# TYPE {} {}\n",
321 meta.name,
322 meta.metric_type.as_str()
323 ));
324 output.push_str(&format!("{} {}\n", name, counter.get()));
325 }
326 }
327
328 for (name, gauge) in &self.gauges {
330 if let Some(meta) = self.metadata.get(name) {
331 output.push_str(&format!("# HELP {} {}\n", meta.name, meta.help));
332 output.push_str(&format!(
333 "# TYPE {} {}\n",
334 meta.name,
335 meta.metric_type.as_str()
336 ));
337 output.push_str(&format!("{} {}\n", name, gauge.get()));
338 }
339 }
340
341 for (name, histogram) in &self.histograms {
343 if let Some(meta) = self.metadata.get(name) {
344 output.push_str(&format!("# HELP {} {}\n", meta.name, meta.help));
345 output.push_str(&format!(
346 "# TYPE {} {}\n",
347 meta.name,
348 meta.metric_type.as_str()
349 ));
350 output.push_str(&format!("{}_sum {}\n", name, histogram.sum()));
351 output.push_str(&format!("{}_count {}\n", name, histogram.count()));
352 }
353 }
354
355 output
356 }
357
358 #[must_use]
360 #[inline]
361 pub fn get_counter(&self, name: &str) -> Option<&Counter> {
362 self.counters.get(name)
363 }
364
365 #[must_use]
367 #[inline]
368 pub fn get_gauge(&self, name: &str) -> Option<&Gauge> {
369 self.gauges.get(name)
370 }
371
372 #[must_use]
374 #[inline]
375 pub fn get_histogram(&self, name: &str) -> Option<&Histogram> {
376 self.histograms.get(name)
377 }
378
379 pub fn reset_all(&self) {
381 for counter in self.counters.values() {
382 counter.reset();
383 }
384 for histogram in self.histograms.values() {
385 histogram.reset();
386 }
387 }
389}
390
391impl Default for MetricsRegistry {
392 fn default() -> Self {
393 Self::new()
394 }
395}
396
397pub fn create_standard_registry() -> MetricsRegistry {
399 let mut registry = MetricsRegistry::new();
400
401 registry.counter("chie_content_requests_total", "Total content requests");
403 registry.counter("chie_content_requests_failed", "Failed content requests");
404 registry.counter("chie_bytes_transferred_total", "Total bytes transferred");
405 registry.gauge("chie_storage_bytes_used", "Storage bytes used");
406 registry.gauge("chie_storage_bytes_available", "Storage bytes available");
407 registry.gauge(
408 "chie_pinned_content_count",
409 "Number of pinned content items",
410 );
411
412 registry.gauge("chie_connected_peers", "Number of connected peers");
414 registry.counter("chie_peer_connections_total", "Total peer connections");
415 registry.counter(
416 "chie_peer_disconnections_total",
417 "Total peer disconnections",
418 );
419
420 registry.counter(
422 "chie_bandwidth_proofs_submitted",
423 "Total bandwidth proofs submitted",
424 );
425 registry.counter(
426 "chie_bandwidth_proofs_verified",
427 "Total bandwidth proofs verified",
428 );
429 registry.counter(
430 "chie_bandwidth_proofs_failed",
431 "Failed bandwidth proof submissions",
432 );
433
434 registry.histogram(
436 "chie_request_duration_seconds",
437 "Request duration in seconds",
438 );
439 registry.histogram(
440 "chie_chunk_transfer_duration_seconds",
441 "Chunk transfer duration",
442 );
443
444 registry.gauge("chie_earnings_total", "Total earnings");
446 registry.gauge("chie_earnings_pending", "Pending earnings");
447
448 registry
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454
455 #[test]
456 fn test_counter_basic() {
457 let counter = Counter::new();
458 assert_eq!(counter.get(), 0.0);
459
460 counter.inc();
461 assert_eq!(counter.get(), 1.0);
462
463 counter.add(5.0);
464 assert_eq!(counter.get(), 6.0);
465
466 counter.reset();
467 assert_eq!(counter.get(), 0.0);
468 }
469
470 #[test]
471 fn test_counter_negative() {
472 let counter = Counter::new();
473 counter.add(-5.0);
474 assert_eq!(counter.get(), 0.0); }
476
477 #[test]
478 fn test_gauge_basic() {
479 let gauge = Gauge::new();
480 assert_eq!(gauge.get(), 0.0);
481
482 gauge.set(10.0);
483 assert_eq!(gauge.get(), 10.0);
484
485 gauge.inc();
486 assert_eq!(gauge.get(), 11.0);
487
488 gauge.dec();
489 assert_eq!(gauge.get(), 10.0);
490
491 gauge.add(5.0);
492 assert_eq!(gauge.get(), 15.0);
493
494 gauge.sub(3.0);
495 assert_eq!(gauge.get(), 12.0);
496 }
497
498 #[test]
499 fn test_histogram_basic() {
500 let histogram = Histogram::new();
501 assert_eq!(histogram.count(), 0);
502 assert_eq!(histogram.sum(), 0.0);
503
504 histogram.observe(1.0);
505 histogram.observe(2.0);
506 histogram.observe(3.0);
507
508 assert_eq!(histogram.count(), 3);
509 assert_eq!(histogram.sum(), 6.0);
510 assert_eq!(histogram.avg(), 2.0);
511
512 histogram.reset();
513 assert_eq!(histogram.count(), 0);
514 assert_eq!(histogram.sum(), 0.0);
515 }
516
517 #[test]
518 fn test_registry_counter() {
519 let mut registry = MetricsRegistry::new();
520 let counter = registry.counter("test_counter", "Test counter");
521
522 counter.inc();
523 assert_eq!(counter.get(), 1.0);
524
525 let retrieved = registry.get_counter("test_counter").unwrap();
526 assert_eq!(retrieved.get(), 1.0);
527 }
528
529 #[test]
530 fn test_registry_gauge() {
531 let mut registry = MetricsRegistry::new();
532 let gauge = registry.gauge("test_gauge", "Test gauge");
533
534 gauge.set(42.0);
535 assert_eq!(gauge.get(), 42.0);
536
537 let retrieved = registry.get_gauge("test_gauge").unwrap();
538 assert_eq!(retrieved.get(), 42.0);
539 }
540
541 #[test]
542 fn test_registry_histogram() {
543 let mut registry = MetricsRegistry::new();
544 let histogram = registry.histogram("test_histogram", "Test histogram");
545
546 histogram.observe(1.0);
547 histogram.observe(2.0);
548
549 let retrieved = registry.get_histogram("test_histogram").unwrap();
550 assert_eq!(retrieved.count(), 2);
551 assert_eq!(retrieved.sum(), 3.0);
552 }
553
554 #[test]
555 fn test_export_format() {
556 let mut registry = MetricsRegistry::new();
557 let counter = registry.counter("test_counter", "Test counter");
558 let gauge = registry.gauge("test_gauge", "Test gauge");
559
560 counter.inc();
561 gauge.set(42.0);
562
563 let output = registry.export();
564 assert!(output.contains("# HELP test_counter Test counter"));
565 assert!(output.contains("# TYPE test_counter counter"));
566 assert!(output.contains("test_counter 1"));
567 assert!(output.contains("# HELP test_gauge Test gauge"));
568 assert!(output.contains("# TYPE test_gauge gauge"));
569 assert!(output.contains("test_gauge 42"));
570 }
571
572 #[test]
573 fn test_reset_all() {
574 let mut registry = MetricsRegistry::new();
575 let counter = registry.counter("test_counter", "Test counter");
576 let histogram = registry.histogram("test_histogram", "Test histogram");
577
578 counter.inc();
579 histogram.observe(1.0);
580
581 registry.reset_all();
582
583 assert_eq!(counter.get(), 0.0);
584 assert_eq!(histogram.count(), 0);
585 }
586
587 #[test]
588 fn test_create_standard_registry() {
589 let registry = create_standard_registry();
590 assert!(
591 registry
592 .get_counter("chie_content_requests_total")
593 .is_some()
594 );
595 assert!(registry.get_gauge("chie_storage_bytes_used").is_some());
596 assert!(
597 registry
598 .get_histogram("chie_request_duration_seconds")
599 .is_some()
600 );
601 }
602
603 #[test]
604 fn test_counter_clone() {
605 let counter1 = Counter::new();
606 let counter2 = counter1.clone();
607
608 counter1.inc();
609 assert_eq!(counter2.get(), 1.0);
610 }
611
612 #[test]
613 fn test_gauge_clone() {
614 let gauge1 = Gauge::new();
615 let gauge2 = gauge1.clone();
616
617 gauge1.set(10.0);
618 assert_eq!(gauge2.get(), 10.0);
619 }
620}