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
81#[cfg_attr(
82 doc,
83 doc = "ExecutionAccessPathVariant\n\nCoarse access path shape recorded in execution traces."
84)]
85#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86pub enum ExecutionAccessPathVariant {
87 ByKey,
88 ByKeys,
89 FullScan,
90 IndexBranchSet,
91 IndexMultiLookup,
92 IndexPrefix,
93 IndexRange,
94 KeyRange,
95 Union,
96 Intersection,
97}
98
99#[cfg_attr(
100 doc,
101 doc = "ExecutionTrace\n\nStructured execution trace snapshot for one load path.\nCaptures plan shape and counters without affecting behavior."
102)]
103#[derive(Clone, Copy, Debug, Eq, PartialEq)]
104pub struct ExecutionTrace {
105 pub(crate) access_path_variant: ExecutionAccessPathVariant,
106 pub(crate) direction: OrderDirection,
107 pub(crate) optimization: Option<ExecutionOptimization>,
108 pub(crate) keys_scanned: u64,
109 pub(crate) rows_materialized: u64,
110 pub(crate) rows_returned: u64,
111 pub(crate) execution_time_micros: u64,
112 pub(crate) index_only: bool,
113 pub(crate) continuation_applied: bool,
114 pub(crate) index_predicate_applied: bool,
115 pub(crate) index_predicate_keys_rejected: u64,
116 pub(crate) distinct_keys_deduped: u64,
117 pub(crate) execution_stats: Option<ExecutionStats>,
118}
119
120#[cfg_attr(
121 doc,
122 doc = "ExecutionMetrics\n\nCompact metrics view derived from one `ExecutionTrace`.\nKept small for lightweight observability surfaces."
123)]
124#[derive(Clone, Copy, Debug, Eq, PartialEq)]
125pub struct ExecutionMetrics {
126 pub(crate) rows_scanned: u64,
127 pub(crate) rows_materialized: u64,
128 pub(crate) execution_time_micros: u64,
129 pub(crate) index_only: bool,
130}
131
132impl ExecutionTrace {
133 #[must_use]
135 pub(in crate::db) const fn new_from_variant(
136 access_path_variant: ExecutionAccessPathVariant,
137 direction: OrderDirection,
138 continuation_applied: bool,
139 ) -> Self {
140 Self {
141 access_path_variant,
142 direction,
143 optimization: None,
144 keys_scanned: 0,
145 rows_materialized: 0,
146 rows_returned: 0,
147 execution_time_micros: 0,
148 index_only: false,
149 continuation_applied,
150 index_predicate_applied: false,
151 index_predicate_keys_rejected: 0,
152 distinct_keys_deduped: 0,
153 execution_stats: None,
154 }
155 }
156
157 #[expect(clippy::too_many_arguments)]
159 pub(in crate::db) fn set_path_outcome(
160 &mut self,
161 optimization: Option<ExecutionOptimization>,
162 keys_scanned: usize,
163 rows_materialized: usize,
164 rows_returned: usize,
165 execution_time_micros: u64,
166 index_only: bool,
167 index_predicate_applied: bool,
168 index_predicate_keys_rejected: u64,
169 distinct_keys_deduped: u64,
170 ) {
171 self.optimization = optimization;
172 self.keys_scanned = u64::try_from(keys_scanned).unwrap_or(u64::MAX);
173 self.rows_materialized = u64::try_from(rows_materialized).unwrap_or(u64::MAX);
174 self.rows_returned = u64::try_from(rows_returned).unwrap_or(u64::MAX);
175 self.execution_time_micros = execution_time_micros;
176 self.index_only = index_only;
177 self.index_predicate_applied = index_predicate_applied;
178 self.index_predicate_keys_rejected = index_predicate_keys_rejected;
179 self.distinct_keys_deduped = distinct_keys_deduped;
180 debug_assert_eq!(
181 self.keys_scanned,
182 u64::try_from(keys_scanned).unwrap_or(u64::MAX),
183 "execution trace keys_scanned must match rows_scanned metrics input",
184 );
185 }
186
187 pub(in crate::db) const fn set_execution_stats(&mut self, stats: Option<ExecutionStats>) {
189 self.execution_stats = stats;
190 }
191
192 #[must_use]
194 pub const fn metrics(&self) -> ExecutionMetrics {
195 ExecutionMetrics {
196 rows_scanned: self.keys_scanned,
197 rows_materialized: self.rows_materialized,
198 execution_time_micros: self.execution_time_micros,
199 index_only: self.index_only,
200 }
201 }
202
203 #[must_use]
205 pub const fn access_path_variant(&self) -> ExecutionAccessPathVariant {
206 self.access_path_variant
207 }
208
209 #[must_use]
211 pub const fn direction(&self) -> OrderDirection {
212 self.direction
213 }
214
215 #[must_use]
217 pub const fn optimization(&self) -> Option<ExecutionOptimization> {
218 self.optimization
219 }
220
221 #[must_use]
223 pub const fn keys_scanned(&self) -> u64 {
224 self.keys_scanned
225 }
226
227 #[must_use]
229 pub const fn rows_materialized(&self) -> u64 {
230 self.rows_materialized
231 }
232
233 #[must_use]
235 pub const fn rows_returned(&self) -> u64 {
236 self.rows_returned
237 }
238
239 #[must_use]
241 pub const fn execution_time_micros(&self) -> u64 {
242 self.execution_time_micros
243 }
244
245 #[must_use]
247 pub const fn index_only(&self) -> bool {
248 self.index_only
249 }
250
251 #[must_use]
253 pub const fn continuation_applied(&self) -> bool {
254 self.continuation_applied
255 }
256
257 #[must_use]
259 pub const fn index_predicate_applied(&self) -> bool {
260 self.index_predicate_applied
261 }
262
263 #[must_use]
265 pub const fn index_predicate_keys_rejected(&self) -> u64 {
266 self.index_predicate_keys_rejected
267 }
268
269 #[must_use]
271 pub const fn distinct_keys_deduped(&self) -> u64 {
272 self.distinct_keys_deduped
273 }
274}
275
276impl ExecutionMetrics {
277 #[must_use]
279 pub const fn rows_scanned(&self) -> u64 {
280 self.rows_scanned
281 }
282
283 #[must_use]
285 pub const fn rows_materialized(&self) -> u64 {
286 self.rows_materialized
287 }
288
289 #[must_use]
291 pub const fn execution_time_micros(&self) -> u64 {
292 self.execution_time_micros
293 }
294
295 #[must_use]
297 pub const fn index_only(&self) -> bool {
298 self.index_only
299 }
300}