Skip to main content

laminar_connectors/mongodb/
metrics.rs

1//! `MongoDB` connector metrics.
2//!
3//! Prometheus-backed counters for tracking CDC source and sink performance.
4
5use prometheus::{IntCounter, Registry};
6
7use crate::prom::reg_or_local;
8
9/// Metrics for the `MongoDB` CDC source connector.
10#[derive(Debug, Clone)]
11pub struct MongoDbCdcMetrics {
12    /// Total change events received.
13    pub events_received: IntCounter,
14    /// Total bytes received from the change stream.
15    pub bytes_received: IntCounter,
16    /// Total errors encountered.
17    pub errors: IntCounter,
18    /// Total batches produced for downstream.
19    pub batches_produced: IntCounter,
20    /// Total INSERT operations received.
21    pub inserts: IntCounter,
22    /// Total UPDATE operations received.
23    pub updates: IntCounter,
24    /// Total REPLACE operations received.
25    pub replaces: IntCounter,
26    /// Total DELETE operations received.
27    pub deletes: IntCounter,
28    /// Total lifecycle events (drop/rename/invalidate).
29    pub lifecycle_events: IntCounter,
30    /// Total reconnection attempts.
31    pub reconnects: IntCounter,
32}
33
34impl MongoDbCdcMetrics {
35    /// Creates a new metrics instance with all counters at zero.
36    #[must_use]
37    #[allow(clippy::missing_panics_doc)]
38    pub fn new(registry: Option<&Registry>) -> Self {
39        let mut local = None;
40        let reg = reg_or_local(registry, &mut local);
41
42        Self {
43            events_received: reg.counter(
44                "mongodb_cdc_events_received_total",
45                "Total MongoDB CDC events received",
46            ),
47            bytes_received: reg.counter(
48                "mongodb_cdc_bytes_received_total",
49                "Total bytes from change stream",
50            ),
51            errors: reg.counter("mongodb_cdc_errors_total", "Total MongoDB CDC errors"),
52            batches_produced: reg.counter(
53                "mongodb_cdc_batches_produced_total",
54                "Total batches produced",
55            ),
56            inserts: reg.counter("mongodb_cdc_inserts_total", "Total INSERT events"),
57            updates: reg.counter("mongodb_cdc_updates_total", "Total UPDATE events"),
58            replaces: reg.counter("mongodb_cdc_replaces_total", "Total REPLACE events"),
59            deletes: reg.counter("mongodb_cdc_deletes_total", "Total DELETE events"),
60            lifecycle_events: reg.counter(
61                "mongodb_cdc_lifecycle_events_total",
62                "Total lifecycle events",
63            ),
64            reconnects: reg.counter(
65                "mongodb_cdc_reconnects_total",
66                "Total reconnection attempts",
67            ),
68        }
69    }
70
71    /// Records a received change event by operation type.
72    pub fn record_event(&self, op: &str) {
73        self.events_received.inc();
74        match op {
75            "I" => self.inserts.inc(),
76            "U" => self.updates.inc(),
77            "R" => self.replaces.inc(),
78            "D" => self.deletes.inc(),
79            _ => self.lifecycle_events.inc(),
80        }
81    }
82
83    /// Records bytes received from the change stream.
84    pub fn record_bytes(&self, bytes: u64) {
85        self.bytes_received.inc_by(bytes);
86    }
87
88    /// Records an error.
89    pub fn record_error(&self) {
90        self.errors.inc();
91    }
92
93    /// Records a batch produced for downstream.
94    pub fn record_batch(&self) {
95        self.batches_produced.inc();
96    }
97
98    /// Records a reconnection attempt.
99    pub fn record_reconnect(&self) {
100        self.reconnects.inc();
101    }
102}
103
104impl Default for MongoDbCdcMetrics {
105    fn default() -> Self {
106        Self::new(None)
107    }
108}
109
110/// Metrics for the `MongoDB` sink connector.
111#[derive(Debug, Clone)]
112pub struct MongoDbSinkMetrics {
113    /// Total records written.
114    pub records_written: IntCounter,
115    /// Total bytes written.
116    pub bytes_written: IntCounter,
117    /// Total errors encountered.
118    pub errors: IntCounter,
119    /// Total batches flushed.
120    pub batches_flushed: IntCounter,
121    /// Total bulk write operations issued.
122    pub bulk_writes: IntCounter,
123    /// Total individual insert operations.
124    pub inserts: IntCounter,
125    /// Total upsert operations.
126    pub upserts: IntCounter,
127    /// Total delete operations.
128    pub deletes: IntCounter,
129}
130
131impl MongoDbSinkMetrics {
132    /// Creates a new metrics instance with all counters at zero.
133    #[must_use]
134    #[allow(clippy::missing_panics_doc)]
135    pub fn new(registry: Option<&Registry>) -> Self {
136        let mut local = None;
137        let reg = reg_or_local(registry, &mut local);
138
139        Self {
140            records_written: reg.counter(
141                "mongodb_sink_records_written_total",
142                "Total MongoDB sink records written",
143            ),
144            bytes_written: reg.counter(
145                "mongodb_sink_bytes_written_total",
146                "Total MongoDB sink bytes written",
147            ),
148            errors: reg.counter("mongodb_sink_errors_total", "Total MongoDB sink errors"),
149            batches_flushed: reg.counter(
150                "mongodb_sink_batches_flushed_total",
151                "Total batches flushed",
152            ),
153            bulk_writes: reg.counter(
154                "mongodb_sink_bulk_writes_total",
155                "Total bulk write operations",
156            ),
157            inserts: reg.counter("mongodb_sink_inserts_total", "Total insert operations"),
158            upserts: reg.counter("mongodb_sink_upserts_total", "Total upsert operations"),
159            deletes: reg.counter("mongodb_sink_deletes_total", "Total delete operations"),
160        }
161    }
162
163    /// Records a successful batch flush.
164    pub fn record_flush(&self, records: u64, bytes: u64) {
165        self.records_written.inc_by(records);
166        self.bytes_written.inc_by(bytes);
167        self.batches_flushed.inc();
168    }
169
170    /// Records a bulk write operation.
171    pub fn record_bulk_write(&self) {
172        self.bulk_writes.inc();
173    }
174
175    /// Records an error.
176    pub fn record_error(&self) {
177        self.errors.inc();
178    }
179
180    /// Records insert operations.
181    pub fn record_inserts(&self, count: u64) {
182        self.inserts.inc_by(count);
183    }
184
185    /// Records upsert operations.
186    pub fn record_upserts(&self, count: u64) {
187        self.upserts.inc_by(count);
188    }
189
190    /// Records delete operations.
191    pub fn record_deletes(&self, count: u64) {
192        self.deletes.inc_by(count);
193    }
194}
195
196impl Default for MongoDbSinkMetrics {
197    fn default() -> Self {
198        Self::new(None)
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_source_metrics_record_events() {
208        let m = MongoDbCdcMetrics::new(None);
209        m.record_event("I");
210        m.record_event("I");
211        m.record_event("U");
212        m.record_event("D");
213        m.record_event("DROP");
214        m.record_bytes(1024);
215        m.record_error();
216        m.record_batch();
217        m.record_reconnect();
218
219        assert_eq!(m.events_received.get(), 5);
220        assert_eq!(m.inserts.get(), 2);
221        assert_eq!(m.updates.get(), 1);
222        assert_eq!(m.deletes.get(), 1);
223        assert_eq!(m.lifecycle_events.get(), 1);
224        assert_eq!(m.bytes_received.get(), 1024);
225        assert_eq!(m.errors.get(), 1);
226        assert_eq!(m.batches_produced.get(), 1);
227        assert_eq!(m.reconnects.get(), 1);
228    }
229
230    #[test]
231    fn test_sink_metrics_record_flush() {
232        let m = MongoDbSinkMetrics::new(None);
233        m.record_flush(100, 5000);
234        m.record_bulk_write();
235        m.record_inserts(80);
236        m.record_upserts(15);
237        m.record_deletes(5);
238        m.record_error();
239
240        assert_eq!(m.records_written.get(), 100);
241        assert_eq!(m.bytes_written.get(), 5000);
242        assert_eq!(m.batches_flushed.get(), 1);
243        assert_eq!(m.bulk_writes.get(), 1);
244        assert_eq!(m.inserts.get(), 80);
245        assert_eq!(m.upserts.get(), 15);
246        assert_eq!(m.deletes.get(), 5);
247        assert_eq!(m.errors.get(), 1);
248    }
249}