icydb_core/db/diagnostics/
execution_trace.rs1use crate::db::query::plan::OrderDirection;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum ExecutionOptimization {
18 PrimaryKey,
19 PrimaryKeyTopNSeek,
20 SecondaryOrderPushdown,
21 SecondaryOrderTopNSeek,
22 IndexRangeLimitPushdown,
23}
24
25#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
34pub struct ExecutionStats {
35 rows_scanned_pre_filter: u64,
36 rows_after_predicate: u64,
37 rows_after_projection: u64,
38 rows_after_distinct: u64,
39 rows_sorted: u64,
40 keys_streamed: u64,
41 key_stream_micros: u64,
42 ordering_micros: u64,
43 projection_micros: u64,
44 aggregation_micros: u64,
45}
46
47impl ExecutionStats {
48 #[must_use]
50 #[expect(
51 clippy::too_many_arguments,
52 reason = "diagnostics stats DTO exposes the exact flat trace counter surface"
53 )]
54 pub(in crate::db) const fn new(
55 rows_scanned_pre_filter: u64,
56 rows_after_predicate: u64,
57 rows_after_projection: u64,
58 rows_after_distinct: u64,
59 rows_sorted: u64,
60 keys_streamed: u64,
61 key_stream_micros: u64,
62 ordering_micros: u64,
63 projection_micros: u64,
64 aggregation_micros: u64,
65 ) -> Self {
66 Self {
67 rows_scanned_pre_filter,
68 rows_after_predicate,
69 rows_after_projection,
70 rows_after_distinct,
71 rows_sorted,
72 keys_streamed,
73 key_stream_micros,
74 ordering_micros,
75 projection_micros,
76 aggregation_micros,
77 }
78 }
79
80 #[cfg(test)]
82 #[must_use]
83 pub(in crate::db) const fn rows_scanned_pre_filter(&self) -> u64 {
84 self.rows_scanned_pre_filter
85 }
86
87 #[cfg(test)]
89 #[must_use]
90 pub(in crate::db) const fn rows_after_predicate(&self) -> u64 {
91 self.rows_after_predicate
92 }
93
94 #[cfg(test)]
96 #[must_use]
97 pub(in crate::db) const fn rows_after_projection(&self) -> u64 {
98 self.rows_after_projection
99 }
100
101 #[cfg(test)]
103 #[must_use]
104 pub(in crate::db) const fn keys_streamed(&self) -> u64 {
105 self.keys_streamed
106 }
107}
108
109#[cfg_attr(
110 doc,
111 doc = "ExecutionAccessPathVariant\n\nCoarse access path shape recorded in execution traces."
112)]
113#[derive(Clone, Copy, Debug, Eq, PartialEq)]
114pub enum ExecutionAccessPathVariant {
115 ByKey,
116 ByKeys,
117 FullScan,
118 IndexBranchSet,
119 IndexMultiLookup,
120 IndexPrefix,
121 IndexRange,
122 KeyRange,
123 Union,
124 Intersection,
125}
126
127#[cfg_attr(
128 doc,
129 doc = "ExecutionTrace\n\nStructured execution trace snapshot for one load path.\nCaptures plan shape and counters without affecting behavior."
130)]
131#[derive(Clone, Copy, Debug, Eq, PartialEq)]
132pub struct ExecutionTrace {
133 pub(crate) access_path_variant: ExecutionAccessPathVariant,
134 pub(crate) direction: OrderDirection,
135 pub(crate) optimization: Option<ExecutionOptimization>,
136 pub(crate) keys_scanned: u64,
137 pub(crate) rows_materialized: u64,
138 pub(crate) rows_returned: u64,
139 pub(crate) execution_time_micros: u64,
140 pub(crate) index_only: bool,
141 pub(crate) continuation_applied: bool,
142 pub(crate) index_predicate_applied: bool,
143 pub(crate) index_predicate_keys_rejected: u64,
144 pub(crate) distinct_keys_deduped: u64,
145 pub(crate) execution_stats: Option<ExecutionStats>,
146}
147
148#[cfg_attr(
149 doc,
150 doc = "ExecutionMetrics\n\nCompact metrics view derived from one `ExecutionTrace`.\nKept small for lightweight observability surfaces."
151)]
152#[derive(Clone, Copy, Debug, Eq, PartialEq)]
153pub struct ExecutionMetrics {
154 pub(crate) rows_scanned: u64,
155 pub(crate) rows_materialized: u64,
156 pub(crate) execution_time_micros: u64,
157 pub(crate) index_only: bool,
158}
159
160impl ExecutionTrace {
161 #[must_use]
163 pub(in crate::db) const fn new_from_variant(
164 access_path_variant: ExecutionAccessPathVariant,
165 direction: OrderDirection,
166 continuation_applied: bool,
167 ) -> Self {
168 Self {
169 access_path_variant,
170 direction,
171 optimization: None,
172 keys_scanned: 0,
173 rows_materialized: 0,
174 rows_returned: 0,
175 execution_time_micros: 0,
176 index_only: false,
177 continuation_applied,
178 index_predicate_applied: false,
179 index_predicate_keys_rejected: 0,
180 distinct_keys_deduped: 0,
181 execution_stats: None,
182 }
183 }
184
185 #[expect(clippy::too_many_arguments)]
187 pub(in crate::db) fn set_path_outcome(
188 &mut self,
189 optimization: Option<ExecutionOptimization>,
190 keys_scanned: usize,
191 rows_materialized: usize,
192 rows_returned: usize,
193 execution_time_micros: u64,
194 index_only: bool,
195 index_predicate_applied: bool,
196 index_predicate_keys_rejected: u64,
197 distinct_keys_deduped: u64,
198 ) {
199 self.optimization = optimization;
200 self.keys_scanned = u64::try_from(keys_scanned).unwrap_or(u64::MAX);
201 self.rows_materialized = u64::try_from(rows_materialized).unwrap_or(u64::MAX);
202 self.rows_returned = u64::try_from(rows_returned).unwrap_or(u64::MAX);
203 self.execution_time_micros = execution_time_micros;
204 self.index_only = index_only;
205 self.index_predicate_applied = index_predicate_applied;
206 self.index_predicate_keys_rejected = index_predicate_keys_rejected;
207 self.distinct_keys_deduped = distinct_keys_deduped;
208 debug_assert_eq!(
209 self.keys_scanned,
210 u64::try_from(keys_scanned).unwrap_or(u64::MAX),
211 "execution trace keys_scanned must match rows_scanned metrics input",
212 );
213 }
214
215 pub(in crate::db) const fn set_execution_stats(&mut self, stats: Option<ExecutionStats>) {
217 self.execution_stats = stats;
218 }
219
220 #[must_use]
222 pub const fn metrics(&self) -> ExecutionMetrics {
223 ExecutionMetrics {
224 rows_scanned: self.keys_scanned,
225 rows_materialized: self.rows_materialized,
226 execution_time_micros: self.execution_time_micros,
227 index_only: self.index_only,
228 }
229 }
230
231 #[must_use]
233 pub const fn access_path_variant(&self) -> ExecutionAccessPathVariant {
234 self.access_path_variant
235 }
236
237 #[must_use]
239 pub const fn direction(&self) -> OrderDirection {
240 self.direction
241 }
242
243 #[must_use]
245 pub const fn optimization(&self) -> Option<ExecutionOptimization> {
246 self.optimization
247 }
248
249 #[must_use]
251 pub const fn keys_scanned(&self) -> u64 {
252 self.keys_scanned
253 }
254
255 #[must_use]
257 pub const fn rows_materialized(&self) -> u64 {
258 self.rows_materialized
259 }
260
261 #[must_use]
263 pub const fn rows_returned(&self) -> u64 {
264 self.rows_returned
265 }
266
267 #[must_use]
269 pub const fn execution_time_micros(&self) -> u64 {
270 self.execution_time_micros
271 }
272
273 #[must_use]
275 pub const fn index_only(&self) -> bool {
276 self.index_only
277 }
278
279 #[must_use]
281 pub const fn continuation_applied(&self) -> bool {
282 self.continuation_applied
283 }
284
285 #[must_use]
287 pub const fn index_predicate_applied(&self) -> bool {
288 self.index_predicate_applied
289 }
290
291 #[must_use]
293 pub const fn index_predicate_keys_rejected(&self) -> u64 {
294 self.index_predicate_keys_rejected
295 }
296
297 #[must_use]
299 pub const fn distinct_keys_deduped(&self) -> u64 {
300 self.distinct_keys_deduped
301 }
302
303 #[cfg(test)]
305 #[must_use]
306 pub(in crate::db) const fn execution_stats(&self) -> Option<ExecutionStats> {
307 self.execution_stats
308 }
309}
310
311impl ExecutionMetrics {
312 #[must_use]
314 pub const fn rows_scanned(&self) -> u64 {
315 self.rows_scanned
316 }
317
318 #[must_use]
320 pub const fn rows_materialized(&self) -> u64 {
321 self.rows_materialized
322 }
323
324 #[must_use]
326 pub const fn execution_time_micros(&self) -> u64 {
327 self.execution_time_micros
328 }
329
330 #[must_use]
332 pub const fn index_only(&self) -> bool {
333 self.index_only
334 }
335}