diff --git a/core/src/main/java/org/apache/calcite/plan/visualizer/RuleMatchVisualizer.java b/core/src/main/java/org/apache/calcite/plan/visualizer/RuleMatchVisualizer.java index 5cbc6aa10ee..b42b895cc25 100644 --- a/core/src/main/java/org/apache/calcite/plan/visualizer/RuleMatchVisualizer.java +++ b/core/src/main/java/org/apache/calcite/plan/visualizer/RuleMatchVisualizer.java @@ -95,7 +95,7 @@ public class RuleMatchVisualizer implements RelOptListener { private @Nullable RelOptPlanner planner = null; private boolean includeTransitiveEdges = false; - private boolean includeIntermediateCosts = false; + private boolean includeIntermediateCosts = true; private final List steps = new ArrayList<>(); private final Map allNodes = new LinkedHashMap<>(); @@ -133,6 +133,24 @@ public void attachTo(RelOptPlanner planner) { this.planner = planner; } + /** + * Register the root node of the planner and adds an INITIAL step. + * This method is automatically invoked when the first rule is attempted. + */ + public void ensureInitialized() { + if (!initialized) { + requireNonNull(planner, "planner"); + RelNode root = requireNonNull(planner.getRoot(), "root"); + initialized = true; + updateInitialPlan(root); + } + // add the initialState + if (!this.allNodes.isEmpty() && this.steps.isEmpty()) { + this.addStep(INITIAL, null); + this.latestRuleID = INITIAL; + } + } + /** * Output edges from a subset to the nodes of all subsets that satisfy it. */ @@ -142,6 +160,7 @@ public void setIncludeTransitiveEdges(final boolean includeTransitiveEdges) { /** * Output intermediate costs, including all cost updates. + * The default value is true. */ public void setIncludeIntermediateCosts(final boolean includeIntermediateCosts) { this.includeIntermediateCosts = includeIntermediateCosts; @@ -149,12 +168,7 @@ public void setIncludeIntermediateCosts(final boolean includeIntermediateCosts) @Override public void ruleAttempted(RuleAttemptedEvent event) { // HepPlanner compatibility - if (!initialized) { - requireNonNull(planner, "planner"); - RelNode root = requireNonNull(planner.getRoot()); - initialized = true; - updateInitialPlan(root); - } + ensureInitialized(); } /** @@ -217,11 +231,7 @@ private void updateFinalPlan(RelNode node) { @Override public void ruleProductionSucceeded(RuleProductionEvent event) { // method is called once before ruleMatch, and once after ruleMatch if (event.isBefore()) { - // add the initialState - if (latestRuleID.isEmpty()) { - this.addStep(INITIAL, null); - this.latestRuleID = INITIAL; - } + ensureInitialized(); return; } @@ -300,6 +310,11 @@ private void updateNodeInfo(final RelNode rel, final boolean isLastStep) { RelOptCost cost = planner.getCost(rel, mq); Double rowCount = mq.getRowCount(rel); helper.updateAttribute("cost", formatCost(rowCount, cost)); + if (rel instanceof RelSubset) { + final RelNode best = ((RelSubset) rel).getBest(); + final String bestId = best != null ? Integer.toString(best.getId()) : ""; + helper.updateAttribute("best", bestId); + } } List inputs = new ArrayList<>(); @@ -464,7 +479,7 @@ private static String formatCost(Double rowCount, @Nullable RelOptCost cost) { || originalStr.contains("tiny")) { return originalStr; } - return new MessageFormat("\nrowCount: {0}\nrows: {1}\ncpu: {2}\nio: {3}", + return new MessageFormat("rowCount: {0}\nrows: {1}\ncpu: {2}\nio: {3}", Locale.ROOT).format(new String[]{ formatCostScientific(rowCount), formatCostScientific(cost.getRows()), diff --git a/core/src/main/resources/org/apache/calcite/plan/visualizer/viz-template.html b/core/src/main/resources/org/apache/calcite/plan/visualizer/viz-template.html index 84e3989326a..1bdaa9c40a1 100644 --- a/core/src/main/resources/org/apache/calcite/plan/visualizer/viz-template.html +++ b/core/src/main/resources/org/apache/calcite/plan/visualizer/viz-template.html @@ -21,8 +21,6 @@ Calcite Rule Match Visualization - - @@ -37,8 +35,28 @@ } li a { + padding: 0.1em 20px 0.1em 2px; + text-decoration: none; + } + + ul#node-list { + list-style-type: none; + padding: 0px 20px; + margin: 0px; + } + + #step-list { + margin: 0px; + } + + #step-list li a { display: block; - padding: 5px 20px; + } + + .side-section { + font-size: 1.2em; + font-weight: bold; + padding: 4px; } section { @@ -58,7 +76,7 @@ border: 1px solid #ccc; } - #step-list-column { + #side-column { border-left: 1px solid #ccc; } @@ -107,37 +125,78 @@ word-wrap: break-word; } - button { + .node-link,.step-link { + text-decoration: underline; + cursor: pointer; + } + + #toolbar button { padding: 0.1em; display: inline-block; font-size: 2em; min-width: 1.5em; } + + details { + overflow-y: auto; + min-height: 2em; + flex: 0 1 auto; + } + + details summary { + /* always show the summary */ + position: sticky; + top: 0; + background: white; + z-index: 1; + }
- - - + + +   - - - -
+ + + +
-
-
-
-
    -
+
+
Options: +
+
+
+ + +
+
Nodes: + +
+ + + + +
+ + + +
+
    +
+
Steps: +
    +
-