1use std::fmt::{Debug, Display};
13use std::sync::Arc;
14
15use mz_catalog::memory::objects::Cluster;
16use mz_compute_types::dataflows::DataflowDescription;
17use mz_compute_types::plan::LirRelationExpr;
18use mz_expr::explain::ExplainContext;
19use mz_expr::{MirRelationExpr, MirScalarExpr, OptimizedMirRelationExpr, RowSetFinishing};
20use mz_ore::collections::CollectionExt;
21use mz_repr::explain::tracing::{PlanTrace, TraceEntry};
22use mz_repr::explain::{
23 Explain, ExplainConfig, ExplainError, ExplainFormat, ExprHumanizer, UsedIndexes,
24};
25use mz_repr::optimize::OptimizerFeatures;
26use mz_repr::{Datum, Row};
27use mz_sql::ast::display::AstDisplay;
28use mz_sql::plan::{self, HirRelationExpr, HirScalarExpr};
29use mz_sql_parser::ast::{ExplainStage, NamedPlan};
30use mz_transform::dataflow::DataflowMetainfo;
31use mz_transform::notice::RawOptimizerNotice;
32use smallvec::SmallVec;
33use tracing::dispatcher;
34use tracing_subscriber::prelude::*;
35
36use crate::AdapterError;
37use crate::coord::peek::FastPathPlan;
38use crate::explain::Explainable;
39use crate::explain::insights::{self, PlanInsightsContext};
40
41pub struct OptimizerTrace(dispatcher::Dispatch);
55
56impl std::fmt::Debug for OptimizerTrace {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.debug_tuple("OptimizerTrace").finish() }
60}
61
62impl OptimizerTrace {
63 pub fn new(filter: Option<SmallVec<[NamedPlan; 4]>>) -> OptimizerTrace {
69 let filter = || filter.clone();
70 if let Some(global_subscriber) = mz_ore::tracing::GLOBAL_SUBSCRIBER.get() {
71 let subscriber = Arc::clone(global_subscriber)
72 .with(PlanTrace::<String>::new(filter()))
75 .with(PlanTrace::<HirScalarExpr>::new(filter()))
76 .with(PlanTrace::<MirScalarExpr>::new(filter()))
77 .with(PlanTrace::<HirRelationExpr>::new(filter()))
79 .with(PlanTrace::<MirRelationExpr>::new(filter()))
80 .with(PlanTrace::<DataflowDescription<OptimizedMirRelationExpr>>::new(filter()))
81 .with(PlanTrace::<DataflowDescription<LirRelationExpr>>::new(
82 filter(),
83 ))
84 .with(PlanTrace::<FastPathPlan>::new(None))
86 .with(PlanTrace::<UsedIndexes>::new(None))
87 .with(tracing::level_filters::LevelFilter::TRACE);
96
97 OptimizerTrace(dispatcher::Dispatch::new(subscriber))
98 } else {
99 let subscriber = tracing_subscriber::registry()
102 .with(PlanTrace::<String>::new(filter()))
103 .with(PlanTrace::<HirScalarExpr>::new(filter()))
104 .with(PlanTrace::<MirScalarExpr>::new(filter()))
105 .with(PlanTrace::<HirRelationExpr>::new(filter()))
106 .with(PlanTrace::<MirRelationExpr>::new(filter()))
107 .with(PlanTrace::<DataflowDescription<OptimizedMirRelationExpr>>::new(filter()))
108 .with(PlanTrace::<DataflowDescription<LirRelationExpr>>::new(
109 filter(),
110 ))
111 .with(PlanTrace::<FastPathPlan>::new(None))
112 .with(PlanTrace::<UsedIndexes>::new(None))
113 .with(tracing::level_filters::LevelFilter::TRACE);
114
115 OptimizerTrace(dispatcher::Dispatch::new(subscriber))
116 }
117 }
118
119 pub fn as_guard<'s>(&'s self) -> DispatchGuard<'s> {
124 let dispatch = self.0.clone();
125 let tracing_guard = tracing::dispatcher::set_default(&dispatch);
126
127 DispatchGuard {
128 _tracing_guard: tracing_guard,
129 _life: std::marker::PhantomData,
130 }
131 }
132
133 pub async fn into_rows(
136 self,
137 format: ExplainFormat,
138 config: &ExplainConfig,
139 features: &OptimizerFeatures,
140 humanizer: &dyn ExprHumanizer,
141 row_set_finishing: Option<RowSetFinishing>,
142 target_cluster: Option<&Cluster>,
143 dataflow_metainfo: DataflowMetainfo,
144 stage: ExplainStage,
145 stmt_kind: plan::ExplaineeStatementKind,
146 insights_ctx: Option<Box<PlanInsightsContext>>,
147 ) -> Result<Vec<Row>, AdapterError> {
148 let collect_all = |format| {
149 self.collect_all(
150 format,
151 config,
152 features,
153 humanizer,
154 row_set_finishing.clone(),
155 target_cluster.map(|c| c.name.as_str()),
156 dataflow_metainfo.clone(),
157 )
158 };
159
160 let rows = match stage {
161 ExplainStage::Trace => {
162 let rows = collect_all(format)?
165 .0
166 .into_iter()
167 .map(|entry| {
168 let span_duration = u64::try_from(entry.span_duration.as_nanos());
170 Row::pack_slice(&[
171 Datum::from(span_duration.unwrap_or(u64::MAX)),
172 Datum::from(entry.path.as_str()),
173 Datum::from(entry.plan.as_str()),
174 ])
175 })
176 .collect();
177 rows
178 }
179 ExplainStage::PlanInsights => {
180 if format != ExplainFormat::Json {
181 coord_bail!("EXPLAIN PLAN INSIGHTS only supports JSON format");
182 }
183
184 let mut text_traces = collect_all(ExplainFormat::Text)?;
185 let mut json_traces = collect_all(ExplainFormat::Json)?;
186 let global_plan = self.collect_global_plan();
187 let fast_path_plan = self.collect_fast_path_plan();
188
189 let mut get_plan = |name: NamedPlan| {
192 let text_plan = match text_traces.remove(name.path()) {
193 None => "<unknown>".into(),
194 Some(entry) => entry.plan,
195 };
196 let json_plan = match json_traces.remove(name.path()) {
197 None => serde_json::Value::Null,
198 Some(entry) => serde_json::from_str(&entry.plan).unwrap_or_else(|e| {
199 serde_json::json!({
200 "error": format!("internal error: {e}"),
201 })
202 }),
203 };
204 serde_json::json!({
205 "text": text_plan,
206 "json": json_plan,
207 })
208 };
209
210 let is_fast_path = fast_path_plan.is_some();
211 let mut plan_insights =
212 insights::plan_insights(humanizer, global_plan, fast_path_plan);
213 let mut redacted_sql = None;
214 if let Some(insights_ctx) = insights_ctx {
215 redacted_sql = insights_ctx
216 .stmt
217 .as_ref()
218 .map(|s| Some(s.to_ast_string_redacted()));
219 if let (Some(plan_insights), false) = (plan_insights.as_mut(), is_fast_path) {
220 if insights_ctx.enable_re_optimize {
221 plan_insights
222 .compute_fast_path_clusters(humanizer, insights_ctx)
223 .await;
224 }
225 }
226 }
227 let cluster = target_cluster.map(|c| {
228 serde_json::json!({
229 "name": c.name,
230 "id": c.id,
231 })
232 });
233
234 let output = serde_json::json!({
235 "plans": {
236 "raw": get_plan(NamedPlan::Raw),
237 "optimized": {
238 "global": get_plan(NamedPlan::Global),
239 "fast_path": get_plan(NamedPlan::FastPath),
240 }
241 },
242 "insights": plan_insights,
243 "cluster": cluster,
244 "redacted_sql": redacted_sql,
245 });
246 let output = serde_json::to_string_pretty(&output).expect("JSON string");
247 vec![Row::pack_slice(&[Datum::from(output.as_str())])]
248 }
249 _ => {
250 let path = stage
254 .paths()
255 .map(|path| path.into_element().path())
256 .ok_or_else(|| {
257 AdapterError::Internal("explain stage unexpectedly missing path".into())
258 })?;
259 let mut traces = collect_all(format)?;
260
261 let plan = if stage.show_fast_path() && !config.no_fast_path {
264 traces
265 .remove(NamedPlan::FastPath.path())
266 .or_else(|| traces.remove(path))
267 } else {
268 traces.remove(path)
269 };
270
271 let row = plan
272 .map(|entry| Row::pack_slice(&[Datum::from(entry.plan.as_str())]))
273 .ok_or_else(|| {
274 if !stmt_kind.supports(&stage) {
275 AdapterError::Unstructured(anyhow::anyhow!(format!(
277 "cannot EXPLAIN {stage} FOR {stmt_kind}"
278 )))
279 } else {
280 AdapterError::Internal(format!(
282 "stage `{path}` not present in the collected optimizer trace",
283 ))
284 }
285 })?;
286 vec![row]
287 }
288 };
289
290 drop(self);
300 tracing_core::callsite::rebuild_interest_cache();
301 Ok(rows)
302 }
303
304 pub async fn into_plan_insights(
307 self,
308 features: &OptimizerFeatures,
309 humanizer: &dyn ExprHumanizer,
310 row_set_finishing: Option<RowSetFinishing>,
311 target_cluster: Option<&Cluster>,
312 dataflow_metainfo: DataflowMetainfo,
313 insights_ctx: Option<Box<PlanInsightsContext>>,
314 ) -> Result<String, AdapterError> {
315 let rows = self
316 .into_rows(
317 ExplainFormat::Json,
318 &ExplainConfig::default(),
319 features,
320 humanizer,
321 row_set_finishing,
322 target_cluster,
323 dataflow_metainfo,
324 ExplainStage::PlanInsights,
325 plan::ExplaineeStatementKind::Select,
326 insights_ctx,
327 )
328 .await?;
329
330 Ok(rows.into_element().into_element().unwrap_str().into())
334 }
335
336 fn collect_all(
339 &self,
340 format: ExplainFormat,
341 config: &ExplainConfig,
342 features: &OptimizerFeatures,
343 humanizer: &dyn ExprHumanizer,
344 row_set_finishing: Option<RowSetFinishing>,
345 target_cluster: Option<&str>,
346 dataflow_metainfo: DataflowMetainfo,
347 ) -> Result<TraceEntries<String>, ExplainError> {
348 let mut results = vec![];
349
350 let mut context = ExplainContext {
353 config,
354 features,
355 humanizer,
356 cardinality_stats: Default::default(), used_indexes: Default::default(),
358 finishing: row_set_finishing.clone(),
359 duration: Default::default(),
360 target_cluster,
361 optimizer_notices: RawOptimizerNotice::explain(
362 &dataflow_metainfo.optimizer_notices,
363 humanizer,
364 config.redacted,
365 )?,
366 };
367
368 results.extend(itertools::chain!(
370 self.collect_explainable_entries::<HirRelationExpr>(&format, &mut context)?,
371 self.collect_explainable_entries::<MirRelationExpr>(&format, &mut context)?,
372 ));
373
374 let mut context = ExplainContext {
376 config,
377 features,
378 humanizer,
379 cardinality_stats: Default::default(), used_indexes: Default::default(),
381 finishing: row_set_finishing,
382 duration: Default::default(),
383 target_cluster,
384 optimizer_notices: RawOptimizerNotice::explain(
385 &dataflow_metainfo.optimizer_notices,
386 humanizer,
387 config.redacted,
388 )?,
389 };
390 results.extend(itertools::chain!(
391 self.collect_explainable_entries::<DataflowDescription<OptimizedMirRelationExpr>>(
392 &format,
393 &mut context,
394 )?,
395 self.collect_explainable_entries::<DataflowDescription<LirRelationExpr>>(
396 &format,
397 &mut context
398 )?,
399 self.collect_explainable_entries::<FastPathPlan>(&format, &mut context)?,
400 ));
401
402 results.extend(itertools::chain!(
405 self.collect_scalar_entries::<HirScalarExpr>(),
406 self.collect_scalar_entries::<MirScalarExpr>(),
407 self.collect_string_entries(),
408 ));
409
410 results.sort_by_key(|x| x.instant);
414
415 Ok(TraceEntries(results))
416 }
417
418 fn collect_global_plan(&self) -> Option<DataflowDescription<OptimizedMirRelationExpr>> {
420 self.0
421 .downcast_ref::<PlanTrace<DataflowDescription<OptimizedMirRelationExpr>>>()
422 .and_then(|trace| trace.find(NamedPlan::Global.path()))
423 .map(|entry| entry.plan)
424 }
425
426 fn collect_fast_path_plan(&self) -> Option<FastPathPlan> {
428 self.0
429 .downcast_ref::<PlanTrace<FastPathPlan>>()
430 .and_then(|trace| trace.find(NamedPlan::FastPath.path()))
431 .map(|entry| entry.plan)
432 }
433
434 fn collect_explainable_entries<T>(
437 &self,
438 format: &ExplainFormat,
439 context: &mut ExplainContext,
440 ) -> Result<Vec<TraceEntry<String>>, ExplainError>
441 where
442 T: Clone + Debug + 'static,
443 for<'a> Explainable<'a, T>: Explain<'a, Context = ExplainContext<'a>>,
444 {
445 if let Some(trace) = self.0.downcast_ref::<PlanTrace<T>>() {
446 let used_indexes_trace = self.0.downcast_ref::<PlanTrace<UsedIndexes>>();
448
449 trace
450 .collect_as_vec()
451 .into_iter()
452 .map(|mut entry| {
453 context.duration = entry.full_duration;
455
456 let used_indexes = used_indexes_trace.map(|t| t.used_indexes_for(&entry.path));
458
459 let plan = if let Some(mut used_indexes) = used_indexes {
461 std::mem::swap(&mut context.used_indexes, &mut used_indexes);
465 let plan = Explainable::new(&mut entry.plan).explain(format, context)?;
466 std::mem::swap(&mut context.used_indexes, &mut used_indexes);
467 plan
468 } else {
469 Explainable::new(&mut entry.plan).explain(format, context)?
472 };
473
474 Ok(TraceEntry {
475 instant: entry.instant,
476 span_duration: entry.span_duration,
477 full_duration: entry.full_duration,
478 path: entry.path,
479 plan,
480 })
481 })
482 .collect()
483 } else {
484 unreachable!("collect_explainable_entries called with wrong plan type T");
485 }
486 }
487
488 fn collect_scalar_entries<T>(&self) -> Vec<TraceEntry<String>>
490 where
491 T: Clone + Debug + 'static,
492 T: Display,
493 {
494 if let Some(trace) = self.0.downcast_ref::<PlanTrace<T>>() {
495 trace
496 .collect_as_vec()
497 .into_iter()
498 .map(|entry| TraceEntry {
499 instant: entry.instant,
500 span_duration: entry.span_duration,
501 full_duration: entry.full_duration,
502 path: entry.path,
503 plan: entry.plan.to_string(),
504 })
505 .collect()
506 } else {
507 vec![]
508 }
509 }
510
511 fn collect_string_entries(&self) -> Vec<TraceEntry<String>> {
513 if let Some(trace) = self.0.downcast_ref::<PlanTrace<String>>() {
514 trace.collect_as_vec()
515 } else {
516 vec![]
517 }
518 }
519}
520
521pub struct DispatchGuard<'a> {
523 _tracing_guard: tracing::subscriber::DefaultGuard,
524 _life: std::marker::PhantomData<&'a ()>,
525}
526
527pub struct TraceEntries<T>(pub Vec<TraceEntry<T>>);
529
530impl<T> TraceEntries<T> {
531 pub fn remove(&mut self, path: &'static str) -> Option<TraceEntry<T>> {
534 let index = self.0.iter().position(|entry| entry.path == path);
535 index.map(|index| self.0.remove(index))
536 }
537}