1use crate::{
6 db::query::plan::{AccessPath, AccessPlan, ExecutablePlan, PlanFingerprint},
7 error::{ErrorClass, ErrorOrigin, InternalError},
8 traits::EntityKind,
9};
10use sha2::{Digest, Sha256};
11
12pub trait QueryTraceSink: Send + Sync {
17 fn on_event(&self, event: QueryTraceEvent);
18}
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum TraceExecutorKind {
26 Load,
27 Save,
28 Delete,
29}
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum TraceAccess {
37 ByKey,
38 ByKeys { count: u32 },
39 KeyRange,
40 IndexPrefix { name: &'static str, prefix_len: u32 },
41 FullScan,
42 Union { branches: u32 },
43 Intersection { branches: u32 },
44}
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51pub enum TracePhase {
52 Access,
53}
54
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub enum QueryTraceEvent {
61 Start {
62 fingerprint: PlanFingerprint,
63 executor: TraceExecutorKind,
64 access: Option<TraceAccess>,
65 },
66 Phase {
67 fingerprint: PlanFingerprint,
68 executor: TraceExecutorKind,
69 access: Option<TraceAccess>,
70 phase: TracePhase,
71 rows: u64,
72 },
73 Finish {
74 fingerprint: PlanFingerprint,
75 executor: TraceExecutorKind,
76 access: Option<TraceAccess>,
77 rows: u64,
78 },
79 Error {
80 fingerprint: PlanFingerprint,
81 executor: TraceExecutorKind,
82 access: Option<TraceAccess>,
83 class: ErrorClass,
84 origin: ErrorOrigin,
85 },
86}
87
88pub struct TraceScope {
93 sink: &'static dyn QueryTraceSink,
94 fingerprint: PlanFingerprint,
95 executor: TraceExecutorKind,
96 access: Option<TraceAccess>,
97}
98
99impl TraceScope {
100 fn new(
101 sink: &'static dyn QueryTraceSink,
102 fingerprint: PlanFingerprint,
103 executor: TraceExecutorKind,
104 access: Option<TraceAccess>,
105 ) -> Self {
106 sink.on_event(QueryTraceEvent::Start {
107 fingerprint,
108 executor,
109 access,
110 });
111 Self {
112 sink,
113 fingerprint,
114 executor,
115 access,
116 }
117 }
118
119 pub(crate) fn finish(self, rows: u64) {
120 self.sink.on_event(QueryTraceEvent::Finish {
121 fingerprint: self.fingerprint,
122 executor: self.executor,
123 access: self.access,
124 rows,
125 });
126 }
127
128 pub(crate) fn phase(&self, phase: TracePhase, rows: u64) {
129 self.sink.on_event(QueryTraceEvent::Phase {
130 fingerprint: self.fingerprint,
131 executor: self.executor,
132 access: self.access,
133 phase,
134 rows,
135 });
136 }
137
138 pub(crate) fn error(self, err: &InternalError) {
139 self.sink.on_event(QueryTraceEvent::Error {
140 fingerprint: self.fingerprint,
141 executor: self.executor,
142 access: self.access,
143 class: err.class,
144 origin: err.origin,
145 });
146 }
147}
148
149pub fn start_plan_trace<E: EntityKind>(
150 sink: Option<&'static dyn QueryTraceSink>,
151 executor: TraceExecutorKind,
152 plan: &ExecutablePlan<E>,
153) -> Option<TraceScope> {
154 let sink = sink?;
155 let access = Some(trace_access_from_plan(plan.access()));
156 let fingerprint = plan.fingerprint();
157 Some(TraceScope::new(sink, fingerprint, executor, access))
158}
159
160pub fn start_exec_trace(
161 sink: Option<&'static dyn QueryTraceSink>,
162 executor: TraceExecutorKind,
163 entity_path: &'static str,
164 access: Option<TraceAccess>,
165 detail: Option<&'static str>,
166) -> Option<TraceScope> {
167 let sink = sink?;
168 let fingerprint = exec_fingerprint(executor, entity_path, detail);
169 Some(TraceScope::new(sink, fingerprint, executor, access))
170}
171
172fn trace_access_from_plan<K>(plan: &AccessPlan<K>) -> TraceAccess {
173 match plan {
174 AccessPlan::Path(path) => trace_access_from_path(path),
175 AccessPlan::Union(children) => {
176 TraceAccess::Union {
178 branches: u32::try_from(children.len()).unwrap_or(u32::MAX),
179 }
180 }
181 AccessPlan::Intersection(children) => {
182 TraceAccess::Intersection {
184 branches: u32::try_from(children.len()).unwrap_or(u32::MAX),
185 }
186 }
187 }
188}
189
190fn trace_access_from_path<K>(path: &AccessPath<K>) -> TraceAccess {
191 match path {
192 AccessPath::ByKey(_) => TraceAccess::ByKey,
193 AccessPath::ByKeys(keys) => {
194 TraceAccess::ByKeys {
196 count: u32::try_from(keys.len()).unwrap_or(u32::MAX),
197 }
198 }
199 AccessPath::KeyRange { .. } => TraceAccess::KeyRange,
200 AccessPath::IndexPrefix { index, values } => {
201 TraceAccess::IndexPrefix {
203 name: index.name,
204 prefix_len: u32::try_from(values.len()).unwrap_or(u32::MAX),
205 }
206 }
207 AccessPath::FullScan => TraceAccess::FullScan,
208 }
209}
210
211fn exec_fingerprint(
212 executor: TraceExecutorKind,
213 entity_path: &'static str,
214 detail: Option<&'static str>,
215) -> PlanFingerprint {
216 let mut hasher = Sha256::new();
217 hasher.update(b"execfp:v1");
218 hasher.update([executor_tag(executor)]);
219 write_str(&mut hasher, entity_path);
220 match detail {
221 Some(detail) => {
222 hasher.update([1u8]);
223 write_str(&mut hasher, detail);
224 }
225 None => {
226 hasher.update([0u8]);
227 }
228 }
229 let digest = hasher.finalize();
230 let mut out = [0u8; 32];
231 out.copy_from_slice(&digest);
232 PlanFingerprint::from_bytes(out)
233}
234
235const fn executor_tag(executor: TraceExecutorKind) -> u8 {
236 match executor {
237 TraceExecutorKind::Load => 0x01,
238 TraceExecutorKind::Save => 0x02,
239 TraceExecutorKind::Delete => 0x03,
240 }
241}
242
243fn write_str(hasher: &mut Sha256, value: &str) {
244 let len = u32::try_from(value.len()).unwrap_or(u32::MAX);
246 hasher.update(len.to_be_bytes());
247 hasher.update(value.as_bytes());
248}