11use crate :: channel_resolver_ext:: get_distributed_channel_resolver;
22use crate :: execution_plans:: NetworkCoalesceExec ;
3+ use crate :: metrics:: TaskMetricsRewriter ;
34use crate :: { ChannelResolver , NetworkShuffleExec , PartitionIsolatorExec } ;
45use datafusion:: common:: { exec_err, internal_datafusion_err, internal_err, plan_err} ;
56use datafusion:: error:: { DataFusionError , Result } ;
67use datafusion:: execution:: TaskContext ;
8+ use datafusion:: physical_plan:: display:: DisplayableExecutionPlan ;
79use datafusion:: physical_plan:: {
810 DisplayAs , DisplayFormatType , ExecutionPlan , ExecutionPlanProperties , displayable,
911} ;
1012use datafusion:: prelude:: SessionContext ;
13+ use datafusion:: sql:: sqlparser:: keywords:: CHAIN ;
1114use itertools:: Itertools ;
1215use rand:: Rng ;
1316use std:: collections:: VecDeque ;
@@ -91,6 +94,8 @@ pub struct StageExec {
9194 pub tasks : Vec < ExecutionTask > ,
9295 /// tree depth of our location in the stage tree, used for display only
9396 pub depth : usize ,
97+
98+ pub display_ctx : Option < DisplayCtx > ,
9499}
95100
96101/// A [StageExec] that is the input of another [StageExec].
@@ -192,6 +197,7 @@ impl StageExec {
192197 . collect ( ) ,
193198 tasks : vec ! [ ExecutionTask { url: None } ; n_tasks] ,
194199 depth : 0 ,
200+ display_ctx : None ,
195201 }
196202 }
197203
@@ -239,6 +245,7 @@ impl StageExec {
239245 inputs : assigned_input_stages,
240246 tasks : assigned_tasks,
241247 depth : self . depth ,
248+ display_ctx : self . display_ctx . clone ( ) ,
242249 } ;
243250
244251 Ok ( assigned_stage)
@@ -292,9 +299,27 @@ impl ExecutionPlan for StageExec {
292299
293300 fn with_new_children (
294301 self : Arc < Self > ,
295- _children : Vec < Arc < dyn ExecutionPlan > > ,
302+ children : Vec < Arc < dyn ExecutionPlan > > ,
296303 ) -> Result < Arc < dyn ExecutionPlan > > {
297- plan_err ! ( "with_new_children() not supported for StageExec" )
304+ let num_children = children. len ( ) ;
305+ let child_stage_execs = children
306+ . into_iter ( )
307+ . filter ( |child| child. as_any ( ) . downcast_ref :: < StageExec > ( ) . is_some ( ) )
308+ . map ( |child| child. as_any ( ) . downcast_ref :: < StageExec > ( ) . unwrap ( ) . clone ( ) ) . collect :: < Vec < _ > > ( ) ;
309+ if child_stage_execs. len ( ) != num_children {
310+ return plan_err ! ( "not all children are StageExec" ) ;
311+ }
312+ let stage = StageExec {
313+ query_id : self . query_id . clone ( ) ,
314+ num : self . num ,
315+ name : self . name . clone ( ) ,
316+ plan : self . plan . clone ( ) ,
317+ inputs : child_stage_execs. into_iter ( ) . map ( |s| InputStage :: Decoded ( Arc :: new ( s) ) ) . collect ( ) ,
318+ tasks : self . tasks . clone ( ) ,
319+ depth : self . depth ,
320+ display_ctx : self . display_ctx . clone ( ) ,
321+ } ;
322+ Ok ( Arc :: new ( stage) )
298323 }
299324
300325 fn properties ( & self ) -> & datafusion:: physical_plan:: PlanProperties {
@@ -347,6 +372,10 @@ impl ExecutionPlan for StageExec {
347372use bytes:: Bytes ;
348373use datafusion:: common:: tree_node:: { TreeNode , TreeNodeRecursion } ;
349374use datafusion:: physical_expr:: Partitioning ;
375+ use datafusion:: common:: HashMap ;
376+ use crate :: metrics:: proto:: MetricsSetProto ;
377+ use crate :: protobuf:: StageKey ;
378+
350379/// Be able to display a nice tree for stages.
351380///
352381/// The challenge to doing this at the moment is that `TreeRenderVistor`
@@ -367,9 +396,29 @@ const LDCORNER: &str = "└"; // Left bottom corner
367396const VERTICAL : & str = "│" ; // Vertical line
368397const HORIZONTAL : & str = "─" ; // Horizontal line
369398
399+ // Context used to display a StageExec, tasks, and plans.
400+ #[ derive( Debug , Clone ) ]
401+ pub struct DisplayCtx {
402+ metrics : Arc < HashMap < StageKey , Vec < MetricsSetProto > > > ,
403+ }
404+
405+ impl DisplayCtx {
406+ pub fn new ( metrics : HashMap < StageKey , Vec < MetricsSetProto > > ) -> Self {
407+ Self {
408+ metrics : Arc :: new ( metrics) ,
409+ }
410+ }
411+ }
412+
370413impl StageExec {
371414 fn format ( & self , plan : & dyn ExecutionPlan , indent : usize , f : & mut String ) -> std:: fmt:: Result {
372- let mut node_str = displayable ( plan) . one_line ( ) . to_string ( ) ;
415+ // println!("plan {:?}", plan);
416+ let mut node_str = match & self . display_ctx {
417+ None => displayable ( plan) . one_line ( ) . to_string ( ) ,
418+ Some ( _) => {
419+ DisplayableExecutionPlan :: with_metrics ( plan) . one_line ( ) . to_string ( )
420+ }
421+ } ;
373422 node_str. pop ( ) ;
374423 write ! ( f, "{} {node_str}" , " " . repeat( indent) ) ?;
375424
@@ -430,32 +479,90 @@ impl DisplayAs for StageExec {
430479 write ! ( f, "{}" , self . name)
431480 }
432481 DisplayFormatType :: Verbose => {
433- writeln ! (
434- f,
435- "{}{} {} {}" ,
436- LTCORNER ,
437- HORIZONTAL . repeat( 5 ) ,
438- self . name,
439- format_tasks_for_stage( self . tasks. len( ) , & self . plan)
440- ) ?;
482+ match & self . display_ctx {
483+ None => {
484+ writeln ! (
485+ f,
486+ "{}{} {} {}" ,
487+ LTCORNER ,
488+ HORIZONTAL . repeat( 5 ) ,
489+ self . name,
490+ format_tasks_for_stage( self . tasks. len( ) , & self . plan)
491+ ) ?;
441492
442- let mut plan_str = String :: new ( ) ;
443- self . format ( self . plan . as_ref ( ) , 0 , & mut plan_str) ?;
444- let plan_str = plan_str
445- . split ( '\n' )
446- . filter ( |v| !v. is_empty ( ) )
447- . collect :: < Vec < _ > > ( )
448- . join ( & format ! ( "\n {}{}" , " " . repeat( self . depth) , VERTICAL ) ) ;
449- writeln ! ( f, "{}{}{}" , " " . repeat( self . depth) , VERTICAL , plan_str) ?;
450- write ! (
451- f,
452- "{}{}{}" ,
453- " " . repeat( self . depth) ,
454- LDCORNER ,
455- HORIZONTAL . repeat( 50 )
456- ) ?;
493+ let mut plan_str = String :: new ( ) ;
494+ self . format ( self . plan . as_ref ( ) , 0 , & mut plan_str) ?;
495+ let plan_str = plan_str
496+ . split ( '\n' )
497+ . filter ( |v| !v. is_empty ( ) )
498+ . collect :: < Vec < _ > > ( )
499+ . join ( & format ! ( "\n {}{}" , " " . repeat( self . depth) , VERTICAL ) ) ;
500+ writeln ! ( f, "{}{}{}" , " " . repeat( self . depth) , VERTICAL , plan_str) ?;
501+ // Add bottom border
502+ write ! (
503+ f,
504+ "{}{}{}" ,
505+ " " . repeat( self . depth) ,
506+ LDCORNER ,
507+ HORIZONTAL . repeat( 50 )
508+ ) ?;
457509
458- Ok ( ( ) )
510+ Ok ( ( ) )
511+ }
512+ Some ( display_ctx) => {
513+ for ( i, _) in self . tasks . iter ( ) . enumerate ( ) {
514+ if i > 0 {
515+ writeln ! ( f) ?;
516+ }
517+ writeln ! (
518+ f,
519+ "{}{}{}{} {}" ,
520+ " " . repeat( self . depth) ,
521+ LTCORNER ,
522+ HORIZONTAL . repeat( 5 ) ,
523+ format!( " {} " , self . name) ,
524+ format_task_for_stage( i, & self . plan) ,
525+ ) ?;
526+ // Uniquely identify the task.
527+ let key = StageKey {
528+ query_id : self . query_id . to_string ( ) ,
529+ stage_id : self . num as u64 ,
530+ task_number : i as u64 ,
531+ } ;
532+
533+ let mut plan_str = String :: new ( ) ;
534+ let plan = match display_ctx. metrics . get ( & key) {
535+ Some ( metrics) => {
536+ let result = TaskMetricsRewriter :: new ( metrics. to_owned ( ) ) . enrich_task_with_metrics ( self . plan . clone ( ) ) ;
537+ if let Err ( e) = result {
538+ write ! ( f, "Error enriching task with metrics: {}" , e) ?;
539+ return Err ( std:: fmt:: Error ) ;
540+ }
541+ result. unwrap ( )
542+ }
543+ None => {
544+ self . plan . clone ( )
545+ }
546+ } ;
547+ self . format ( plan. as_ref ( ) , 0 , & mut plan_str) ?;
548+ let plan_str = plan_str
549+ . split ( '\n' )
550+ . filter ( |v| !v. is_empty ( ) )
551+ . collect :: < Vec < _ > > ( )
552+ . join ( & format ! ( "\n {}{}" , " " . repeat( self . depth) , VERTICAL ) ) ;
553+ writeln ! ( f, "{}{}{}" , " " . repeat( self . depth) , VERTICAL , plan_str) ?;
554+ // Add bottom border
555+ write ! (
556+ f,
557+ "{}{}{}" ,
558+ " " . repeat( self . depth) ,
559+ LDCORNER ,
560+ HORIZONTAL . repeat( 50 )
561+ ) ?;
562+ }
563+ return Ok ( ( ) ) ;
564+ }
565+ }
459566 }
460567 DisplayFormatType :: TreeRender => write ! (
461568 f,
@@ -483,6 +590,22 @@ fn format_tasks_for_stage(n_tasks: usize, head: &Arc<dyn ExecutionPlan>) -> Stri
483590 result
484591}
485592
593+ fn format_task_for_stage ( task_number : usize , head : & Arc < dyn ExecutionPlan > ) -> String {
594+ let partitioning = head. properties ( ) . output_partitioning ( ) ;
595+ let input_partitions = partitioning. partition_count ( ) ;
596+ let hash_shuffle = matches ! ( partitioning, Partitioning :: Hash ( _, _) ) ;
597+ let off = task_number * if hash_shuffle { 0 } else { input_partitions } ;
598+
599+ let mut result = "Task " . to_string ( ) ;
600+ result += & format ! ( "t{task_number}:[" ) ;
601+ result += & ( off..( off + input_partitions) )
602+ . map ( |v| format ! ( "p{v}" ) )
603+ . join ( "," ) ;
604+ result += "] " ;
605+
606+ result
607+ }
608+
486609fn format_tasks_for_partition_isolator (
487610 isolator : & PartitionIsolatorExec ,
488611 tasks : & [ ExecutionTask ] ,
0 commit comments