Skip to main content

icydb_core/db/executor/
trace.rs

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