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