Skip to content

Commit

Permalink
better visualize physical plan
Browse files Browse the repository at this point in the history
  • Loading branch information
XiangpengHao committed Nov 12, 2024
1 parent 71448a8 commit 2989e1f
Showing 1 changed file with 112 additions and 131 deletions.
243 changes: 112 additions & 131 deletions src/query_results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use arrow::record_batch::RecordBatch;
use datafusion::{
common::cast::{as_binary_array, as_binary_view_array, as_string_view_array},
logical_expr::LogicalPlan,
physical_plan::{accept, ExecutionPlan, ExecutionPlanVisitor},
physical_plan::{
accept, display::DisplayableExecutionPlan, DisplayFormatType, ExecutionPlan,
ExecutionPlanVisitor,
},
};
use leptos::*;

Expand Down Expand Up @@ -41,16 +44,6 @@ pub fn QueryResults(
>
"Logical Plan"
</button>
<button
class=move || format!(
"px-4 py-2 {} {}",
if active_tab() == "logical_graphviz" { "border-b-2 border-blue-500 text-blue-600" } else { "text-gray-600" },
"hover:text-blue-600"
)
on:click=move |_| set_active_tab("logical_graphviz".to_string())
>
"Logical GraphViz"
</button>
<button
class=move || format!(
"px-4 py-2 {} {}",
Expand Down Expand Up @@ -116,20 +109,6 @@ pub fn QueryResults(
{logical_plan.display_indent().to_string()}
</div>
}.into_view(),
"logical_graphviz" => view! {
<div class="whitespace-pre-wrap font-mono">
<p class="mb-4">Copy the following GraphViz DOT code and visualize it at:
<a
href="https://dreampuf.github.io/GraphvizOnline"
target="_blank"
class="text-blue-600 hover:underline"
>
GraphvizOnline
</a>
</p>
{logical_plan.display_graphviz().to_string()}
</div>
}.into_view(),
"physical_plan" => view! {
<PhysicalPlan physical_plan=physical_plan.clone() />
}.into_view(),
Expand Down Expand Up @@ -176,129 +155,131 @@ impl ArrayExt for dyn Array {
}
}

#[component]
pub fn PhysicalPlan(physical_plan: Arc<dyn ExecutionPlan>) -> impl IntoView {
#[derive(Debug, Clone)]
struct PlanNode {
id: usize,
label: String,
metrics: Option<String>,
children: Vec<PlanNode>,
}
#[derive(Debug, Clone)]
struct PlanNode {
_id: usize,
name: String,
label: String,
metrics: Option<String>,
children: Vec<PlanNode>,
}

struct TreeBuilder {
next_id: usize,
current_path: Vec<PlanNode>,
struct TreeBuilder {
next_id: usize,
current_path: Vec<PlanNode>,
}

struct DisplayPlan<'a> {
plan: &'a dyn ExecutionPlan,
}

impl<'a> std::fmt::Display for DisplayPlan<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.plan.fmt_as(DisplayFormatType::Default, f)
}
}

impl ExecutionPlanVisitor for TreeBuilder {
type Error = std::fmt::Error;
impl ExecutionPlanVisitor for TreeBuilder {
type Error = std::fmt::Error;

fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
let label = plan.name().to_string();
fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
let name = plan.name().to_string();
let label = format!("{}", DisplayPlan { plan });

let metrics = plan.metrics().map(|m| {
let metrics = m
.aggregate_by_name()
.sorted_for_display()
.timestamps_removed();
format!("{metrics}")
});
let metrics = plan.metrics().map(|m| {
let metrics = m
.aggregate_by_name()
.sorted_for_display()
.timestamps_removed();
format!("{metrics}")
});

let node = PlanNode {
id: self.next_id,
label,
metrics,
children: vec![],
};
let node = PlanNode {
_id: self.next_id,
name,
label,
metrics,
children: vec![],
};

self.next_id += 1;
self.current_path.push(node);
Ok(true)
}
self.next_id += 1;
self.current_path.push(node);
Ok(true)
}

fn post_visit(&mut self, _: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
if self.current_path.len() >= 2 {
let child = self.current_path.pop().unwrap();
self.current_path.last_mut().unwrap().children.push(child);
}
Ok(true)
fn post_visit(&mut self, _: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
if self.current_path.len() >= 2 {
let child = self.current_path.pop().unwrap();
self.current_path.last_mut().unwrap().children.push(child);
}
Ok(true)
}
}

let mut builder = TreeBuilder {
next_id: 0,
current_path: vec![],
};
accept(physical_plan.as_ref(), &mut builder).unwrap();
let root = builder.current_path.pop().unwrap();

#[component]
fn PlanNode(node: PlanNode) -> impl IntoView {
view! {
<div class="relative">
<div class="flex flex-col items-center">
<div class="p-4 border rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow">
<div class="font-medium">{node.label}</div>
{node.metrics.map(|m| view! {
<div class="text-sm text-gray-600 mt-1">{m}</div>
})}
</div>
#[component]
fn PlanNode(node: PlanNode) -> impl IntoView {
view! {
<div class="relative">
<div class="flex flex-col items-center">
<div class="p-4 border rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow">
<div class="font-medium">{node.name}</div>
<div class="text-sm text-gray-700 mt-1 font-mono">{node.label}</div>
{node.metrics.map(|m| view! {
<div class="text-sm text-blue-600 mt-1 italic">{m}</div>
})}
</div>

{(!node.children.is_empty()).then(|| view! {
<div class="relative pt-4">
<svg class="absolute top-0 left-1/2 -translate-x-[0.5px] h-4 w-1" overflow="visible">
<defs>
<marker
id="arrowhead"
markerWidth="10"
markerHeight="7"
refX="0"
refY="3.5"
orient="auto">
<polygon
points="10 0, 0 3.5, 10 7"
fill="#D1D5DB"
/>
</marker>
</defs>
<line
x1="0.5"
y1="0"
x2="0.5"
y2="16"
stroke="#D1D5DB"
stroke-width="1"
marker-end="url(#arrowhead)"
/>
</svg>
{(!node.children.is_empty()).then(|| view! {
<div class="relative pt-4">
<svg class="absolute top-0 left-1/2 -translate-x-[0.5px] h-4 w-1 z-10" overflow="visible">
<line
x1="0.5"
y1="16"
x2="0.5"
y2="0"
stroke="#D1D5DB"
stroke-width="1"
marker-end="url(#global-arrowhead)"
/>
</svg>

<div class="relative flex items-center justify-center">
{(node.children.len() > 1).then(|| view! {
<svg class="absolute top-0 h-[1px]" style="left: 25%; width: 50%;" overflow="visible">
<line
x1="0"
y1="0.5"
x2="100%"
y2="0.5"
stroke="#D1D5DB"
stroke-width="1"
/>
</svg>
})}
</div>
<div class="relative flex items-center justify-center">
{(node.children.len() > 1).then(|| view! {
<svg class="absolute top-0 h-[1px]" style="left: 25%; width: 50%;" overflow="visible">
<line
x1="0"
y1="0.5"
x2="100%"
y2="0.5"
stroke="#D1D5DB"
stroke-width="1"
/>
</svg>
})}
</div>

<div class="flex gap-8">
{node.children.into_iter().map(|child| view! {
<PlanNode node={child}/>
}).collect::<Vec<_>>()}
</div>
<div class="flex gap-8">
{node.children.into_iter().map(|child| view! {
<PlanNode node={child}/>
}).collect::<Vec<_>>()}
</div>
})}
</div>
</div>
})}
</div>
}
</div>
}
}

#[component]
pub fn PhysicalPlan(physical_plan: Arc<dyn ExecutionPlan>) -> impl IntoView {
let mut builder = TreeBuilder {
next_id: 0,
current_path: vec![],
};
let displayable_plan = DisplayableExecutionPlan::with_metrics(physical_plan.as_ref());
accept(physical_plan.as_ref(), &mut builder).unwrap();
let root = builder.current_path.pop().unwrap();
web_sys::console::log_1(&displayable_plan.indent(true).to_string().into());

view! {
<div class="relative">
Expand Down

0 comments on commit 2989e1f

Please sign in to comment.