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