Skip to main content

valence_core/instrumentation/backend/
mod.rs

1//! [`DatabaseBackend`] decorator for read/write/error telemetry.
2
3mod reads;
4mod writes;
5
6use std::sync::Arc;
7
8use async_trait::async_trait;
9
10use crate::backend::DatabaseBackend;
11use crate::error::Result;
12use crate::ttl::{BackendTtlCapability, SchemaTtlPolicy};
13
14use super::metrics;
15
16/// Wraps an inner backend and emits instrumentation telemetry on every I/O call.
17#[derive(Debug)]
18pub struct InstrumentedBackend {
19    pub(super) inner: Arc<dyn DatabaseBackend>,
20}
21
22impl InstrumentedBackend {
23    pub fn new(inner: Arc<dyn DatabaseBackend>) -> Self {
24        Self { inner }
25    }
26
27    pub(super) fn telemetry_label(&self) -> &'static str {
28        self.inner.capabilities().telemetry_label
29    }
30
31    pub(super) fn on_err(&self, operation: &str, err: &crate::error::Error) {
32        metrics::record_db_error(operation, self.telemetry_label(), &err.to_string());
33    }
34
35    pub(super) fn record_io_timing(
36        &self,
37        operation: &str,
38        table: &str,
39        op: &str,
40        wall_ms: f64,
41        record_id: Option<&str>,
42    ) {
43        let label = self.telemetry_label();
44        metrics::record_db_wall_ms(table, label, op, wall_ms);
45        metrics::maybe_record_slow_op(operation, table, op, label, wall_ms, record_id);
46    }
47}
48
49/// Wrap `inner` with [`InstrumentedBackend`].
50pub fn wrap_backend(inner: Arc<dyn DatabaseBackend>) -> Arc<dyn DatabaseBackend> {
51    Arc::new(InstrumentedBackend::new(inner))
52}
53
54#[async_trait]
55impl DatabaseBackend for InstrumentedBackend {
56    fn engine_id(&self) -> &'static str {
57        self.inner.engine_id()
58    }
59
60    fn capabilities(&self) -> crate::backend::BackendCapabilities {
61        self.inner.capabilities()
62    }
63
64    fn as_any_local(&self) -> Option<&dyn std::any::Any> {
65        self.inner.as_any_local()
66    }
67
68    async fn use_namespace(&self, ns: &str, db_name: &str) -> Result<()> {
69        self.inner.use_namespace(ns, db_name).await
70    }
71
72    async fn ensure_schemaless_table(&self, table: &str) -> Result<()> {
73        self.inner.ensure_schemaless_table(table).await
74    }
75
76    async fn define_unique_index(&self, table: &str, field: &str) -> Result<()> {
77        self.inner.define_unique_index(table, field).await
78    }
79
80    fn ttl_capability(&self) -> BackendTtlCapability {
81        self.inner.ttl_capability()
82    }
83
84    async fn apply_ttl_policy(&self, table: &str, policy: &SchemaTtlPolicy) -> Result<()> {
85        self.inner.apply_ttl_policy(table, policy).await
86    }
87
88    async fn execute_compiled_query(
89        &self,
90        compiled: &crate::compiled_query::CompiledQuery,
91    ) -> Result<Vec<serde_json::Value>> {
92        self.measured_execute_compiled_query(compiled).await
93    }
94
95    async fn get_record(&self, table: &str, id: &str) -> Result<Option<serde_json::Value>> {
96        self.measured_get_record(table, id).await
97    }
98
99    async fn get_edge_targets(
100        &self,
101        from: &crate::RecordId,
102        edge_table: &str,
103    ) -> Result<Vec<crate::RecordId>> {
104        self.measured_get_edge_targets(from, edge_table).await
105    }
106
107    async fn create_record(
108        &self,
109        table: &str,
110        content: serde_json::Value,
111    ) -> Result<serde_json::Value> {
112        self.measured_create_record(table, content).await
113    }
114
115    async fn update_record(
116        &self,
117        table: &str,
118        id: &str,
119        content: serde_json::Value,
120    ) -> Result<serde_json::Value> {
121        self.measured_update_record(table, id, content).await
122    }
123
124    async fn merge_record(
125        &self,
126        table: &str,
127        id: &str,
128        patch: serde_json::Value,
129    ) -> Result<serde_json::Value> {
130        self.measured_merge_record(table, id, patch).await
131    }
132
133    async fn upsert_record(
134        &self,
135        table: &str,
136        id: &str,
137        content: serde_json::Value,
138    ) -> Result<serde_json::Value> {
139        self.measured_upsert_record(table, id, content).await
140    }
141
142    async fn delete_record(&self, table: &str, id: &str) -> Result<()> {
143        self.measured_delete_record(table, id).await
144    }
145
146    async fn relate_edge(
147        &self,
148        from: &crate::RecordId,
149        edge_table: &str,
150        to: &crate::RecordId,
151    ) -> Result<()> {
152        self.measured_relate_edge(from, edge_table, to).await
153    }
154
155    async fn unrelate_edge(
156        &self,
157        from: &crate::RecordId,
158        edge_table: &str,
159        to: &crate::RecordId,
160    ) -> Result<()> {
161        self.measured_unrelate_edge(from, edge_table, to).await
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use crate::compiled_query::CompiledQuery;
169    use std::sync::atomic::{AtomicUsize, Ordering};
170
171    #[derive(Debug)]
172    struct MockBackend {
173        gets: AtomicUsize,
174    }
175
176    #[async_trait]
177    impl DatabaseBackend for MockBackend {
178        fn engine_id(&self) -> &'static str {
179            "mem"
180        }
181
182        fn capabilities(&self) -> crate::backend::BackendCapabilities {
183            crate::backend::BackendCapabilities::mem()
184        }
185
186        async fn execute_compiled_query(
187            &self,
188            _compiled: &CompiledQuery,
189        ) -> Result<Vec<serde_json::Value>> {
190            Ok(vec![])
191        }
192
193        async fn get_record(&self, _table: &str, _id: &str) -> Result<Option<serde_json::Value>> {
194            self.gets.fetch_add(1, Ordering::SeqCst);
195            Ok(None)
196        }
197
198        async fn create_record(
199            &self,
200            _table: &str,
201            _content: serde_json::Value,
202        ) -> Result<serde_json::Value> {
203            Ok(serde_json::json!({}))
204        }
205
206        async fn update_record(
207            &self,
208            _table: &str,
209            _id: &str,
210            _content: serde_json::Value,
211        ) -> Result<serde_json::Value> {
212            Ok(serde_json::json!({}))
213        }
214
215        async fn upsert_record(
216            &self,
217            _table: &str,
218            _id: &str,
219            _content: serde_json::Value,
220        ) -> Result<serde_json::Value> {
221            Ok(serde_json::json!({}))
222        }
223
224        async fn delete_record(&self, _table: &str, _id: &str) -> Result<()> {
225            Ok(())
226        }
227
228        async fn relate_edge(
229            &self,
230            _from: &crate::RecordId,
231            _edge_table: &str,
232            _to: &crate::RecordId,
233        ) -> Result<()> {
234            Ok(())
235        }
236
237        async fn unrelate_edge(
238            &self,
239            _from: &crate::RecordId,
240            _edge_table: &str,
241            _to: &crate::RecordId,
242        ) -> Result<()> {
243            Ok(())
244        }
245
246        async fn get_edge_targets(
247            &self,
248            _from: &crate::RecordId,
249            _edge_table: &str,
250        ) -> Result<Vec<crate::RecordId>> {
251            Ok(vec![])
252        }
253    }
254
255    #[tokio::test]
256    async fn decorator_delegates_to_inner() {
257        let inner = Arc::new(MockBackend {
258            gets: AtomicUsize::new(0),
259        });
260        let wrapped = wrap_backend(inner.clone());
261        let _ = wrapped.get_record("t", "id").await;
262        assert_eq!(inner.gets.load(Ordering::SeqCst), 1);
263    }
264}