You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ja...@apache.org on 2022/04/18 09:30:35 UTC

[iotdb] branch master updated: [IoTDB-2661] Add PlanNode visualizer tool (#5489)

This is an automated email from the ASF dual-hosted git repository.

jackietien pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new a263e4c1a3 [IoTDB-2661] Add PlanNode visualizer tool (#5489)
a263e4c1a3 is described below

commit a263e4c1a35578e91bc69ffe863d15502202142a
Author: Zhang.Jinrui <xi...@gmail.com>
AuthorDate: Mon Apr 18 17:30:30 2022 +0800

    [IoTDB-2661] Add PlanNode visualizer tool (#5489)
---
 .../org/apache/iotdb/db/mpp/common/QueryId.java    |   2 +-
 .../sql/planner/plan/node/PlanGraphPrinter.java    | 309 +++++++++++++++++++++
 .../db/mpp/sql/plan/DistributionPlannerTest.java   |   1 -
 3 files changed, 310 insertions(+), 2 deletions(-)

diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/common/QueryId.java b/server/src/main/java/org/apache/iotdb/db/mpp/common/QueryId.java
index 4d24e5cfc0..6fe6ed5a96 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/common/QueryId.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/common/QueryId.java
@@ -48,7 +48,7 @@ public class QueryId {
   }
 
   public PlanNodeId genPlanNodeId() {
-    return new PlanNodeId(String.format("%s_%d", id, nextPlanNodeIndex++));
+    return new PlanNodeId(String.format("%d", nextPlanNodeIndex++));
   }
 
   public PlanFragmentId genPlanFragmentId() {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/sql/planner/plan/node/PlanGraphPrinter.java b/server/src/main/java/org/apache/iotdb/db/mpp/sql/planner/plan/node/PlanGraphPrinter.java
new file mode 100644
index 0000000000..d74a6c49f1
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/sql/planner/plan/node/PlanGraphPrinter.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.mpp.sql.planner.plan.node;
+
+import org.apache.iotdb.db.mpp.sql.planner.plan.node.process.ExchangeNode;
+import org.apache.iotdb.db.mpp.sql.planner.plan.node.process.LimitNode;
+import org.apache.iotdb.db.mpp.sql.planner.plan.node.process.TimeJoinNode;
+import org.apache.iotdb.db.mpp.sql.planner.plan.node.sink.FragmentSinkNode;
+import org.apache.iotdb.db.mpp.sql.planner.plan.node.source.SeriesScanNode;
+
+import org.apache.commons.lang3.Validate;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PlanGraphPrinter extends PlanVisitor<List<String>, PlanGraphPrinter.GraphContext> {
+
+  private static final String INDENT = " ";
+  private static final String HENG = "─";
+  private static final String SHU = "│";
+  private static final String LEFT_BOTTOM = "└";
+  private static final String RIGHT_BOTTOM = "┘";
+  private static final String LEFT_TOP = "┌";
+  private static final String RIGHT_TOP = "┐";
+  private static final String SHANG = "┴";
+  private static final String XIA = "┬";
+  private static final String CROSS = "┼";
+
+  private static final int BOX_MARGIN = 1;
+  private static final int CONNECTION_LINE_HEIGHT = 2;
+
+  @Override
+  public List<String> visitPlan(PlanNode node, GraphContext context) {
+    List<String> boxValue = new ArrayList<>();
+    boxValue.add(String.format("PlanNode-%s", node.getPlanNodeId().getId()));
+    return render(node, boxValue, context);
+  }
+
+  @Override
+  public List<String> visitSeriesScan(SeriesScanNode node, GraphContext context) {
+    List<String> boxValue = new ArrayList<>();
+    boxValue.add(String.format("SeriesScanNode-%s", node.getPlanNodeId().getId()));
+    boxValue.add(String.format("Series: %s", node.getSeriesPath()));
+    boxValue.add(String.format("Partition: %s", node.getRegionReplicaSet().getConsensusGroupId()));
+    return render(node, boxValue, context);
+  }
+
+  @Override
+  public List<String> visitExchange(ExchangeNode node, GraphContext context) {
+    List<String> boxValue = new ArrayList<>();
+    boxValue.add(String.format("ExchangeNode-%s", node.getPlanNodeId().getId()));
+    return render(node, boxValue, context);
+  }
+
+  @Override
+  public List<String> visitTimeJoin(TimeJoinNode node, GraphContext context) {
+    List<String> boxValue = new ArrayList<>();
+    boxValue.add(String.format("TimeJoinNode-%s", node.getPlanNodeId().getId()));
+    boxValue.add(String.format("Order: %s", node.getMergeOrder()));
+    return render(node, boxValue, context);
+  }
+
+  @Override
+  public List<String> visitLimit(LimitNode node, GraphContext context) {
+    List<String> boxValue = new ArrayList<>();
+    boxValue.add(String.format("Limit-%s", node.getPlanNodeId().getId()));
+    boxValue.add(String.format("Count: %d", node.getLimit()));
+    return render(node, boxValue, context);
+  }
+
+  @Override
+  public List<String> visitFragmentSink(FragmentSinkNode node, GraphContext context) {
+    List<String> boxValue = new ArrayList<>();
+    boxValue.add(String.format("FragmentSinkNode-%s", node.getPlanNodeId().getId()));
+    boxValue.add(String.format("Destination: %s", node.getDownStreamPlanNodeId()));
+    return render(node, boxValue, context);
+  }
+
+  private List<String> render(PlanNode node, List<String> nodeBoxString, GraphContext context) {
+    Box box = new Box(nodeBoxString);
+    List<List<String>> children = new ArrayList<>();
+    for (PlanNode child : node.getChildren()) {
+      children.add(child.accept(this, context));
+    }
+    box.calculateBoxParams(children);
+
+    box.lines.add(printBoxEdge(box, true));
+    for (String valueLine : nodeBoxString) {
+      StringBuilder line = new StringBuilder();
+      for (int i = 0; i < box.lineWidth; i++) {
+        if (i < box.startPosition) {
+          line.append(INDENT);
+          continue;
+        }
+        if (i > box.endPosition) {
+          line.append(INDENT);
+          continue;
+        }
+        if (i == box.startPosition || i == box.endPosition) {
+          line.append(SHU);
+          continue;
+        }
+        if (i - box.startPosition - 1 < valueLine.length()) {
+          line.append(valueLine.charAt(i - box.startPosition - 1));
+        } else {
+          line.append(INDENT);
+        }
+      }
+      box.lines.add(line.toString());
+    }
+    box.lines.add(printBoxEdge(box, false));
+
+    if (children.size() == 0) {
+      return box.lines;
+    }
+
+    // Print Connection Line
+    if (children.size() == 1) {
+      for (int i = 0; i < CONNECTION_LINE_HEIGHT; i++) {
+        StringBuilder line = new StringBuilder();
+        for (int j = 0; j < box.lineWidth; j++) {
+          line.append(j == box.midPosition ? SHU : INDENT);
+        }
+        box.lines.add(line.toString());
+      }
+    } else {
+      Map<Integer, String> symbolMap = new HashMap<>();
+      Map<Integer, Boolean> childMidPositionMap = new HashMap<>();
+      symbolMap.put(box.midPosition, SHANG);
+      for (int i = 0; i < children.size(); i++) {
+        int childMidPosition = getChildMidPosition(children, i);
+        childMidPositionMap.put(childMidPosition, true);
+        if (childMidPosition == box.midPosition) {
+          symbolMap.put(box.midPosition, CROSS);
+          continue;
+        }
+        symbolMap.put(
+            childMidPosition, i == 0 ? LEFT_TOP : i == children.size() - 1 ? RIGHT_TOP : XIA);
+      }
+      StringBuilder line1 = new StringBuilder();
+      for (int i = 0; i < box.lineWidth; i++) {
+        if (i < getChildMidPosition(children, 0)
+            || i > getChildMidPosition(children, children.size() - 1)) {
+          line1.append(INDENT);
+          continue;
+        }
+        line1.append(symbolMap.getOrDefault(i, HENG));
+      }
+      box.lines.add(line1.toString());
+
+      for (int row = 1; row < CONNECTION_LINE_HEIGHT; row++) {
+        StringBuilder nextLine = new StringBuilder();
+        for (int i = 0; i < box.lineWidth; i++) {
+          nextLine.append(childMidPositionMap.containsKey(i) ? SHU : INDENT);
+        }
+        box.lines.add(nextLine.toString());
+      }
+    }
+
+    for (int i = 0; i < getChildrenLineCount(children); i++) {
+      StringBuilder line = new StringBuilder();
+      for (int j = 0; j < children.size(); j++) {
+        line.append(getLine(children, j, i));
+        if (j != children.size() - 1) {
+          for (int m = 0; m < BOX_MARGIN; m++) {
+            line.append(INDENT);
+          }
+        }
+      }
+      box.lines.add(line.toString());
+    }
+    return box.lines;
+  }
+
+  private String printBoxEdge(Box box, boolean isTopEdge) {
+    StringBuilder line = new StringBuilder();
+    for (int i = 0; i < box.lineWidth; i++) {
+      if (i < box.startPosition) {
+        line.append(INDENT);
+      } else if (i > box.endPosition) {
+        line.append(INDENT);
+      } else if (i == box.startPosition) {
+        line.append(isTopEdge ? LEFT_TOP : LEFT_BOTTOM);
+      } else if (i == box.endPosition) {
+        line.append(isTopEdge ? RIGHT_TOP : RIGHT_BOTTOM);
+      } else {
+        line.append(HENG);
+      }
+    }
+    return line.toString();
+  }
+
+  private String getLine(List<List<String>> children, int child, int line) {
+    if (line < children.get(child).size()) {
+      return children.get(child).get(line);
+    }
+    return genEmptyLine(children.get(child).get(0).length());
+  }
+
+  private String genEmptyLine(int lineWidth) {
+    StringBuilder line = new StringBuilder();
+    for (int i = 0; i < lineWidth; i++) {
+      line.append(INDENT);
+    }
+    return line.toString();
+  }
+
+  private int getChildrenLineCount(List<List<String>> children) {
+    int count = 0;
+    for (List<String> child : children) {
+      count = Math.max(count, child.size());
+    }
+    return count;
+  }
+
+  private static int getChildMidPosition(List<List<String>> children, int idx) {
+    int left = 0;
+    for (int i = 0; i < idx; i++) {
+      left += children.get(i).get(0).length();
+      left += BOX_MARGIN;
+    }
+    left += children.get(idx).get(0).length() / 2;
+    return left;
+  }
+
+  private static class Box {
+    private List<String> boxString;
+    private int boxWidth;
+    private int lineWidth;
+    private List<String> lines;
+    private int startPosition;
+    private int endPosition;
+    private int midPosition;
+
+    public Box(List<String> boxString) {
+      this.boxString = boxString;
+      this.boxWidth = getBoxWidth();
+      this.lines = new ArrayList<>();
+    }
+
+    public int getBoxWidth() {
+      int width = 0;
+      for (String line : boxString) {
+        width = Math.max(width, line.length());
+      }
+      return width + 2;
+    }
+
+    public String getLine(int idx) {
+      if (idx < lines.size()) {
+        return lines.get(idx);
+      }
+      return genEmptyLine(lineWidth);
+    }
+
+    private String genEmptyLine(int lineWidth) {
+      StringBuilder line = new StringBuilder();
+      for (int i = 0; i < lineWidth; i++) {
+        line.append(INDENT);
+      }
+      return line.toString();
+    }
+
+    public void calculateBoxParams(List<List<String>> childBoxStrings) {
+      int childrenWidth = 0;
+      for (List<String> childBoxString : childBoxStrings) {
+        Validate.isTrue(childBoxString.size() > 0, "Lines of box string should be greater than 0");
+        childrenWidth += childBoxString.get(0).length();
+      }
+      childrenWidth += childBoxStrings.size() > 1 ? (childBoxStrings.size() - 1) * BOX_MARGIN : 0;
+      this.lineWidth = Math.max(this.boxWidth, childrenWidth);
+      this.startPosition = (this.lineWidth - this.boxWidth) / 2;
+      this.endPosition = this.startPosition + this.boxWidth - 1;
+      this.midPosition = this.lineWidth / 2;
+    }
+  }
+
+  public static class GraphContext {}
+
+  public static List<String> getGraph(PlanNode node) {
+    return node.accept(new PlanGraphPrinter(), new PlanGraphPrinter.GraphContext());
+  }
+
+  public static void print(PlanNode node) {
+    List<String> lines = getGraph(node);
+    for (String line : lines) {
+      System.out.println(line);
+    }
+  }
+}
diff --git a/server/src/test/java/org/apache/iotdb/db/mpp/sql/plan/DistributionPlannerTest.java b/server/src/test/java/org/apache/iotdb/db/mpp/sql/plan/DistributionPlannerTest.java
index 21ca0ed2d1..d317b9d5cc 100644
--- a/server/src/test/java/org/apache/iotdb/db/mpp/sql/plan/DistributionPlannerTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/mpp/sql/plan/DistributionPlannerTest.java
@@ -259,7 +259,6 @@ public class DistributionPlannerTest {
     DistributionPlanner planner =
         new DistributionPlanner(analysis, new LogicalQueryPlan(context, root));
     DistributedQueryPlan plan = planner.planFragments();
-    plan.getInstances().forEach(System.out::println);
     assertEquals(3, plan.getInstances().size());
   }