Skip to main content

dbx_core/monitoring/
exporter.rs

1//! Prometheus Exposition Format Exporter
2//!
3//! Converts `DbxMetrics` to Prometheus text format (version 0.0.4).
4//! See: https://prometheus.io/docs/instrumenting/exposition_formats/
5
6use crate::monitoring::metrics::DbxMetrics;
7
8/// Export all DBX metrics in Prometheus text exposition format.
9///
10/// # Returns
11/// A `String` in Prometheus text format, ready to serve at `/metrics`.
12///
13/// # Example output
14/// ```text
15/// # HELP dbx_inserts_total Total INSERT operations
16/// # TYPE dbx_inserts_total counter
17/// dbx_inserts_total 1234
18/// ```
19pub fn export_prometheus(metrics: &DbxMetrics) -> String {
20    let mut out = String::with_capacity(4096);
21    let snap = metrics.snapshot();
22
23    // ── Operation Counters ──────────────────────────────────────────────
24    append_counter(
25        &mut out,
26        "dbx_inserts_total",
27        snap.inserts_total,
28        "Total number of INSERT operations",
29    );
30    append_counter(
31        &mut out,
32        "dbx_gets_total",
33        snap.gets_total,
34        "Total number of GET operations",
35    );
36    append_counter(
37        &mut out,
38        "dbx_deletes_total",
39        snap.deletes_total,
40        "Total number of DELETE operations",
41    );
42    append_counter(
43        &mut out,
44        "dbx_sql_queries_total",
45        snap.sql_queries_total,
46        "Total number of SQL queries executed",
47    );
48    append_counter(
49        &mut out,
50        "dbx_flush_total",
51        snap.flush_total,
52        "Total number of Delta Store flush operations",
53    );
54
55    // ── Tier Hit/Miss Counters ───────────────────────────────────────────
56    append_counter(
57        &mut out,
58        "dbx_delta_hits_total",
59        snap.delta_hits,
60        "Cache hits in Tier 1 (Delta Store)",
61    );
62    append_counter(
63        &mut out,
64        "dbx_delta_misses_total",
65        snap.delta_misses,
66        "Cache misses in Tier 1 (Delta Store)",
67    );
68    append_gauge_f64(
69        &mut out,
70        "dbx_delta_hit_rate",
71        snap.delta_hit_rate,
72        "Hit rate of Tier 1 Delta Store (0.0 - 1.0)",
73    );
74
75    append_counter(
76        &mut out,
77        "dbx_cache_hits_total",
78        snap.cache_hits,
79        "Cache hits in Tier 2 (Columnar Cache)",
80    );
81    append_counter(
82        &mut out,
83        "dbx_cache_misses_total",
84        snap.cache_misses,
85        "Cache misses in Tier 2 (Columnar Cache)",
86    );
87    append_gauge_f64(
88        &mut out,
89        "dbx_cache_hit_rate",
90        snap.cache_hit_rate,
91        "Hit rate of Tier 2 Columnar Cache (0.0 - 1.0)",
92    );
93
94    append_counter(
95        &mut out,
96        "dbx_wos_hits_total",
97        snap.wos_hits,
98        "Cache hits in Tier 3 (Write-Optimized Store)",
99    );
100    append_counter(
101        &mut out,
102        "dbx_wos_misses_total",
103        snap.wos_misses,
104        "Cache misses in Tier 3 (Write-Optimized Store)",
105    );
106    append_gauge_f64(
107        &mut out,
108        "dbx_wos_hit_rate",
109        snap.wos_hit_rate,
110        "Hit rate of Tier 3 WOS (0.0 - 1.0)",
111    );
112
113    // ── Sharding Stats ──────────────────────────────────────────────────
114    append_counter(
115        &mut out,
116        "dbx_scatter_writes_total",
117        snap.scatter_writes_total,
118        "Total number of scatter write operations (sharding)",
119    );
120    append_counter(
121        &mut out,
122        "dbx_scatter_reads_total",
123        snap.scatter_reads_total,
124        "Total number of scatter read operations (sharding)",
125    );
126
127    // ── Partition Stats ─────────────────────────────────────────────────
128    append_counter(
129        &mut out,
130        "dbx_partition_prune_hits_total",
131        snap.partition_prune_hits,
132        "Total number of partition pruning hits",
133    );
134
135    // ── WAL Stats ───────────────────────────────────────────────────────
136    append_counter(
137        &mut out,
138        "dbx_wal_appends_total",
139        snap.wal_appends_total,
140        "Total number of WAL append operations",
141    );
142    append_counter(
143        &mut out,
144        "dbx_wal_compactions_total",
145        snap.wal_compactions_total,
146        "Total number of WAL compaction operations",
147    );
148
149    // ── Latency Gauges (avg µs) ─────────────────────────────────────────
150    append_gauge(
151        &mut out,
152        "dbx_avg_query_latency_us",
153        snap.avg_query_latency_us,
154        "Average SQL query latency in microseconds",
155    );
156    append_gauge(
157        &mut out,
158        "dbx_avg_insert_latency_us",
159        snap.avg_insert_latency_us,
160        "Average INSERT latency in microseconds",
161    );
162
163    // ── Latency Histograms ──────────────────────────────────────────────
164    metrics.query_latency_us.export_prometheus(&mut out);
165    metrics.insert_latency_us.export_prometheus(&mut out);
166
167    out
168}
169
170fn append_counter(out: &mut String, name: &str, value: u64, help: &str) {
171    out.push_str(&format!("# HELP {name} {help}\n"));
172    out.push_str(&format!("# TYPE {name} counter\n"));
173    out.push_str(&format!("{name} {value}\n\n"));
174}
175
176fn append_gauge(out: &mut String, name: &str, value: u64, help: &str) {
177    out.push_str(&format!("# HELP {name} {help}\n"));
178    out.push_str(&format!("# TYPE {name} gauge\n"));
179    out.push_str(&format!("{name} {value}\n\n"));
180}
181
182fn append_gauge_f64(out: &mut String, name: &str, value: f64, help: &str) {
183    out.push_str(&format!("# HELP {name} {help}\n"));
184    out.push_str(&format!("# TYPE {name} gauge\n"));
185    out.push_str(&format!("{name} {:.4}\n\n", value));
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn test_prometheus_format() {
194        let m = DbxMetrics::new();
195        m.inc_inserts();
196        m.inc_inserts();
197        m.inc_sql_queries();
198        m.query_latency_us.observe(500);
199
200        let output = export_prometheus(&m);
201
202        assert!(
203            output.contains("dbx_inserts_total 2"),
204            "inserts_total should be 2"
205        );
206        assert!(
207            output.contains("dbx_sql_queries_total 1"),
208            "sql_queries_total should be 1"
209        );
210        assert!(output.contains("# TYPE dbx_inserts_total counter"));
211        assert!(output.contains("# TYPE dbx_cache_hit_rate gauge"));
212        assert!(output.contains("# TYPE dbx_query_latency_us histogram"));
213        assert!(output.contains("dbx_query_latency_us_count 1"));
214    }
215
216    #[test]
217    fn test_hit_rate_export() {
218        let m = DbxMetrics::new();
219        m.inc_cache_hit();
220        m.inc_cache_hit();
221        m.inc_cache_miss();
222
223        let output = export_prometheus(&m);
224        // Hit rate should be ~0.6667
225        assert!(output.contains("dbx_cache_hit_rate 0.6667"));
226    }
227}