Skip to main content

hyperi_rustlib/metrics/dfe_groups/
enrichment.rs

1// Project:   hyperi-rustlib
2// File:      src/metrics/dfe_groups/enrichment.rs
3// Purpose:   DFE enrichment metrics group
4// Language:  Rust
5//
6// License:   BUSL-1.1
7// Copyright: (c) 2026 HYPERI PTY LIMITED
8
9//! Enrichment cache metrics (GeoIP, reputation, lookup tables).
10
11use super::super::MetricsManager;
12use super::super::manifest::{MetricDescriptor, MetricType};
13
14/// Enrichment cache metrics.
15///
16/// Tracks cache hit/miss rates, cache size, and lookup latency.
17/// The `type` label distinguishes enrichment sources (e.g., `geoip`, `reputation`).
18#[derive(Clone)]
19pub struct EnrichmentMetrics {
20    namespace: String,
21}
22
23impl EnrichmentMetrics {
24    #[must_use]
25    pub fn new(manager: &MetricsManager) -> Self {
26        let ns = manager.namespace();
27
28        // enrichment_cache_hits_total -- label-based
29        let hits_key = if ns.is_empty() {
30            "enrichment_cache_hits_total".to_string()
31        } else {
32            format!("{ns}_enrichment_cache_hits_total")
33        };
34        metrics::describe_counter!(hits_key.clone(), "Enrichment cache hits");
35        manager.registry().push(MetricDescriptor {
36            name: hits_key,
37            metric_type: MetricType::Counter,
38            description: "Enrichment cache hits".into(),
39            unit: String::new(),
40            labels: vec!["type".into()],
41            group: "enrichment".into(),
42            buckets: None,
43            use_cases: vec![],
44            dashboard_hint: None,
45        });
46
47        // enrichment_cache_misses_total -- label-based
48        let misses_key = if ns.is_empty() {
49            "enrichment_cache_misses_total".to_string()
50        } else {
51            format!("{ns}_enrichment_cache_misses_total")
52        };
53        metrics::describe_counter!(misses_key.clone(), "Enrichment cache misses");
54        manager.registry().push(MetricDescriptor {
55            name: misses_key,
56            metric_type: MetricType::Counter,
57            description: "Enrichment cache misses".into(),
58            unit: String::new(),
59            labels: vec!["type".into()],
60            group: "enrichment".into(),
61            buckets: None,
62            use_cases: vec![],
63            dashboard_hint: None,
64        });
65
66        // enrichment_cache_size -- label-based
67        let size_key = if ns.is_empty() {
68            "enrichment_cache_size".to_string()
69        } else {
70            format!("{ns}_enrichment_cache_size")
71        };
72        metrics::describe_gauge!(size_key.clone(), "Current enrichment cache entries");
73        manager.registry().push(MetricDescriptor {
74            name: size_key,
75            metric_type: MetricType::Gauge,
76            description: "Current enrichment cache entries".into(),
77            unit: String::new(),
78            labels: vec!["type".into()],
79            group: "enrichment".into(),
80            buckets: None,
81            use_cases: vec![],
82            dashboard_hint: None,
83        });
84
85        // enrichment_duration_seconds -- label-based
86        let dur_key = if ns.is_empty() {
87            "enrichment_duration_seconds".to_string()
88        } else {
89            format!("{ns}_enrichment_duration_seconds")
90        };
91        metrics::describe_histogram!(
92            dur_key.clone(),
93            metrics::Unit::Seconds,
94            "Enrichment lookup latency"
95        );
96        manager.registry().push(MetricDescriptor {
97            name: dur_key,
98            metric_type: MetricType::Histogram,
99            description: "Enrichment lookup latency".into(),
100            unit: "seconds".into(),
101            labels: vec!["type".into()],
102            group: "enrichment".into(),
103            buckets: None,
104            use_cases: vec![],
105            dashboard_hint: None,
106        });
107
108        Self {
109            namespace: ns.to_string(),
110        }
111    }
112
113    #[inline]
114    pub fn record_hit(&self, enrichment_type: &str) {
115        let key = if self.namespace.is_empty() {
116            "enrichment_cache_hits_total".to_string()
117        } else {
118            format!("{}_enrichment_cache_hits_total", self.namespace)
119        };
120        metrics::counter!(key, "type" => enrichment_type.to_string()).increment(1);
121    }
122
123    #[inline]
124    pub fn record_miss(&self, enrichment_type: &str) {
125        let key = if self.namespace.is_empty() {
126            "enrichment_cache_misses_total".to_string()
127        } else {
128            format!("{}_enrichment_cache_misses_total", self.namespace)
129        };
130        metrics::counter!(key, "type" => enrichment_type.to_string()).increment(1);
131    }
132
133    #[inline]
134    pub fn set_cache_size(&self, enrichment_type: &str, size: usize) {
135        let key = if self.namespace.is_empty() {
136            "enrichment_cache_size".to_string()
137        } else {
138            format!("{}_enrichment_cache_size", self.namespace)
139        };
140        metrics::gauge!(key, "type" => enrichment_type.to_string()).set(size as f64);
141    }
142
143    #[inline]
144    pub fn record_duration(&self, enrichment_type: &str, seconds: f64) {
145        let key = if self.namespace.is_empty() {
146            "enrichment_duration_seconds".to_string()
147        } else {
148            format!("{}_enrichment_duration_seconds", self.namespace)
149        };
150        metrics::histogram!(key, "type" => enrichment_type.to_string()).record(seconds);
151    }
152}