Skip to content

Commit ca6957e

Browse files
authored
feat: Highlight nodes in streaming phys plan graph (#22535)
1 parent 14ca918 commit ca6957e

File tree

1 file changed

+69
-7
lines changed
  • crates/polars-stream/src/physical_plan

1 file changed

+69
-7
lines changed

crates/polars-stream/src/physical_plan/fmt.rs

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,62 @@ use slotmap::{Key, SecondaryMap, SlotMap};
1212

1313
use super::{PhysNode, PhysNodeKey, PhysNodeKind};
1414

15+
/// A style of a graph node.
16+
enum NodeStyle {
17+
InMemoryFallback,
18+
MemoryIntensive,
19+
Generic,
20+
}
21+
22+
impl NodeStyle {
23+
const COLOR_IN_MEM_FALLBACK: &str = "0.0 0.3 1.0"; // Pastel red
24+
const COLOR_MEM_INTENSIVE: &str = "0.16 0.3 1.0"; // Pastel yellow
25+
26+
/// Returns a style for a node kind.
27+
pub fn for_node_kind(kind: &PhysNodeKind) -> Self {
28+
use PhysNodeKind as K;
29+
match kind {
30+
K::InMemoryMap { .. } => Self::InMemoryFallback,
31+
K::InMemorySource { .. }
32+
| K::InputIndependentSelect { .. }
33+
| K::NegativeSlice { .. }
34+
| K::InMemorySink { .. }
35+
| K::Sort { .. }
36+
| K::GroupBy { .. }
37+
| K::EquiJoin { .. }
38+
| K::SemiAntiJoin { .. }
39+
| K::InMemoryJoin { .. }
40+
| K::MergeSorted { .. }
41+
| K::Multiplexer { .. } => Self::MemoryIntensive,
42+
_ => Self::Generic,
43+
}
44+
}
45+
46+
/// Returns extra styling attributes (if any) for the graph node.
47+
pub fn node_attrs(&self) -> Option<String> {
48+
match self {
49+
Self::InMemoryFallback => Some(format!(
50+
"style=filled,fillcolor=\"{}\"",
51+
Self::COLOR_IN_MEM_FALLBACK
52+
)),
53+
Self::MemoryIntensive => Some(format!(
54+
"style=filled,fillcolor=\"{}\"",
55+
Self::COLOR_MEM_INTENSIVE
56+
)),
57+
Self::Generic => None,
58+
}
59+
}
60+
61+
/// Returns a legend explaining the node style meaning.
62+
pub fn legend() -> String {
63+
format!(
64+
"fontsize=\"10\"\nlabelloc=\"b\"\nlabel=<<BR/><BR/><B>Legend</B><BR/><BR/>◯ streaming engine node<FONT COLOR=\"{}\">⬤</FONT>potentially memory-intensive node<FONT COLOR=\"{}\">⬤</FONT>in-memory engine fallback>",
65+
Self::COLOR_MEM_INTENSIVE,
66+
Self::COLOR_IN_MEM_FALLBACK,
67+
)
68+
}
69+
}
70+
1571
fn escape_graphviz(s: &str) -> String {
1672
s.replace('\\', "\\\\")
1773
.replace('\n', "\\n")
@@ -39,8 +95,10 @@ fn visualize_plan_rec(
3995
}
4096
visited.insert(node_key, ());
4197

98+
let kind = &phys_sm[node_key].kind;
99+
42100
use std::slice::from_ref;
43-
let (label, inputs) = match &phys_sm[node_key].kind {
101+
let (label, inputs) = match kind {
44102
PhysNodeKind::InMemorySource { df } => (
45103
format!(
46104
"in-memory-source\\ncols: {}",
@@ -329,11 +387,14 @@ fn visualize_plan_rec(
329387
},
330388
};
331389

332-
out.push(format!(
333-
"{} [label=\"{}\"];",
334-
node_key.data().as_ffi(),
335-
label
336-
));
390+
let node_id = node_key.data().as_ffi();
391+
let style = NodeStyle::for_node_kind(kind);
392+
393+
if let Some(attrs) = style.node_attrs() {
394+
out.push(format!("{node_id} [label=\"{label}\",{attrs}];"));
395+
} else {
396+
out.push(format!("{node_id} [label=\"{label}\"];"));
397+
}
337398
for input in inputs {
338399
visualize_plan_rec(input.node, phys_sm, expr_arena, visited, out);
339400
out.push(format!(
@@ -350,8 +411,9 @@ pub fn visualize_plan(
350411
expr_arena: &Arena<AExpr>,
351412
) -> String {
352413
let mut visited: SecondaryMap<PhysNodeKey, ()> = SecondaryMap::new();
353-
let mut out = Vec::with_capacity(phys_sm.len() + 2);
414+
let mut out = Vec::with_capacity(phys_sm.len() + 3);
354415
out.push("digraph polars {\nrankdir=\"BT\"".to_string());
416+
out.push(NodeStyle::legend());
355417
visualize_plan_rec(root, phys_sm, expr_arena, &mut visited, &mut out);
356418
out.push("}".to_string());
357419
out.join("\n")

0 commit comments

Comments
 (0)