valence_core/instrumentation/backend/
mod.rs1mod 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#[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
50pub 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}