icydb_core/db/diagnostics/
execution_trace.rs1use crate::db::{
7 executor::{ExecutionOptimization, ExecutionStats},
8 query::plan::OrderDirection,
9};
10
11#[cfg_attr(
12 doc,
13 doc = "ExecutionAccessPathVariant\n\nCoarse access path shape recorded in execution traces."
14)]
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub enum ExecutionAccessPathVariant {
17 ByKey,
18 ByKeys,
19 KeyRange,
20 IndexPrefix,
21 IndexMultiLookup,
22 IndexRange,
23 FullScan,
24 Union,
25 Intersection,
26}
27
28#[cfg_attr(
29 doc,
30 doc = "ExecutionTrace\n\nStructured execution trace snapshot for one load path.\nCaptures plan shape and counters without affecting behavior."
31)]
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct ExecutionTrace {
34 pub(crate) access_path_variant: ExecutionAccessPathVariant,
35 pub(crate) direction: OrderDirection,
36 pub(crate) optimization: Option<ExecutionOptimization>,
37 pub(crate) keys_scanned: u64,
38 pub(crate) rows_materialized: u64,
39 pub(crate) rows_returned: u64,
40 pub(crate) execution_time_micros: u64,
41 pub(crate) index_only: bool,
42 pub(crate) continuation_applied: bool,
43 pub(crate) index_predicate_applied: bool,
44 pub(crate) index_predicate_keys_rejected: u64,
45 pub(crate) distinct_keys_deduped: u64,
46 pub(crate) execution_stats: Option<ExecutionStats>,
47}
48
49#[cfg_attr(
50 doc,
51 doc = "ExecutionMetrics\n\nCompact metrics view derived from one `ExecutionTrace`.\nKept small for lightweight observability surfaces."
52)]
53#[derive(Clone, Copy, Debug, Eq, PartialEq)]
54pub struct ExecutionMetrics {
55 pub(crate) rows_scanned: u64,
56 pub(crate) rows_materialized: u64,
57 pub(crate) execution_time_micros: u64,
58 pub(crate) index_only: bool,
59}
60
61impl ExecutionTrace {
62 #[must_use]
64 pub(crate) const fn new_from_variant(
65 access_path_variant: ExecutionAccessPathVariant,
66 direction: OrderDirection,
67 continuation_applied: bool,
68 ) -> Self {
69 Self {
70 access_path_variant,
71 direction,
72 optimization: None,
73 keys_scanned: 0,
74 rows_materialized: 0,
75 rows_returned: 0,
76 execution_time_micros: 0,
77 index_only: false,
78 continuation_applied,
79 index_predicate_applied: false,
80 index_predicate_keys_rejected: 0,
81 distinct_keys_deduped: 0,
82 execution_stats: None,
83 }
84 }
85
86 #[expect(clippy::too_many_arguments)]
88 pub(in crate::db) fn set_path_outcome(
89 &mut self,
90 optimization: Option<ExecutionOptimization>,
91 keys_scanned: usize,
92 rows_materialized: usize,
93 rows_returned: usize,
94 execution_time_micros: u64,
95 index_only: bool,
96 index_predicate_applied: bool,
97 index_predicate_keys_rejected: u64,
98 distinct_keys_deduped: u64,
99 ) {
100 self.optimization = optimization;
101 self.keys_scanned = u64::try_from(keys_scanned).unwrap_or(u64::MAX);
102 self.rows_materialized = u64::try_from(rows_materialized).unwrap_or(u64::MAX);
103 self.rows_returned = u64::try_from(rows_returned).unwrap_or(u64::MAX);
104 self.execution_time_micros = execution_time_micros;
105 self.index_only = index_only;
106 self.index_predicate_applied = index_predicate_applied;
107 self.index_predicate_keys_rejected = index_predicate_keys_rejected;
108 self.distinct_keys_deduped = distinct_keys_deduped;
109 debug_assert_eq!(
110 self.keys_scanned,
111 u64::try_from(keys_scanned).unwrap_or(u64::MAX),
112 "execution trace keys_scanned must match rows_scanned metrics input",
113 );
114 }
115
116 pub(in crate::db) const fn set_execution_stats(&mut self, stats: Option<ExecutionStats>) {
118 self.execution_stats = stats;
119 }
120
121 #[must_use]
123 pub const fn metrics(&self) -> ExecutionMetrics {
124 ExecutionMetrics {
125 rows_scanned: self.keys_scanned,
126 rows_materialized: self.rows_materialized,
127 execution_time_micros: self.execution_time_micros,
128 index_only: self.index_only,
129 }
130 }
131
132 #[must_use]
134 pub const fn access_path_variant(&self) -> ExecutionAccessPathVariant {
135 self.access_path_variant
136 }
137
138 #[must_use]
140 pub const fn direction(&self) -> OrderDirection {
141 self.direction
142 }
143
144 #[must_use]
146 pub const fn optimization(&self) -> Option<ExecutionOptimization> {
147 self.optimization
148 }
149
150 #[must_use]
152 pub const fn keys_scanned(&self) -> u64 {
153 self.keys_scanned
154 }
155
156 #[must_use]
158 pub const fn rows_materialized(&self) -> u64 {
159 self.rows_materialized
160 }
161
162 #[must_use]
164 pub const fn rows_returned(&self) -> u64 {
165 self.rows_returned
166 }
167
168 #[must_use]
170 pub const fn execution_time_micros(&self) -> u64 {
171 self.execution_time_micros
172 }
173
174 #[must_use]
176 pub const fn index_only(&self) -> bool {
177 self.index_only
178 }
179
180 #[must_use]
182 pub const fn continuation_applied(&self) -> bool {
183 self.continuation_applied
184 }
185
186 #[must_use]
188 pub const fn index_predicate_applied(&self) -> bool {
189 self.index_predicate_applied
190 }
191
192 #[must_use]
194 pub const fn index_predicate_keys_rejected(&self) -> u64 {
195 self.index_predicate_keys_rejected
196 }
197
198 #[must_use]
200 pub const fn distinct_keys_deduped(&self) -> u64 {
201 self.distinct_keys_deduped
202 }
203
204 #[must_use]
206 #[cfg_attr(
207 not(test),
208 expect(
209 dead_code,
210 reason = "execution stats are an internal diagnostics/testing surface"
211 )
212 )]
213 pub(in crate::db) const fn execution_stats(&self) -> Option<ExecutionStats> {
214 self.execution_stats
215 }
216}
217
218impl ExecutionMetrics {
219 #[must_use]
221 pub const fn rows_scanned(&self) -> u64 {
222 self.rows_scanned
223 }
224
225 #[must_use]
227 pub const fn rows_materialized(&self) -> u64 {
228 self.rows_materialized
229 }
230
231 #[must_use]
233 pub const fn execution_time_micros(&self) -> u64 {
234 self.execution_time_micros
235 }
236
237 #[must_use]
239 pub const fn index_only(&self) -> bool {
240 self.index_only
241 }
242}