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 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
49pub 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}