You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by mo...@apache.org on 2021/03/21 03:19:02 UTC
[incubator-doris] branch master updated: [Profile] Visualize the
query plan and query profile (#5475)
This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-doris.git
The following commit(s) were added to refs/heads/master by this push:
new b9d92e0 [Profile] Visualize the query plan and query profile (#5475)
b9d92e0 is described below
commit b9d92e0fcba1accc02b6e15553c4782ac0320f9c
Author: Mingyu Chen <mo...@gmail.com>
AuthorDate: Sun Mar 21 11:18:50 2021 +0800
[Profile] Visualize the query plan and query profile (#5475)
Add command:
1. EXPLAIN GRAPGH SELECT ...
2. SHOW QUERY PROFILE "..."
Document will be added in next PR
Change-Id: Ifd9365e10b1f9ff4fdf8ae0556343783d97545f0
---
fe/fe-core/pom.xml | 5 +
fe/fe-core/src/main/cup/sql_parser.cup | 30 +-
.../org/apache/doris/analysis/ExplainOptions.java | 37 +++
.../java/org/apache/doris/analysis/QueryStmt.java | 6 +-
.../doris/analysis/ShowQueryProfileStmt.java | 164 ++++++++++
.../org/apache/doris/analysis/StatementBase.java | 40 ++-
.../apache/doris/common/profile/CounterNode.java | 53 ++++
.../apache/doris/common/profile/ExecNodeNode.java | 27 ++
.../doris/common/profile/PlanTreeBuilder.java | 122 +++++++
.../apache/doris/common/profile/PlanTreeNode.java | 39 +++
.../doris/common/profile/PlanTreePrinter.java | 40 +++
.../doris/common/profile/ProfileTreeBuilder.java | 349 +++++++++++++++++++++
.../doris/common/profile/ProfileTreeNode.java | 145 +++++++++
.../doris/common/profile/ProfileTreePrinter.java | 55 ++++
.../apache/doris/common/util/ProfileManager.java | 91 +++++-
.../apache/doris/common/util/RuntimeProfile.java | 28 +-
.../org/apache/doris/planner/AggregationNode.java | 14 +-
.../org/apache/doris/planner/AnalyticEvalNode.java | 14 +-
.../apache/doris/planner/AssertNumRowsNode.java | 5 +-
.../org/apache/doris/planner/BrokerScanNode.java | 14 +-
.../org/apache/doris/planner/CrossJoinNode.java | 9 +-
.../java/org/apache/doris/planner/EsScanNode.java | 13 +-
.../org/apache/doris/planner/ExchangeNode.java | 7 +-
.../java/org/apache/doris/planner/ExportSink.java | 3 +
.../org/apache/doris/planner/HashJoinNode.java | 19 +-
.../org/apache/doris/planner/MergeJoinNode.java | 2 +-
.../java/org/apache/doris/planner/MergeNode.java | 7 +-
.../org/apache/doris/planner/MysqlScanNode.java | 11 +-
.../org/apache/doris/planner/MysqlTableSink.java | 5 +-
.../org/apache/doris/planner/OdbcScanNode.java | 11 +-
.../org/apache/doris/planner/OlapScanNode.java | 12 +-
.../org/apache/doris/planner/OlapTableSink.java | 3 +
.../org/apache/doris/planner/PlanFragment.java | 2 +
.../java/org/apache/doris/planner/PlanNode.java | 21 +-
.../java/org/apache/doris/planner/Planner.java | 20 +-
.../java/org/apache/doris/planner/RepeatNode.java | 11 +-
.../java/org/apache/doris/planner/SelectNode.java | 5 +-
.../org/apache/doris/planner/SetOperationNode.java | 14 +-
.../java/org/apache/doris/planner/SortNode.java | 12 +-
.../apache/doris/planner/StreamLoadScanNode.java | 8 +-
.../java/org/apache/doris/qe/ShowExecutor.java | 63 +++-
.../java/org/apache/doris/qe/StmtExecutor.java | 11 +-
fe/fe-core/src/main/jflex/sql_scanner.flex | 2 +
.../doris/planner/DistributedPlannerTest.java | 14 +-
.../java/org/apache/doris/planner/PlannerTest.java | 27 +-
.../java/org/apache/doris/utframe/DorisAssert.java | 4 +-
.../org/apache/doris/utframe/UtFrameUtils.java | 8 +-
fe/pom.xml | 7 +
gensrc/thrift/Types.thrift | 1 +
49 files changed, 1471 insertions(+), 139 deletions(-)
diff --git a/fe/fe-core/pom.xml b/fe/fe-core/pom.xml
index 5112e6b..ac7e6f8 100644
--- a/fe/fe-core/pom.xml
+++ b/fe/fe-core/pom.xml
@@ -602,6 +602,11 @@ under the License.
<artifactId>lombok</artifactId>
</dependency>
+ <dependency>
+ <groupId>hu.webarticum</groupId>
+ <artifactId>tree-printer</artifactId>
+ </dependency>
+
</dependencies>
<build>
diff --git a/fe/fe-core/src/main/cup/sql_parser.cup b/fe/fe-core/src/main/cup/sql_parser.cup
index 376a1cf..f810dbf 100644
--- a/fe/fe-core/src/main/cup/sql_parser.cup
+++ b/fe/fe-core/src/main/cup/sql_parser.cup
@@ -238,7 +238,7 @@ terminal String KW_ADD, KW_ADMIN, KW_AFTER, KW_AGGREGATE, KW_ALL, KW_ALTER, KW_A
KW_ELSE, KW_ENABLE, KW_END, KW_ENGINE, KW_ENGINES, KW_ENTER, KW_ERRORS, KW_EVENTS, KW_EXCEPT, KW_EXCLUDE,
KW_EXISTS, KW_EXPORT, KW_EXTERNAL, KW_EXTRACT,
KW_FALSE, KW_FEATURE, KW_FOLLOWER, KW_FOLLOWING, KW_FREE, KW_FROM, KW_FILE, KW_FILTER, KW_FIRST, KW_FLOAT, KW_FOR, KW_FORCE, KW_FORMAT, KW_FRONTEND, KW_FRONTENDS, KW_FULL, KW_FUNCTION, KW_FUNCTIONS,
- KW_GLOBAL, KW_GRANT, KW_GRANTS, KW_GROUP, KW_GROUPING,
+ KW_GLOBAL, KW_GRANT, KW_GRANTS, KW_GRAPH, KW_GROUP, KW_GROUPING,
KW_HASH, KW_HAVING, KW_HDFS, KW_HELP,KW_HLL, KW_HLL_UNION, KW_HOUR, KW_HUB,
KW_IDENTIFIED, KW_IF, KW_IN, KW_INDEX, KW_INDEXES, KW_INFILE, KW_INSTALL,
KW_INNER, KW_INSERT, KW_INT, KW_INTERMEDIATE, KW_INTERSECT, KW_INTERVAL, KW_INTO, KW_IS, KW_ISNULL, KW_ISOLATION,
@@ -252,7 +252,7 @@ terminal String KW_ADD, KW_ADMIN, KW_AFTER, KW_AGGREGATE, KW_ALL, KW_ALTER, KW_A
KW_PARTITION, KW_PARTITIONS, KW_PASSWORD, KW_PATH, KW_PAUSE, KW_PIPE, KW_PRECEDING,
KW_PLUGIN, KW_PLUGINS,
KW_PRIMARY,
- KW_PROC, KW_PROCEDURE, KW_PROCESSLIST, KW_PROPERTIES, KW_PROPERTY,
+ KW_PROC, KW_PROCEDURE, KW_PROCESSLIST, KW_PROFILE, KW_PROPERTIES, KW_PROPERTY,
KW_QUERY, KW_QUOTA,
KW_RANDOM, KW_RANGE, KW_READ, KW_RECOVER, KW_REGEXP, KW_RELEASE, KW_RENAME,
KW_REPAIR, KW_REPEATABLE, KW_REPOSITORY, KW_REPOSITORIES, KW_REPLACE, KW_REPLACE_IF_NOT_NULL, KW_REPLICA, KW_RESOURCE, KW_RESOURCES, KW_RESTORE, KW_RETURNS, KW_RESUME, KW_REVOKE,
@@ -479,7 +479,7 @@ nonterminal IndexDef.IndexType opt_index_type;
nonterminal ShowAlterStmt.AlterType opt_alter_type;
nonterminal Boolean opt_builtin;
-nonterminal Boolean opt_verbose;
+nonterminal ExplainOptions opt_explain_options;
nonterminal Boolean opt_tmp;
@@ -2495,6 +2495,10 @@ show_param ::=
{:
RESULT = new ShowTransactionStmt(dbName, parser.where);
:}
+ | KW_QUERY KW_PROFILE STRING_LITERAL:queryIdPath
+ {:
+ RESULT = new ShowQueryProfileStmt(queryIdPath);
+ :}
;
opt_tmp ::=
@@ -2634,13 +2638,17 @@ opt_builtin ::=
:}
;
-opt_verbose ::=
+opt_explain_options ::=
{:
- RESULT = false;
+ RESULT = new ExplainOptions(false, false);
:}
| KW_VERBOSE
{:
- RESULT = true;
+ RESULT = new ExplainOptions(true, false);
+ :}
+ | KW_GRAPH
+ {:
+ RESULT = new ExplainOptions(false, true);
:}
;
@@ -2654,14 +2662,14 @@ describe_stmt ::=
{:
RESULT = new DescribeStmt(table, true);
:}
- | describe_command opt_verbose:isVerbose query_stmt:query
+ | describe_command opt_explain_options:options query_stmt:query
{:
- query.setIsExplain(true, isVerbose);
+ query.setIsExplain(options);
RESULT = query;
:}
| describe_command insert_stmt:stmt
{:
- stmt.getQueryStmt().setIsExplain(true);
+ stmt.getQueryStmt().setIsExplain(new ExplainOptions(true, false));
RESULT = stmt;
:}
;
@@ -4759,6 +4767,8 @@ keyword ::=
{: RESULT = id; :}
| KW_PATH:id
{: RESULT = id; :}
+ | KW_PROFILE:id
+ {: RESULT = id; :}
| KW_FUNCTION:id
{: RESULT = id; :}
| KW_END:id
@@ -4777,6 +4787,8 @@ keyword ::=
{: RESULT = id; :}
| KW_GLOBAL:id
{: RESULT = id; :}
+ | KW_GRAPH:id
+ {: RESULT = id; :}
| KW_HASH:id
{: RESULT = id; :}
| KW_HELP:id
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/ExplainOptions.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/ExplainOptions.java
new file mode 100644
index 0000000..543fd5b
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/ExplainOptions.java
@@ -0,0 +1,37 @@
+// 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.doris.analysis;
+
+public class ExplainOptions {
+
+ private boolean isVerbose;
+ private boolean isGraph;
+
+ public ExplainOptions(boolean isVerbose, boolean isGraph) {
+ this.isVerbose = isVerbose;
+ this.isGraph = isGraph;
+ }
+
+ public boolean isVerbose() {
+ return isVerbose;
+ }
+
+ public boolean isGraph() {
+ return isGraph;
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/QueryStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/QueryStmt.java
index a53b2e3..43875f5 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/QueryStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/QueryStmt.java
@@ -509,12 +509,12 @@ public abstract class QueryStmt extends StatementBase {
return assertNumRowsElement;
}
- public void setIsExplain(boolean isExplain) {
- this.isExplain = isExplain;
+ public void setIsExplain(ExplainOptions options) {
+ this.explainOptions = options;
}
public boolean isExplain() {
- return isExplain;
+ return this.explainOptions != null;
}
public boolean hasLimitClause() {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowQueryProfileStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowQueryProfileStmt.java
new file mode 100644
index 0000000..ae6a7ff
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowQueryProfileStmt.java
@@ -0,0 +1,164 @@
+// 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.doris.analysis;
+
+import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.ScalarType;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.UserException;
+import org.apache.doris.qe.ShowResultSetMetaData;
+
+import com.google.common.base.Strings;
+
+// For stmt like:
+// show query profile "/"; # list all saving query ids
+// show query profile "/e0f7390f5363419e-b416a2a79996083e" # show graph of fragments of the query
+// show query profile "/e0f7390f5363419e-b416a2a79996083e/0" # show instance list of the specified fragment
+// show query profile "/e0f7390f5363419e-b416a2a79996083e/0/e0f7390f5363419e-b416a2a799960906" # show graph of the instance
+public class ShowQueryProfileStmt extends ShowStmt {
+ // This should be same as ProfileManager.PROFILE_HEADERS
+ private static final ShowResultSetMetaData META_DATA_QUERYIDS =
+ ShowResultSetMetaData.builder()
+ .addColumn(new Column("QueryId", ScalarType.createVarchar(128)))
+ .addColumn(new Column("User", ScalarType.createVarchar(128)))
+ .addColumn(new Column("DefaultDb", ScalarType.createVarchar(128)))
+ .addColumn(new Column("SQL", ScalarType.createVarchar(65535)))
+ .addColumn(new Column("QueryType", ScalarType.createVarchar(128)))
+ .addColumn(new Column("StartTime", ScalarType.createVarchar(128)))
+ .addColumn(new Column("EndTime", ScalarType.createVarchar(128)))
+ .addColumn(new Column("TotalTime", ScalarType.createVarchar(128)))
+ .addColumn(new Column("QueryState", ScalarType.createVarchar(128)))
+ .build();
+
+ private static final ShowResultSetMetaData META_DATA_FRAGMENTS =
+ ShowResultSetMetaData.builder()
+ .addColumn(new Column("Fragments", ScalarType.createVarchar(65535)))
+ .build();
+ private static final ShowResultSetMetaData META_DATA_INSTANCES =
+ ShowResultSetMetaData.builder()
+ .addColumn(new Column("Instances", ScalarType.createVarchar(128)))
+ .addColumn(new Column("Host", ScalarType.createVarchar(64)))
+ .addColumn(new Column("ActiveTime", ScalarType.createVarchar(64)))
+ .build();
+ private static final ShowResultSetMetaData META_DATA_SINGLE_INSTANCE =
+ ShowResultSetMetaData.builder()
+ .addColumn(new Column("Instance", ScalarType.createVarchar(65535)))
+ .build();
+
+ public enum PathType {
+ QUERY_IDS,
+ FRAGMETNS,
+ INSTANCES,
+ SINGLE_INSTANCE
+ }
+
+ private String queryIdPath;
+ private PathType pathType;
+
+ private String queryId = "";
+ private String fragmentId = "";
+ private String instanceId = "";
+
+ public ShowQueryProfileStmt(String queryIdPath) {
+ this.queryIdPath = queryIdPath;
+ }
+
+ public PathType getPathType() {
+ return pathType;
+ }
+
+ public String getQueryId() {
+ return queryId;
+ }
+
+ public String getFragmentId() {
+ return fragmentId;
+ }
+
+ public String getInstanceId() {
+ return instanceId;
+ }
+
+ @Override
+ public void analyze(Analyzer analyzer) throws UserException {
+ super.analyze(analyzer);
+ if (Strings.isNullOrEmpty(queryIdPath)) {
+ // list all query ids
+ pathType = PathType.QUERY_IDS;
+ return;
+ }
+
+ if (!queryIdPath.startsWith("/")) {
+ throw new AnalysisException("Query path must starts with '/'");
+ }
+ pathType = PathType.QUERY_IDS;
+ String[] parts = queryIdPath.split("/");
+ if (parts.length > 4) {
+ throw new AnalysisException("Query path must in format '/queryId/fragmentId/instanceId'");
+ }
+
+ for (int i = 0; i < parts.length; i++) {
+ switch (i) {
+ case 0:
+ pathType = PathType.QUERY_IDS;
+ continue;
+ case 1:
+ queryId = parts[i];
+ pathType = PathType.FRAGMETNS;
+ break;
+ case 2:
+ fragmentId = parts[i];
+ pathType = PathType.INSTANCES;
+ break;
+ case 3:
+ instanceId = parts[i];
+ pathType = PathType.SINGLE_INSTANCE;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ @Override
+ public String toSql() {
+ StringBuilder sb = new StringBuilder("SHOW QUERY PROFILE ").append(queryIdPath);
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return toSql();
+ }
+
+ @Override
+ public ShowResultSetMetaData getMetaData() {
+ switch (pathType) {
+ case QUERY_IDS:
+ return META_DATA_QUERYIDS;
+ case FRAGMETNS:
+ return META_DATA_FRAGMENTS;
+ case INSTANCES:
+ return META_DATA_INSTANCES;
+ case SINGLE_INSTANCE:
+ return META_DATA_SINGLE_INSTANCE;
+ default:
+ return null;
+ }
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/StatementBase.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/StatementBase.java
index 4fb5f64..79047ac 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/StatementBase.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/StatementBase.java
@@ -35,10 +35,8 @@ public abstract class StatementBase implements ParseNode {
private String clusterName;
- // True if this QueryStmt is the top level query from an EXPLAIN <query>
- protected boolean isExplain = false;
- // True if the describe_stmt print verbose information, if `isVerbose` is true, `isExplain` must be set to true.
- protected boolean isVerbose = false;
+ // Set this variable if this QueryStmt is the top level query from an EXPLAIN <query>
+ protected ExplainOptions explainOptions = null;
/////////////////////////////////////////
// BEGIN: Members that need to be reset()
@@ -60,7 +58,7 @@ public abstract class StatementBase implements ParseNode {
*/
protected StatementBase(StatementBase other) {
analyzer = other.analyzer;
- isExplain = other.isExplain;
+ explainOptions = other.explainOptions;
}
/**
@@ -72,7 +70,6 @@ public abstract class StatementBase implements ParseNode {
*/
public void analyze(Analyzer analyzer) throws AnalysisException, UserException {
if (isAnalyzed()) return;
- if (isExplain) analyzer.setIsExplain();
this.analyzer = analyzer;
if (Strings.isNullOrEmpty(analyzer.getClusterName())) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_CLUSTER_NO_SELECT_CLUSTER);
@@ -80,14 +77,33 @@ public abstract class StatementBase implements ParseNode {
this.clusterName = analyzer.getClusterName();
}
- public Analyzer getAnalyzer() { return analyzer; }
- public boolean isAnalyzed() { return analyzer != null; }
- public void setIsExplain(boolean isExplain, boolean isVerbose) { this.isExplain = isExplain; this.isVerbose = isVerbose;}
- public boolean isExplain() { return isExplain; }
- public boolean isVerbose() { return isVerbose; }
+ public Analyzer getAnalyzer() {
+ return analyzer;
+ }
+
+ public boolean isAnalyzed() {
+ return analyzer != null;
+ }
+
+ public void setIsExplain(ExplainOptions options) {
+ this.explainOptions = options;
+ }
+
+ public boolean isExplain() {
+ return this.explainOptions != null;
+ }
+
+ public boolean isVerbose() {
+ return explainOptions != null && explainOptions.isVerbose();
+ }
+
+ public ExplainOptions getExplainOptions() {
+ return explainOptions;
+ }
+
/*
* Print SQL syntax corresponding to this node.
- *
+ *
* @see org.apache.doris.parser.ParseNode#toSql()
*/
@Override
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/profile/CounterNode.java b/fe/fe-core/src/main/java/org/apache/doris/common/profile/CounterNode.java
new file mode 100644
index 0000000..86f2d1a
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/profile/CounterNode.java
@@ -0,0 +1,53 @@
+// 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.doris.common.profile;
+
+import org.apache.doris.common.Pair;
+import org.apache.doris.common.TreeNode;
+
+public class CounterNode extends TreeNode<CounterNode> {
+ private Pair<String, String> counter;
+
+ public void setCounter(String key, String value) {
+ counter = Pair.create(key, value);
+ }
+
+ public String toTree(int indent) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(debugString(indent));
+ for (CounterNode node : getChildren()) {
+ sb.append("\n").append(node.debugString(indent + 4));
+ }
+ return sb.toString();
+ }
+
+ public String debugString(int indent) {
+ if (counter == null) {
+ return printIndent(indent) + " - Counters:";
+ }
+ return printIndent(indent) + " - " + counter.first + ": " + counter.second;
+ }
+
+ private String printIndent(int indent) {
+ String res = "";
+ for (int i = 0; i < indent; i++) {
+ res += " ";
+ }
+ return res;
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/profile/ExecNodeNode.java b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ExecNodeNode.java
new file mode 100644
index 0000000..4cc12b1
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ExecNodeNode.java
@@ -0,0 +1,27 @@
+// 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.doris.common.profile;
+
+public class ExecNodeNode extends ProfileTreeNode {
+
+ public ExecNodeNode(String name, String id) {
+ super(name, id);
+ }
+
+
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/profile/PlanTreeBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/common/profile/PlanTreeBuilder.java
new file mode 100644
index 0000000..39c92c8
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/profile/PlanTreeBuilder.java
@@ -0,0 +1,122 @@
+// 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.doris.common.profile;
+
+import org.apache.doris.common.UserException;
+import org.apache.doris.planner.DataSink;
+import org.apache.doris.planner.ExchangeNode;
+import org.apache.doris.planner.PlanFragment;
+import org.apache.doris.planner.PlanNode;
+import org.apache.doris.planner.PlanNodeId;
+import org.apache.doris.thrift.TExplainLevel;
+
+import com.clearspring.analytics.util.Lists;
+
+import java.util.List;
+
+public class PlanTreeBuilder {
+
+ private List<PlanFragment> fragments;
+ private PlanTreeNode treeRoot;
+ private List<PlanTreeNode> sinkNodes = Lists.newArrayList();
+ private List<PlanTreeNode> exchangeNodes = Lists.newArrayList();
+
+ public PlanTreeBuilder(List<PlanFragment> fragments) {
+ this.fragments = fragments;
+ }
+
+ public PlanTreeNode getTreeRoot() {
+ return treeRoot;
+ }
+
+ public void build() throws UserException {
+ buildFragmentPlans();
+ assembleFragmentPlans();
+ }
+
+ private void buildFragmentPlans() {
+ int i = 0;
+ for (PlanFragment fragment : fragments) {
+ DataSink sink = fragment.getSink();
+ PlanTreeNode sinkNode = null;
+ if (sink != null) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[").append(sink.getExchNodeId().asInt()).append(": ").append(sink.getClass().getSimpleName()).append("]");
+ sb.append("\nFragment: ").append(fragment.getId().asInt());
+ sb.append("\n").append(sink.getExplainString("", TExplainLevel.BRIEF));
+ sinkNode = new PlanTreeNode(sink.getExchNodeId(), sb.toString());
+ if (i == 0) {
+ // sink of first fragment, set it as tree root
+ treeRoot = sinkNode;
+ } else {
+ sinkNodes.add(sinkNode);
+ }
+ }
+
+ PlanNode planRoot = fragment.getPlanRoot();
+ if (planRoot != null) {
+ buildForPlanNode(planRoot, sinkNode);
+ }
+ i++;
+ }
+ }
+
+ private void assembleFragmentPlans() throws UserException {
+ for (PlanTreeNode sender : sinkNodes) {
+ if (sender == treeRoot) {
+ // This is the result sink, skip it
+ continue;
+ }
+ PlanNodeId senderId = sender.getId();
+ PlanTreeNode exchangeNode = findExchangeNode(senderId);
+ if (exchangeNode == null) {
+ throw new UserException("Failed to find exchange node for sender id: " + senderId.asInt());
+ }
+
+ exchangeNode.addChild(sender);
+ }
+ }
+
+ private PlanTreeNode findExchangeNode(PlanNodeId senderId) {
+ for (PlanTreeNode exchangeNode : exchangeNodes) {
+ if (exchangeNode.getId().equals(senderId)) {
+ return exchangeNode;
+ }
+ }
+ return null;
+ }
+
+ private void buildForPlanNode(PlanNode planNode, PlanTreeNode parent) {
+ PlanTreeNode node = new PlanTreeNode(planNode.getId(), planNode.toString());
+
+ if (parent != null) {
+ parent.addChild(node);
+ }
+
+ if (planNode.getPlanNodeName().equals(ExchangeNode.EXCHANGE_NODE)
+ || planNode.getPlanNodeName().equals(ExchangeNode.MERGING_EXCHANGE_NODE)) {
+ exchangeNodes.add(node);
+ } else {
+ // Do not traverse children of exchange node,
+ // They will be visited in other fragments.
+ for (PlanNode child : planNode.getChildren()) {
+ buildForPlanNode(child, node);
+ }
+ }
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/profile/PlanTreeNode.java b/fe/fe-core/src/main/java/org/apache/doris/common/profile/PlanTreeNode.java
new file mode 100644
index 0000000..def1976
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/profile/PlanTreeNode.java
@@ -0,0 +1,39 @@
+// 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.doris.common.profile;
+
+import org.apache.doris.common.TreeNode;
+import org.apache.doris.planner.PlanNodeId;
+
+public class PlanTreeNode extends TreeNode<PlanTreeNode> {
+ private PlanNodeId id;
+ private String explainStr;
+
+ public PlanTreeNode(PlanNodeId id, String explainStr) {
+ this.id = id;
+ this.explainStr = explainStr;
+ }
+
+ public PlanNodeId getId() {
+ return id;
+ }
+
+ public String getExplainStr() {
+ return explainStr;
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/profile/PlanTreePrinter.java b/fe/fe-core/src/main/java/org/apache/doris/common/profile/PlanTreePrinter.java
new file mode 100644
index 0000000..a4d9b6d
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/profile/PlanTreePrinter.java
@@ -0,0 +1,40 @@
+// 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.doris.common.profile;
+
+import hu.webarticum.treeprinter.BorderTreeNodeDecorator;
+import hu.webarticum.treeprinter.SimpleTreeNode;
+import hu.webarticum.treeprinter.TraditionalTreePrinter;
+
+public class PlanTreePrinter {
+
+ public static String printPlanExplanation(PlanTreeNode root) {
+ SimpleTreeNode rootNode = buildNode(root);
+ StringBuilder sb = new StringBuilder();
+ new TraditionalTreePrinter().print(new BorderTreeNodeDecorator(rootNode), sb);
+ return sb.toString();
+ }
+
+ private static SimpleTreeNode buildNode(PlanTreeNode planNode) {
+ SimpleTreeNode node = new SimpleTreeNode(planNode.getExplainStr());
+ for (PlanTreeNode child : planNode.getChildren()) {
+ node.addChild(buildNode(child));
+ }
+ return node;
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileTreeBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileTreeBuilder.java
new file mode 100644
index 0000000..e4a34e3
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileTreeBuilder.java
@@ -0,0 +1,349 @@
+// 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.doris.common.profile;
+
+import org.apache.doris.common.Pair;
+import org.apache.doris.common.UserException;
+import org.apache.doris.common.util.Counter;
+import org.apache.doris.common.util.RuntimeProfile;
+import org.apache.doris.thrift.TUnit;
+
+import org.apache.commons.lang3.tuple.ImmutableTriple;
+import org.apache.commons.lang3.tuple.Triple;
+
+import com.clearspring.analytics.util.Lists;
+import com.google.common.collect.Maps;
+
+import java.util.Formatter;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class is used to build a tree from the query runtime profile
+ * It will build tree for the entire query, and also tree for each instance,
+ * So that user can view the profile tree by query id or by instance id.
+ *
+ * Each runtime profile of a query should be built once and be read every where.
+ */
+public class ProfileTreeBuilder {
+
+ private static final String PROFILE_NAME_QUERY = "Query";
+ private static final String PROFILE_NAME_EXECUTION = "Execution Profile";
+ private static final String PROFILE_NAME_DATA_STREAM_SENDER = "DataStreamSender";
+ private static final String PROFILE_NAME_DATA_BUFFER_SENDER = "DataBufferSender";
+ private static final String PROFILE_NAME_BLOCK_MGR = "BlockMgr";
+ private static final String PROFILE_NAME_BUFFER_POOL = "Buffer pool";
+ private static final String PROFILE_NAME_EXCHANGE_NODE = "EXCHANGE_NODE";
+ public static final String DATA_BUFFER_SENDER_ID = "-1";
+ public static final String UNKNOWN_ID = "-2";
+
+ private RuntimeProfile profileRoot;
+
+ // auxiliary structure to link different fragments
+ private List<ProfileTreeNode> exchangeNodes = Lists.newArrayList();
+ private List<ProfileTreeNode> senderNodes = Lists.newArrayList();
+
+ // fragment id -> instance id -> instance tree root
+ private Map<String, Map<String, ProfileTreeNode>> instanceTreeMap = Maps.newHashMap();
+ // fragment id -> (instance id, instance host, instance active time)
+ private Map<String, List<Triple<String, String, Long>>> instanceActiveTimeMap = Maps.newHashMap();
+
+ // the tree root of the entire query profile tree
+ private ProfileTreeNode fragmentTreeRoot;
+
+ // Match string like:
+ // EXCHANGE_NODE (id=3):(Active: 103.899ms, % non-child: 2.27%)
+ // Extract "EXCHANGE_NODE" and "3"
+ private static final String EXEC_NODE_NAME_ID_PATTERN_STR = "^(.*) .*id=([0-9]+).*";
+ private static final Pattern EXEC_NODE_NAME_ID_PATTERN;
+
+ // Match string like:
+ // Fragment 0:
+ // Extract "0"
+ private static final String FRAGMENT_ID_PATTERN_STR = "^Fragment ([0-9]+).*";
+ private static final Pattern FRAGMENT_ID_PATTERN;
+
+ // Match string like:
+ // Instance e0f7390f5363419e-b416a2a7999608b6 (host=TNetworkAddress(hostname:192.168.1.1, port:9060)):(Active: 1s858ms, % non-child: 0.02%)
+ // Extract "e0f7390f5363419e-b416a2a7999608b6", "192.168.1.1", "9060"
+ private static final String INSTANCE_PATTERN_STR = "^Instance (.*) \\(.*hostname:(.*), port:([0-9]+).*";
+ private static final Pattern INSTANCE_PATTERN;
+
+ static {
+ EXEC_NODE_NAME_ID_PATTERN = Pattern.compile(EXEC_NODE_NAME_ID_PATTERN_STR);
+ FRAGMENT_ID_PATTERN = Pattern.compile(FRAGMENT_ID_PATTERN_STR);
+ INSTANCE_PATTERN = Pattern.compile(INSTANCE_PATTERN_STR);
+ }
+
+ public ProfileTreeBuilder(RuntimeProfile root) {
+ this.profileRoot = root;
+ }
+
+ public ProfileTreeNode getFragmentTreeRoot() {
+ return fragmentTreeRoot;
+ }
+
+ public ProfileTreeNode getInstanceTreeRoot(String fragmentId, String instanceId) {
+ if (!instanceTreeMap.containsKey(fragmentId)) {
+ return null;
+ }
+ return instanceTreeMap.get(fragmentId).get(instanceId);
+ }
+
+ public List<Triple<String, String, Long>> getInstanceList(String fragmentId) {
+ return instanceActiveTimeMap.get(fragmentId);
+ }
+
+ public void build() throws UserException {
+ reset();
+ unwrapProfile();
+ analyzeAndBuildFragmentTrees();
+ assembleFragmentTrees();
+ }
+
+ private void reset() {
+ exchangeNodes.clear();
+ senderNodes.clear();
+ instanceTreeMap.clear();
+ instanceActiveTimeMap.clear();
+ fragmentTreeRoot = null;
+ }
+
+ private void unwrapProfile() throws UserException {
+ while(true) {
+ if (profileRoot.getName().startsWith(PROFILE_NAME_QUERY)) {
+ List<Pair<RuntimeProfile, Boolean>> children = profileRoot.getChildList();
+ boolean find = false;
+ for (Pair<RuntimeProfile, Boolean> pair : children) {
+ if (pair.first.getName().startsWith(PROFILE_NAME_EXECUTION)) {
+ this.profileRoot = pair.first;
+ find = true;
+ break;
+ }
+ }
+ if (!find) {
+ throw new UserException("Invalid profile. Expected " + PROFILE_NAME_EXECUTION
+ + " in " + PROFILE_NAME_QUERY);
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ private void analyzeAndBuildFragmentTrees() throws UserException {
+ List<Pair<RuntimeProfile, Boolean>> childrenFragment = profileRoot.getChildList();
+ for (Pair<RuntimeProfile, Boolean> pair : childrenFragment) {
+ analyzeAndBuildFragmentTree(pair.first);
+ }
+ }
+
+ private void analyzeAndBuildFragmentTree(RuntimeProfile fragmentProfile) throws UserException {
+ String fragmentId = getFragmentId(fragmentProfile);
+ List<Pair<RuntimeProfile, Boolean>> fragmentChildren = fragmentProfile.getChildList();
+ if (fragmentChildren.isEmpty()) {
+ throw new UserException("Empty instance in fragment: " + fragmentProfile.getName());
+ }
+
+ // 1. Get max active time of instances in this fragment
+ List<Triple<String, String, Long>> instanceIdAndActiveTimeList = Lists.newArrayList();
+ long maxActiveTimeNs = 0;
+ for (Pair<RuntimeProfile, Boolean> pair : fragmentChildren) {
+ Triple<String, String, Long> instanceIdAndActiveTime = getInstanceIdHostAndActiveTime(pair.first);
+ maxActiveTimeNs = Math.max(instanceIdAndActiveTime.getRight(), maxActiveTimeNs);
+ instanceIdAndActiveTimeList.add(instanceIdAndActiveTime);
+ }
+ instanceActiveTimeMap.put(fragmentId, instanceIdAndActiveTimeList);
+
+ // 2. Build tree for all fragments
+ // All instance in a fragment are same, so use first instance to build the fragment tree
+ RuntimeProfile instanceProfile = fragmentChildren.get(0).first;
+ ProfileTreeNode instanceTreeRoot = buildSingleInstanceTree(instanceProfile, fragmentId, null);
+ instanceTreeRoot.setMaxInstanceActiveTime(RuntimeProfile.printCounter(maxActiveTimeNs, TUnit.TIME_NS));
+ if (instanceTreeRoot.id.equals(DATA_BUFFER_SENDER_ID)) {
+ fragmentTreeRoot = instanceTreeRoot;
+ }
+
+ // 2. Build tree for each single instance
+ int i = 0;
+ Map<String, ProfileTreeNode> instanceTrees = Maps.newHashMap();
+ for (Pair<RuntimeProfile, Boolean> pair : fragmentChildren) {
+ String instanceId = instanceIdAndActiveTimeList.get(i).getLeft();
+ ProfileTreeNode singleInstanceTreeNode = buildSingleInstanceTree(pair.first, fragmentId, instanceId);
+ instanceTrees.put(instanceId, singleInstanceTreeNode);
+ i++;
+ }
+ this.instanceTreeMap.put(fragmentId, instanceTrees);
+ }
+
+ // If instanceId is null, which means this profile tree node is for bulding the entire fragment tree.
+ // So that we need to add sender and exchange node to the auxiliary structure.
+ private ProfileTreeNode buildSingleInstanceTree(RuntimeProfile instanceProfile, String fragmentId,
+ String instanceId) throws UserException {
+ List<Pair<RuntimeProfile, Boolean>> instanceChildren = instanceProfile.getChildList();
+ ProfileTreeNode senderNode = null;
+ ProfileTreeNode execNode = null;
+ for (Pair<RuntimeProfile, Boolean> pair : instanceChildren) {
+ RuntimeProfile profile = pair.first;
+ if (profile.getName().startsWith(PROFILE_NAME_DATA_STREAM_SENDER)
+ || profile.getName().startsWith(PROFILE_NAME_DATA_BUFFER_SENDER)) {
+ senderNode = buildTreeNode(profile, null, fragmentId, instanceId);
+ if (instanceId == null) {
+ senderNodes.add(senderNode);
+ }
+ } else if (profile.getName().startsWith(PROFILE_NAME_BLOCK_MGR)
+ || profile.getName().startsWith(PROFILE_NAME_BUFFER_POOL)) {
+ // skip BlockMgr nad Buffer pool profile
+ continue;
+ } else {
+ // This should be an ExecNode profile
+ execNode = buildTreeNode(profile, null, fragmentId, instanceId);
+ }
+ }
+ if (senderNode == null || execNode == null) {
+ throw new UserException("Invalid instance profile, without sender or exec node: " + instanceProfile);
+ }
+ senderNode.addChild(execNode);
+ execNode.setParentNode(senderNode);
+
+ senderNode.setFragmentAndInstanceId(fragmentId, instanceId);
+ execNode.setFragmentAndInstanceId(fragmentId, instanceId);
+
+ return senderNode;
+ }
+
+ private ProfileTreeNode buildTreeNode(RuntimeProfile profile, ProfileTreeNode root,
+ String fragmentId, String instanceId) {
+ String name = profile.getName();
+ if (name.startsWith(PROFILE_NAME_BUFFER_POOL)) {
+ // skip Buffer pool, and buffer pool does not has child
+ return null;
+ }
+ boolean isDataBufferSender = name.startsWith(PROFILE_NAME_DATA_BUFFER_SENDER);
+ Matcher m = EXEC_NODE_NAME_ID_PATTERN.matcher(name);
+ String extractName;
+ String extractId;
+ if ((!m.find() && !isDataBufferSender) || m.groupCount() != 2) {
+ // DataStreamBuffer name like: "DataBufferSender (dst_fragment_instance_id=d95356f9219b4831-986b4602b41683ca):"
+ // So it has no id.
+ // Other profile should has id like:
+ // EXCHANGE_NODE (id=3):(Active: 103.899ms, % non-child: 2.27%)
+ // HASH_JOIN_NODE (id=2):(Active: 972.329us, % non-child: 0.00%)
+ extractName = name;
+ extractId = UNKNOWN_ID;
+ } else {
+ extractName = isDataBufferSender ? PROFILE_NAME_DATA_BUFFER_SENDER : m.group(1);
+ extractId = isDataBufferSender ? DATA_BUFFER_SENDER_ID : m.group(2);
+ }
+ Counter activeCounter = profile.getCounterTotalTime();
+ ExecNodeNode node = new ExecNodeNode(extractName, extractId);
+ node.setActiveTime(RuntimeProfile.printCounter(activeCounter.getValue(), activeCounter.getType()));
+ try (Formatter fmt = new Formatter()) {
+ node.setNonChild(fmt.format("%.2f", profile.getLocalTimePercent()).toString());
+ }
+ CounterNode rootCounterNode = new CounterNode();
+ buildCounterNode(profile, RuntimeProfile.ROOT_COUNTER, rootCounterNode);
+ node.setCounterNode(rootCounterNode);
+
+ if (root != null) {
+ root.addChild(node);
+ node.setParentNode(root);
+ }
+
+ if (node.name.equals(PROFILE_NAME_EXCHANGE_NODE) && instanceId == null) {
+ exchangeNodes.add(node);
+ }
+
+ // The children in profile is reversed, so traverse it from last to first
+ List<Pair<RuntimeProfile, Boolean>> children = profile.getChildList();
+ for (int i = children.size() - 1; i >= 0; i--) {
+ Pair<RuntimeProfile, Boolean> pair = children.get(i);
+ ProfileTreeNode execNode = buildTreeNode(pair.first, node, fragmentId, instanceId);
+ if (execNode != null) {
+ // For buffer pool profile, buildTreeNode will return null
+ execNode.setFragmentAndInstanceId(fragmentId, instanceId);
+ }
+ }
+ return node;
+ }
+
+ private void buildCounterNode(RuntimeProfile profile, String counterName, CounterNode root) {
+ Map<String, TreeSet<String>> childCounterMap = profile.getChildCounterMap();
+ Set<String> childCounterSet = childCounterMap.get(counterName);
+ if (childCounterSet == null) {
+ return;
+ }
+
+ Map<String, Counter> counterMap = profile.getCounterMap();
+ for (String childCounterName : childCounterSet) {
+ Counter counter = counterMap.get(childCounterName);
+ CounterNode counterNode = new CounterNode();
+ if (root != null) {
+ root.addChild(counterNode);
+ }
+ counterNode.setCounter(childCounterName, RuntimeProfile.printCounter(counter.getValue(), counter.getType()));
+ buildCounterNode(profile, childCounterName, counterNode);
+ }
+ return;
+ }
+
+ private void assembleFragmentTrees() throws UserException {
+ for (ProfileTreeNode senderNode : senderNodes) {
+ if (senderNode.id.equals(DATA_BUFFER_SENDER_ID)) {
+ // this is result sender, skip it.
+ continue;
+ }
+ ProfileTreeNode exchangeNode = findExchangeNode(senderNode.id);
+ exchangeNode.addChild(senderNode);
+ senderNode.setParentNode(exchangeNode);
+ }
+ }
+
+ private ProfileTreeNode findExchangeNode(String senderId) throws UserException {
+ for (ProfileTreeNode node : exchangeNodes) {
+ if (node.id.equals(senderId)) {
+ return node;
+ }
+ }
+ throw new UserException("Failed to find fragment for sender id: " + senderId);
+ }
+
+ private String getFragmentId(RuntimeProfile fragmentProfile) throws UserException {
+ String name = fragmentProfile.getName();
+ Matcher m = FRAGMENT_ID_PATTERN.matcher(name);
+ if (!m.find() || m.groupCount() != 1) {
+ throw new UserException("Invalid fragment profile name: " + name);
+ }
+ return m.group(1);
+ }
+
+ private Triple<String, String, Long> getInstanceIdHostAndActiveTime(RuntimeProfile instanceProfile)
+ throws UserException {
+ long activeTimeNs = instanceProfile.getCounterTotalTime().getValue();
+ String name = instanceProfile.getName();
+ Matcher m = INSTANCE_PATTERN.matcher(name);
+ if (!m.find() || m.groupCount() != 3) {
+ throw new UserException("Invalid instance profile name: " + name);
+ }
+ return new ImmutableTriple<>(m.group(1), m.group(2) + ":" + m.group(3), activeTimeNs);
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileTreeNode.java b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileTreeNode.java
new file mode 100644
index 0000000..e68e9e7
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileTreeNode.java
@@ -0,0 +1,145 @@
+// 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.doris.common.profile;
+
+import org.apache.doris.common.TreeNode;
+
+import com.google.common.base.Strings;
+
+public class ProfileTreeNode extends TreeNode<ProfileTreeNode> {
+
+ protected String name;
+ protected String id;
+ protected CounterNode counterNode;
+ protected String activeTime;
+ protected String nonChild;
+
+ protected String fragmentId = "";
+ protected String instanceId = "";
+
+ // This is used to record the max activeTime of all instances in a fragment.
+ // Usually recorded on the Sender node.
+ protected String maxInstanceActiveTime = "";
+
+ protected ProfileTreeNode parentNode;
+
+ protected ProfileTreeNode(String name, String id) {
+ this.name = name;
+ this.id = id;
+ }
+
+ public void setParentNode(ProfileTreeNode parentNode) {
+ this.parentNode = parentNode;
+ }
+
+ public ProfileTreeNode getParentNode() {
+ return parentNode;
+ }
+
+ public void setCounterNode(CounterNode counterNode) {
+ this.counterNode = counterNode;
+ }
+
+ public CounterNode getCounterNode() {
+ return counterNode;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setActiveTime(String activeTime) {
+ this.activeTime = activeTime;
+ }
+
+ public String getActiveTime() {
+ return activeTime;
+ }
+
+ public void setNonChild(String nonChild) {
+ this.nonChild = nonChild;
+ }
+
+ public String getNonChild() {
+ return nonChild;
+ }
+
+ public String getIdentity() {
+ if (id.equals(ProfileTreeBuilder.UNKNOWN_ID)) {
+ return "[" + name + "]";
+ }
+ return "[" + id + ": " + name + "]";
+ }
+
+ public void setFragmentAndInstanceId(String fragmentId, String instanceId) {
+ this.fragmentId = fragmentId;
+ this.instanceId = instanceId;
+ }
+
+ public void setMaxInstanceActiveTime(String maxInstanceActiveTime) {
+ this.maxInstanceActiveTime = maxInstanceActiveTime;
+ }
+
+ public String getMaxInstanceActiveTime() {
+ return maxInstanceActiveTime;
+ }
+
+ public String debugTree(int indent, ProfileTreePrinter.PrintLevel level) {
+ StringBuilder sb = new StringBuilder(printIndent(indent));
+ sb.append(debugString(indent, level));
+ if (!getChildren().isEmpty()) {
+ int childSize = getChildren().size();
+ for (int i = 0; i < childSize; i++) {
+ ProfileTreeNode node = getChild(i);
+ sb.append("\n").append(node.debugTree(indent + 4, level));
+ }
+ }
+ return sb.toString();
+ }
+
+ public String debugString(int indent, ProfileTreePrinter.PrintLevel level) {
+ String indentStr = printIndent(indent);
+ StringBuilder sb = new StringBuilder();
+ sb.append(indentStr).append(getIdentity()).append("\n");
+ if (level == ProfileTreePrinter.PrintLevel.FRAGMENT) {
+ sb.append(indentStr).append("Fragment: ").append(fragmentId).append("\n");
+ if (!Strings.isNullOrEmpty(maxInstanceActiveTime)) {
+ sb.append(indentStr).append("MaxActiveTime: ").append(maxInstanceActiveTime).append("\n");
+ }
+ }
+ if (level == ProfileTreePrinter.PrintLevel.INSTANCE) {
+ sb.append("(Active: ").append(activeTime).append(", ");
+ sb.append("non-child: ").append(nonChild).append(")").append("\n");
+ // print counters
+ sb.append(counterNode.toTree(indent + 1));
+ }
+ return sb.toString();
+ }
+
+ private String printIndent(int indent) {
+ String res = "";
+ for (int i = 0; i < indent; i++) {
+ res += " ";
+ }
+ return res;
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileTreePrinter.java b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileTreePrinter.java
new file mode 100644
index 0000000..19d2f6a
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ProfileTreePrinter.java
@@ -0,0 +1,55 @@
+// 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.doris.common.profile;
+
+import hu.webarticum.treeprinter.BorderTreeNodeDecorator;
+import hu.webarticum.treeprinter.SimpleTreeNode;
+import hu.webarticum.treeprinter.TraditionalTreePrinter;
+
+public class ProfileTreePrinter {
+
+ public static enum PrintLevel {
+ FRAGMENT,
+ INSTANCE
+ }
+
+ // Fragment tree only print the entire query plan tree with node name
+ // and some other brief info.
+ public static String printFragmentTree(ProfileTreeNode root) {
+ SimpleTreeNode rootNode = buildNode(root, PrintLevel.FRAGMENT);
+ StringBuilder sb = new StringBuilder();
+ new TraditionalTreePrinter().print(new BorderTreeNodeDecorator(rootNode), sb);
+ return sb.toString();
+ }
+
+ // Instance tree will print the details of the tree of a single instance
+ public static String printInstanceTree(ProfileTreeNode root) {
+ SimpleTreeNode rootNode = buildNode(root, PrintLevel.INSTANCE);
+ StringBuilder sb = new StringBuilder();
+ new TraditionalTreePrinter().print(new BorderTreeNodeDecorator(rootNode), sb);
+ return sb.toString();
+ }
+
+ private static SimpleTreeNode buildNode(ProfileTreeNode profileNode, PrintLevel level) {
+ SimpleTreeNode node = new SimpleTreeNode(profileNode.debugString(0, level));
+ for (ProfileTreeNode child : profileNode.getChildren()) {
+ node.addChild(buildNode(child, level));
+ }
+ return node;
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/util/ProfileManager.java b/fe/fe-core/src/main/java/org/apache/doris/common/util/ProfileManager.java
index e084a19..f4b4ca5 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/util/ProfileManager.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/ProfileManager.java
@@ -17,10 +17,16 @@
package org.apache.doris.common.util;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.profile.ProfileTreeBuilder;
+import org.apache.doris.common.profile.ProfileTreeNode;
+import org.apache.doris.common.profile.ProfileTreePrinter;
+
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import org.apache.commons.lang3.tuple.Triple;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -64,10 +70,12 @@ public class ProfileManager {
public static final ArrayList<String> PROFILE_HEADERS = new ArrayList(
Arrays.asList(QUERY_ID, USER, DEFAULT_DB, SQL_STATEMENT, QUERY_TYPE,
START_TIME, END_TIME, TOTAL_TIME, QUERY_STATE));
-
+
private class ProfileElement {
- public Map<String, String> infoStrings = Maps.newHashMap();
- public String profileContent;
+ public Map<String, String> infoStrings = Maps.newHashMap();
+ public String profileContent = "";
+ public ProfileTreeBuilder builder = null;
+ public String errMsg = "";
}
// only protect profileDeque; profileMap is concurrent, no need to protect
@@ -103,6 +111,17 @@ public class ProfileManager {
for (String header : PROFILE_HEADERS) {
element.infoStrings.put(header, summaryProfile.getInfoString(header));
}
+
+ ProfileTreeBuilder builder = new ProfileTreeBuilder(profile);
+ try {
+ builder.build();
+ } catch (Exception e) {
+ element.errMsg = e.getMessage();
+ LOG.warn("failed to build profile tree", e);
+ return element;
+ }
+
+ element.builder = builder;
element.profileContent = profile.toString();
return element;
}
@@ -168,4 +187,70 @@ public class ProfileManager {
readLock.unlock();
}
}
+
+ public String getFragmentProfileTreeString(String queryID) {
+ readLock.lock();
+ try {
+ ProfileElement element = profileMap.get(queryID);
+ if (element == null || element.builder == null) {
+ return null;
+ }
+ ProfileTreeBuilder builder = element.builder;
+ return builder.getFragmentTreeRoot().debugTree(0, ProfileTreePrinter.PrintLevel.INSTANCE);
+ } catch (Exception e) {
+ LOG.warn("failed to get profile tree", e);
+ return null;
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ public ProfileTreeNode getFragmentProfileTree(String queryID) throws AnalysisException {
+ ProfileTreeNode tree;
+ readLock.lock();
+ try {
+ ProfileElement element = profileMap.get(queryID);
+ if (element == null || element.builder == null) {
+ throw new AnalysisException("failed to get fragment profile tree. err: "
+ + (element == null ? "not found" : element.errMsg));
+ }
+ return element.builder.getFragmentTreeRoot();
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ public List<Triple<String, String, Long>> getFragmentInstanceList(String queryID, String fragmentId) throws AnalysisException {
+ ProfileTreeBuilder builder;
+ readLock.lock();
+ try {
+ ProfileElement element = profileMap.get(queryID);
+ if (element == null || element.builder == null) {
+ throw new AnalysisException("failed to get instance list. err: "
+ + (element == null ? "not found" : element.errMsg));
+ }
+ builder = element.builder;
+ } finally {
+ readLock.unlock();
+ }
+
+ return builder.getInstanceList(fragmentId);
+ }
+
+ public ProfileTreeNode getInstanceProfileTree(String queryID, String fragmentId, String instanceId) throws AnalysisException {
+ ProfileTreeBuilder builder;
+ readLock.lock();
+ try {
+ ProfileElement element = profileMap.get(queryID);
+ if (element == null || element.builder == null) {
+ throw new AnalysisException("failed to get instance profile tree. err: "
+ + (element == null ? "not found" : element.errMsg));
+ }
+ builder = element.builder;
+ } finally {
+ readLock.unlock();
+ }
+
+ return builder.getInstanceTreeRoot(fragmentId, instanceId);
+ }
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/util/RuntimeProfile.java b/fe/fe-core/src/main/java/org/apache/doris/common/util/RuntimeProfile.java
index 83e8d2b..bafece3 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/util/RuntimeProfile.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/RuntimeProfile.java
@@ -24,13 +24,13 @@ import org.apache.doris.thrift.TRuntimeProfileNode;
import org.apache.doris.thrift.TRuntimeProfileTree;
import org.apache.doris.thrift.TUnit;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import java.util.Collections;
import java.util.Comparator;
import java.util.Formatter;
@@ -47,7 +47,7 @@ import java.util.TreeSet;
*/
public class RuntimeProfile {
private static final Logger LOG = LogManager.getLogger(RuntimeProfile.class);
- private static String ROOT_COUNTER = "";
+ public static String ROOT_COUNTER = "";
private Counter counterTotalTime;
private double localTimePercent;
@@ -62,7 +62,7 @@ public class RuntimeProfile {
private LinkedList<Pair<RuntimeProfile, Boolean>> childList = Lists.newLinkedList();
private String name;
-
+
public RuntimeProfile(String name) {
this();
this.name = name;
@@ -73,7 +73,11 @@ public class RuntimeProfile {
this.localTimePercent = 0;
this.counterMap.put("TotalTime", counterTotalTime);
}
-
+
+ public String getName() {
+ return name;
+ }
+
public Counter getCounterTotalTime() {
return counterTotalTime;
}
@@ -86,12 +90,20 @@ public class RuntimeProfile {
return childList;
}
- public Map<String, RuntimeProfile> getChildMap () {
+ public Map<String, RuntimeProfile> getChildMap() {
return childMap;
}
+ public Map<String, TreeSet<String>> getChildCounterMap() {
+ return childCounterMap;
+ }
+
+ public double getLocalTimePercent() {
+ return localTimePercent;
+ }
+
public Counter addCounter(String name, TUnit type, String parentCounterName) {
- Counter counter = this.counterMap.get(name);
+ Counter counter = this.counterMap.get(name);
if (counter != null) {
return counter;
} else {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/AggregationNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/AggregationNode.java
index 975f2b1..8fcba78 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/AggregationNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/AggregationNode.java
@@ -29,13 +29,13 @@ import org.apache.doris.thrift.TExpr;
import org.apache.doris.thrift.TPlanNode;
import org.apache.doris.thrift.TPlanNodeType;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
import java.util.ArrayList;
import java.util.List;
@@ -256,16 +256,20 @@ public class AggregationNode extends PlanNode {
}
@Override
- protected String getNodeExplainString(String detailPrefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String detailPrefix, TExplainLevel detailLevel) {
StringBuilder output = new StringBuilder();
String nameDetail = getDisplayLabelDetail();
if (nameDetail != null) {
output.append(detailPrefix + nameDetail + "\n");
}
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return output.toString();
+ }
+
if (aggInfo.getAggregateExprs() != null && aggInfo.getMaterializedAggregateExprs().size() > 0) {
output.append(detailPrefix + "output: ").append(
- getExplainString(aggInfo.getAggregateExprs()) + "\n");
+ getExplainString(aggInfo.getAggregateExprs()) + "\n");
}
// TODO: group by can be very long. Break it into multiple lines
output.append(detailPrefix + "group by: ").append(
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/AnalyticEvalNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/AnalyticEvalNode.java
index ab6903c..d06e4dc 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/AnalyticEvalNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/AnalyticEvalNode.java
@@ -30,15 +30,15 @@ import org.apache.doris.thrift.TPlanNode;
import org.apache.doris.thrift.TPlanNodeType;
import org.apache.doris.thrift.TQueryOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import java.util.List;
/**
@@ -199,10 +199,12 @@ public class AnalyticEvalNode extends PlanNode {
}
}
- protected String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ @Override
+ public String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return "";
+ }
StringBuilder output = new StringBuilder();
- // output.append(String.format("%s%s", prefix, getDisplayLabel()));
- // output.append("\n");
output.append(prefix + "functions: ");
List<String> strings = Lists.newArrayList();
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/AssertNumRowsNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/AssertNumRowsNode.java
index cd49244..86b978d 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/AssertNumRowsNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/AssertNumRowsNode.java
@@ -47,7 +47,10 @@ public class AssertNumRowsNode extends PlanNode {
}
@Override
- protected String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return "";
+ }
StringBuilder output = new StringBuilder()
.append(prefix + "assert number of rows: ")
.append(assertion).append(" ").append(desiredNumOfRows).append("\n");
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/BrokerScanNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/BrokerScanNode.java
index e3e7dc5..7c5c651 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/BrokerScanNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/BrokerScanNode.java
@@ -51,14 +51,14 @@ import org.apache.doris.thrift.TScanRange;
import org.apache.doris.thrift.TScanRangeLocation;
import org.apache.doris.thrift.TScanRangeLocations;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
@@ -538,13 +538,15 @@ public class BrokerScanNode extends LoadScanNode {
}
@Override
- protected String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
StringBuilder output = new StringBuilder();
if (!isLoad()) {
BrokerTable brokerTable = (BrokerTable) targetTable;
output.append(prefix).append("TABLE: ").append(brokerTable.getName()).append("\n");
- output.append(prefix).append("PATH: ")
- .append(Joiner.on(",").join(brokerTable.getPaths())).append("\",\n");
+ if (detailLevel != TExplainLevel.BRIEF) {
+ output.append(prefix).append("PATH: ")
+ .append(Joiner.on(",").join(brokerTable.getPaths())).append("\",\n");
+ }
}
output.append(prefix).append("BROKER: ").append(brokerDesc.getName()).append("\n");
return output.toString();
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/CrossJoinNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/CrossJoinNode.java
index cc0eb95..9246549 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/CrossJoinNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/CrossJoinNode.java
@@ -23,11 +23,11 @@ import org.apache.doris.thrift.TExplainLevel;
import org.apache.doris.thrift.TPlanNode;
import org.apache.doris.thrift.TPlanNodeType;
-import com.google.common.base.MoreObjects;
-
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import com.google.common.base.MoreObjects;
+
/**
* Cross join between left child and right child.
*/
@@ -84,7 +84,10 @@ public class CrossJoinNode extends PlanNode {
}
@Override
- protected String getNodeExplainString(String detailPrefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String detailPrefix, TExplainLevel detailLevel) {
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return "";
+ }
StringBuilder output = new StringBuilder().append(detailPrefix + "cross join:" + "\n");
if (!conjuncts.isEmpty()) {
output.append(detailPrefix + "predicates: ").append(getExplainString(conjuncts) + "\n");
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/EsScanNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/EsScanNode.java
index ecd807a..d075946 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/EsScanNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/EsScanNode.java
@@ -41,6 +41,9 @@ import org.apache.doris.thrift.TScanRange;
import org.apache.doris.thrift.TScanRangeLocation;
import org.apache.doris.thrift.TScanRangeLocations;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -48,9 +51,6 @@ import com.google.common.collect.Multimap;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -300,11 +300,14 @@ public class EsScanNode extends ScanNode {
}
@Override
- protected String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
StringBuilder output = new StringBuilder();
-
output.append(prefix).append("TABLE: ").append(table.getName()).append("\n");
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return output.toString();
+ }
+
if (null != sortColumn) {
output.append(prefix).append("SORT COLUMN: ").append(sortColumn).append("\n");
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/ExchangeNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/ExchangeNode.java
index 350921f..a19aa43 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/ExchangeNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/ExchangeNode.java
@@ -50,6 +50,9 @@ import org.apache.logging.log4j.Logger;
public class ExchangeNode extends PlanNode {
private static final Logger LOG = LogManager.getLogger(ExchangeNode.class);
+ public static final String EXCHANGE_NODE = "EXCHANGE";
+ public static final String MERGING_EXCHANGE_NODE = "MERGING-EXCHANGE";
+
// The parameters based on which sorted input streams are merged by this
// exchange node. Null if this exchange does not merge sorted streams
private SortInfo mergeInfo;
@@ -64,7 +67,7 @@ public class ExchangeNode extends PlanNode {
* need to compute the cardinality here.
*/
public ExchangeNode(PlanNodeId id, PlanNode inputNode, boolean copyConjuncts) {
- super(id, inputNode, "EXCHANGE");
+ super(id, inputNode, EXCHANGE_NODE);
offset = 0;
children.add(inputNode);
if (!copyConjuncts) {
@@ -101,7 +104,7 @@ public class ExchangeNode extends PlanNode {
public void setMergeInfo(SortInfo info, long offset) {
this.mergeInfo = info;
this.offset = offset;
- this.planNodeName = "MERGING-EXCHANGE";
+ this.planNodeName = MERGING_EXCHANGE_NODE;
}
@Override
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/ExportSink.java b/fe/fe-core/src/main/java/org/apache/doris/planner/ExportSink.java
index dcddbca..01229e5 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/ExportSink.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/ExportSink.java
@@ -48,6 +48,9 @@ public class ExportSink extends DataSink {
public String getExplainString(String prefix, TExplainLevel explainLevel) {
StringBuilder sb = new StringBuilder();
sb.append(prefix + "EXPORT SINK\n");
+ if (explainLevel == TExplainLevel.BRIEF) {
+ return sb.toString();
+ }
sb.append(prefix + " path=" + exportPath + "\n");
sb.append(prefix + " columnSeparator="
+ StringEscapeUtils.escapeJava(columnSeparator) + "\n");
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/HashJoinNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/HashJoinNode.java
index e847bfe..93ed8cf 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/HashJoinNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/HashJoinNode.java
@@ -35,13 +35,13 @@ import org.apache.doris.thrift.THashJoinNode;
import org.apache.doris.thrift.TPlanNode;
import org.apache.doris.thrift.TPlanNodeType;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -302,12 +302,17 @@ public class HashJoinNode extends PlanNode {
}
@Override
- protected String getNodeExplainString(String detailPrefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String detailPrefix, TExplainLevel detailLevel) {
String distrModeStr =
- (distrMode != DistributionMode.NONE) ? (" (" + distrMode.toString() + ")") : "";
+ (distrMode != DistributionMode.NONE) ? (" (" + distrMode.toString() + ")") : "";
StringBuilder output = new StringBuilder()
- .append(detailPrefix).append("join op: ").append(joinOp.toString()).append(distrModeStr).append("\n")
- .append(detailPrefix).append("hash predicates:\n")
+ .append(detailPrefix).append("join op: ").append(joinOp.toString()).append(distrModeStr).append("\n");
+
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return output.toString();
+ }
+
+ output.append(detailPrefix).append("hash predicates:\n")
.append(detailPrefix).append("colocate: ").append(isColocate).append(isColocate ? "" : ", reason: " + colocateReason).append("\n");
for (BinaryPredicate eqJoinPredicate : eqJoinConjuncts) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/MergeJoinNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/MergeJoinNode.java
index 2ac7e8f..a775874 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/MergeJoinNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/MergeJoinNode.java
@@ -125,7 +125,7 @@ public class MergeJoinNode extends PlanNode {
}
@Override
- protected String getNodeExplainString(String detailPrefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String detailPrefix, TExplainLevel detailLevel) {
String distrModeStr =
(distrMode != DistributionMode.NONE) ? (" (" + distrMode.toString() + ")") : "";
StringBuilder output = new StringBuilder().append(
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/MergeNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/MergeNode.java
index 1f7f9d4..629cfa9 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/MergeNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/MergeNode.java
@@ -30,8 +30,10 @@ import org.apache.doris.thrift.TExpr;
import org.apache.doris.thrift.TMergeNode;
import org.apache.doris.thrift.TPlanNode;
import org.apache.doris.thrift.TPlanNodeType;
+
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
+
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
@@ -171,7 +173,10 @@ public class MergeNode extends PlanNode {
}
@Override
- protected String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return "";
+ }
StringBuilder output = new StringBuilder();
// A MergeNode may have predicates if a union is used inside an inline view,
// and the enclosing select stmt has predicates referring to the inline view.
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/MysqlScanNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/MysqlScanNode.java
index e54875e..945542b 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/MysqlScanNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/MysqlScanNode.java
@@ -32,13 +32,13 @@ import org.apache.doris.thrift.TPlanNode;
import org.apache.doris.thrift.TPlanNodeType;
import org.apache.doris.thrift.TScanRangeLocations;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
import java.util.ArrayList;
import java.util.List;
@@ -75,9 +75,12 @@ public class MysqlScanNode extends ScanNode {
}
@Override
- protected String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
StringBuilder output = new StringBuilder();
output.append(prefix).append("TABLE: ").append(tblName).append("\n");
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return output.toString();
+ }
output.append(prefix).append("Query: ").append(getMysqlQueryStr()).append("\n");
return output.toString();
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/MysqlTableSink.java b/fe/fe-core/src/main/java/org/apache/doris/planner/MysqlTableSink.java
index d1b7edc..2104a70 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/MysqlTableSink.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/MysqlTableSink.java
@@ -42,7 +42,10 @@ public class MysqlTableSink extends DataSink {
@Override
public String getExplainString(String prefix, TExplainLevel explainLevel) {
- return null;
+ StringBuilder sb = new StringBuilder();
+ sb.append("MYSQL TABLE SINK\n");
+ sb.append("host: ").append(host).append("\n");
+ return sb.toString();
}
@Override
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/OdbcScanNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/OdbcScanNode.java
index 7e37729..918a567 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/OdbcScanNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/OdbcScanNode.java
@@ -34,13 +34,13 @@ import org.apache.doris.thrift.TPlanNode;
import org.apache.doris.thrift.TPlanNodeType;
import org.apache.doris.thrift.TScanRangeLocations;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
import java.util.ArrayList;
import java.util.List;
@@ -94,10 +94,13 @@ public class OdbcScanNode extends ScanNode {
}
@Override
- protected String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
StringBuilder output = new StringBuilder();
output.append(prefix).append("TABLE: ").append(tblName).append("\n");
output.append(prefix).append("TABLE TYPE: ").append(odbcType.toString()).append("\n");
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return output.toString();
+ }
output.append(prefix).append("QUERY: ").append(getOdbcQueryStr()).append("\n");
return output.toString();
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java
index 58b100e..b2c4a61 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java
@@ -65,6 +65,9 @@ import org.apache.doris.thrift.TScanRange;
import org.apache.doris.thrift.TScanRangeLocation;
import org.apache.doris.thrift.TScanRangeLocations;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
@@ -73,9 +76,6 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Range;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -549,11 +549,15 @@ public class OlapScanNode extends ScanNode {
}
@Override
- protected String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
StringBuilder output = new StringBuilder();
output.append(prefix).append("TABLE: ").append(olapTable.getName()).append("\n");
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return output.toString();
+ }
+
if (null != sortColumn) {
output.append(prefix).append("SORT COLUMN: ").append(sortColumn).append("\n");
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapTableSink.java b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapTableSink.java
index af20dad..bb3cf10 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapTableSink.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapTableSink.java
@@ -134,6 +134,9 @@ public class OlapTableSink extends DataSink {
public String getExplainString(String prefix, TExplainLevel explainLevel) {
StringBuilder strBuilder = new StringBuilder();
strBuilder.append(prefix + "OLAP TABLE SINK\n");
+ if (explainLevel == TExplainLevel.BRIEF) {
+ return strBuilder.toString();
+ }
strBuilder.append(prefix + " TUPLE ID: " + tupleDescriptor.getId() + "\n");
strBuilder.append(prefix + " " + DataPartition.RANDOM.getExplainString(explainLevel));
return strBuilder.toString();
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/PlanFragment.java b/fe/fe-core/src/main/java/org/apache/doris/planner/PlanFragment.java
index 1fa6acd..27ada66 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/PlanFragment.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/PlanFragment.java
@@ -246,6 +246,8 @@ public class PlanFragment extends TreeNode<PlanFragment> {
return (dataPartition.getType() != TPartitionType.UNPARTITIONED);
}
+ public PlanFragmentId getId() { return fragmentId; }
+
public PlanFragment getDestFragment() {
if (destNode == null) return null;
return destNode.getFragment();
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/PlanNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/PlanNode.java
index 45b36ae..1f4b4dc 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/PlanNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/PlanNode.java
@@ -30,15 +30,15 @@ import org.apache.doris.thrift.TExplainLevel;
import org.apache.doris.thrift.TPlan;
import org.apache.doris.thrift.TPlanNode;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.math.LongMath;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -113,6 +113,10 @@ abstract public class PlanNode extends TreeNode<PlanNode> {
protected boolean compactData;
protected int numInstances;
+ public String getPlanNodeName() {
+ return planNodeName;
+ }
+
protected PlanNode(PlanNodeId id, ArrayList<TupleId> tupleIds, String planNodeName) {
this.id = id;
this.limit = -1;
@@ -383,7 +387,7 @@ abstract public class PlanNode extends TreeNode<PlanNode> {
* Subclass should override this function.
* Each line should be prefix by detailPrefix.
*/
- protected String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
return "";
}
@@ -632,4 +636,13 @@ abstract public class PlanNode extends TreeNode<PlanNode> {
sb.append(")");
}
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[").append(getId().asInt()).append(": ").append(getPlanNodeName()).append("]");
+ sb.append("\nFragment: ").append(getFragmentId().asInt()).append("]");
+ sb.append("\n").append(getNodeExplainString("", TExplainLevel.BRIEF));
+ return sb.toString();
+ }
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/Planner.java b/fe/fe-core/src/main/java/org/apache/doris/planner/Planner.java
index e173190..6f66cdc 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/Planner.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/Planner.java
@@ -18,6 +18,7 @@
package org.apache.doris.planner;
import org.apache.doris.analysis.Analyzer;
+import org.apache.doris.analysis.ExplainOptions;
import org.apache.doris.analysis.Expr;
import org.apache.doris.analysis.InsertStmt;
import org.apache.doris.analysis.QueryStmt;
@@ -28,6 +29,8 @@ import org.apache.doris.analysis.StatementBase;
import org.apache.doris.analysis.TupleDescriptor;
import org.apache.doris.catalog.PrimitiveType;
import org.apache.doris.common.UserException;
+import org.apache.doris.common.profile.PlanTreeBuilder;
+import org.apache.doris.common.profile.PlanTreePrinter;
import org.apache.doris.rewrite.mvrewrite.MVSelectFailedException;
import org.apache.doris.thrift.TExplainLevel;
import org.apache.doris.thrift.TQueryOptions;
@@ -115,7 +118,22 @@ public class Planner {
/**
* Return combined explain string for all plan fragments.
*/
- public String getExplainString(List<PlanFragment> fragments, TExplainLevel explainLevel) {
+ public String getExplainString(List<PlanFragment> fragments, ExplainOptions explainOptions) {
+ Preconditions.checkNotNull(explainOptions);
+ if (explainOptions.isGraph()) {
+ // print the plan graph
+ PlanTreeBuilder builder = new PlanTreeBuilder(fragments);
+ try {
+ builder.build();
+ } catch (UserException e) {
+ LOG.warn("Failed to build explain plan tree", e);
+ return e.getMessage();
+ }
+ return PlanTreePrinter.printPlanExplanation(builder.getTreeRoot());
+ }
+
+ // print text plan
+ TExplainLevel explainLevel = explainOptions.isVerbose() ? TExplainLevel.VERBOSE : TExplainLevel.NORMAL;
StringBuilder str = new StringBuilder();
for (int i = 0; i < fragments.size(); ++i) {
PlanFragment fragment = fragments.get(i);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/RepeatNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/RepeatNode.java
index e7c3255..b70af9e 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/RepeatNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/RepeatNode.java
@@ -33,11 +33,11 @@ import org.apache.doris.thrift.TPlanNode;
import org.apache.doris.thrift.TPlanNodeType;
import org.apache.doris.thrift.TRepeatNode;
+import org.apache.commons.collections.CollectionUtils;
+
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
-import org.apache.commons.collections.CollectionUtils;
-
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
@@ -182,13 +182,16 @@ public class RepeatNode extends PlanNode {
}
@Override
- protected String getNodeExplainString(String detailPrefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String detailPrefix, TExplainLevel detailLevel) {
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return "";
+ }
StringBuilder output = new StringBuilder();
output.append(detailPrefix + "repeat: repeat ");
output.append(repeatSlotIdList.size() - 1);
output.append(" lines ");
output.append(repeatSlotIdList);
- output.append("\n" );
+ output.append("\n");
if (CollectionUtils.isNotEmpty(outputTupleDesc.getSlots())) {
output.append(detailPrefix + "generate: ");
output.append(outputTupleDesc.getSlots().stream().map(slot -> "`" + slot.getColumn().getName() + "`")
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/SelectNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/SelectNode.java
index 83219af..c2d30bc 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/SelectNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/SelectNode.java
@@ -73,7 +73,10 @@ public class SelectNode extends PlanNode {
}
@Override
- protected String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return "";
+ }
StringBuilder output = new StringBuilder();
if (!conjuncts.isEmpty()) {
output.append(prefix + "predicates: " + getExplainString(conjuncts) + "\n");
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/SetOperationNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/SetOperationNode.java
index 9d2b959..502d4ec 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/SetOperationNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/SetOperationNode.java
@@ -35,9 +35,15 @@ import org.apache.doris.thrift.TIntersectNode;
import org.apache.doris.thrift.TPlanNode;
import org.apache.doris.thrift.TPlanNodeType;
import org.apache.doris.thrift.TUnionNode;
+
+import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -337,7 +343,11 @@ public abstract class SetOperationNode extends PlanNode {
}
@Override
- protected String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return "";
+ }
+
StringBuilder output = new StringBuilder();
// A SetOperationNode may have predicates if a union is set operation inside an inline view,
// and the enclosing select stmt has predicates referring to the inline view.
@@ -346,7 +356,7 @@ public abstract class SetOperationNode extends PlanNode {
}
if (CollectionUtils.isNotEmpty(constExprLists_)) {
output.append(prefix).append("constant exprs: ").append("\n");
- for(List<Expr> exprs : constExprLists_) {
+ for (List<Expr> exprs : constExprLists_) {
output.append(prefix).append(" ").append(exprs.stream().map(Expr::toSql)
.collect(Collectors.joining(" | "))).append("\n");
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/SortNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/SortNode.java
index 97c2c5b..93c139e 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/SortNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/SortNode.java
@@ -31,14 +31,14 @@ import org.apache.doris.thrift.TPlanNodeType;
import org.apache.doris.thrift.TSortInfo;
import org.apache.doris.thrift.TSortNode;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
import java.util.Iterator;
import java.util.List;
@@ -170,7 +170,11 @@ public class SortNode extends PlanNode {
}
@Override
- protected String getNodeExplainString(String detailPrefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String detailPrefix, TExplainLevel detailLevel) {
+ if (detailLevel == TExplainLevel.BRIEF) {
+ return "";
+ }
+
StringBuilder output = new StringBuilder();
output.append(detailPrefix + "order by: ");
Iterator<Expr> expr = info.getOrderingExprs().iterator();
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/StreamLoadScanNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/StreamLoadScanNode.java
index 112fa22..edb935d 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/StreamLoadScanNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/StreamLoadScanNode.java
@@ -39,12 +39,12 @@ import org.apache.doris.thrift.TScanRange;
import org.apache.doris.thrift.TScanRangeLocations;
import org.apache.doris.thrift.TUniqueId;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
@@ -188,7 +188,7 @@ public class StreamLoadScanNode extends LoadScanNode {
public int getNumInstances() { return 1; }
@Override
- protected String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
+ public String getNodeExplainString(String prefix, TExplainLevel detailLevel) {
return "StreamLoadScanNode";
}
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/ShowExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/ShowExecutor.java
index 7cc08bd..189704a 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/ShowExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/ShowExecutor.java
@@ -51,6 +51,7 @@ import org.apache.doris.analysis.ShowPartitionsStmt;
import org.apache.doris.analysis.ShowPluginsStmt;
import org.apache.doris.analysis.ShowProcStmt;
import org.apache.doris.analysis.ShowProcesslistStmt;
+import org.apache.doris.analysis.ShowQueryProfileStmt;
import org.apache.doris.analysis.ShowRepositoriesStmt;
import org.apache.doris.analysis.ShowResourcesStmt;
import org.apache.doris.analysis.ShowRestoreStmt;
@@ -109,10 +110,14 @@ import org.apache.doris.common.proc.ProcNodeInterface;
import org.apache.doris.common.proc.RollupProcDir;
import org.apache.doris.common.proc.SchemaChangeProcDir;
import org.apache.doris.common.proc.TabletsProcDir;
+import org.apache.doris.common.profile.ProfileTreeNode;
+import org.apache.doris.common.profile.ProfileTreePrinter;
import org.apache.doris.common.util.ListComparator;
import org.apache.doris.common.util.LogBuilder;
import org.apache.doris.common.util.LogKey;
import org.apache.doris.common.util.OrderByPair;
+import org.apache.doris.common.util.ProfileManager;
+import org.apache.doris.common.util.RuntimeProfile;
import org.apache.doris.load.DeleteHandler;
import org.apache.doris.load.ExportJob;
import org.apache.doris.load.ExportMgr;
@@ -125,17 +130,19 @@ import org.apache.doris.load.routineload.RoutineLoadJob;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.system.Backend;
import org.apache.doris.system.SystemInfoService;
+import org.apache.doris.thrift.TUnit;
import org.apache.doris.transaction.GlobalTransactionMgr;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import org.apache.commons.lang3.tuple.Triple;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -263,6 +270,8 @@ public class ShowExecutor {
handleShowTransaction();
} else if (stmt instanceof ShowPluginsStmt) {
handleShowPlugins();
+ } else if (stmt instanceof ShowQueryProfileStmt) {
+ handleShowQueryProfile();
} else {
handleEmtpy();
}
@@ -1612,6 +1621,54 @@ public class ShowExecutor {
List<List<String>> rows = Catalog.getCurrentPluginMgr().getPluginShowInfos();
resultSet = new ShowResultSet(pluginsStmt.getMetaData(), rows);
}
+
+ private void handleShowQueryProfile() throws AnalysisException {
+ ShowQueryProfileStmt showStmt = (ShowQueryProfileStmt) stmt;
+ ShowQueryProfileStmt.PathType pathType = showStmt.getPathType();
+ List<List<String>> rows = Lists.newArrayList();
+ switch (pathType) {
+ case QUERY_IDS:
+ rows = ProfileManager.getInstance().getAllQueries();
+ break;
+ case FRAGMETNS: {
+ ProfileTreeNode treeRoot = ProfileManager.getInstance().getFragmentProfileTree(showStmt.getQueryId());
+ if (treeRoot == null) {
+ throw new AnalysisException("Failed to get fragment tree for query: " + showStmt.getQueryId());
+ }
+ List<String> row = Lists.newArrayList(ProfileTreePrinter.printFragmentTree(treeRoot));
+ rows.add(row);
+ break;
+ }
+ case INSTANCES: {
+ List<Triple<String, String, Long>> instanceList
+ = ProfileManager.getInstance().getFragmentInstanceList(showStmt.getQueryId(), showStmt.getFragmentId());
+ if (instanceList == null) {
+ throw new AnalysisException("Failed to get instance list for fragment: " + showStmt.getFragmentId());
+ }
+ for (Triple<String, String, Long> triple : instanceList) {
+ List<String> row = Lists.newArrayList(triple.getLeft(), triple.getMiddle(),
+ RuntimeProfile.printCounter(triple.getRight(), TUnit.TIME_NS));
+ rows.add(row);
+ }
+ break;
+ }
+ case SINGLE_INSTANCE: {
+ ProfileTreeNode treeRoot = ProfileManager.getInstance().getInstanceProfileTree(showStmt.getQueryId(),
+ showStmt.getFragmentId(), showStmt.getInstanceId());
+ if (treeRoot == null) {
+ throw new AnalysisException("Failed to get instance tree for instance: " + showStmt.getInstanceId());
+ }
+ List<String> row = Lists.newArrayList(ProfileTreePrinter.printInstanceTree(treeRoot));
+ rows.add(row);
+ break;
+ }
+ default:
+ break;
+ }
+
+ resultSet = new ShowResultSet(showStmt.getMetaData(), rows);
+ }
+
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
index ff7e349..4a361ec 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
@@ -21,6 +21,7 @@ import org.apache.doris.analysis.Analyzer;
import org.apache.doris.analysis.CreateTableAsSelectStmt;
import org.apache.doris.analysis.DdlStmt;
import org.apache.doris.analysis.EnterStmt;
+import org.apache.doris.analysis.ExplainOptions;
import org.apache.doris.analysis.ExportStmt;
import org.apache.doris.analysis.Expr;
import org.apache.doris.analysis.InsertStmt;
@@ -80,7 +81,6 @@ import org.apache.doris.rewrite.ExprRewriter;
import org.apache.doris.rewrite.mvrewrite.MVSelectFailedException;
import org.apache.doris.rpc.RpcException;
import org.apache.doris.task.LoadEtlTask;
-import org.apache.doris.thrift.TExplainLevel;
import org.apache.doris.thrift.TQueryOptions;
import org.apache.doris.thrift.TQueryType;
import org.apache.doris.thrift.TUniqueId;
@@ -514,9 +514,8 @@ public class StmtExecutor {
private void analyzeAndGenerateQueryPlan(TQueryOptions tQueryOptions) throws UserException {
parsedStmt.analyze(analyzer);
if (parsedStmt instanceof QueryStmt || parsedStmt instanceof InsertStmt) {
- boolean isExplain = parsedStmt.isExplain();
- boolean isVerbose = parsedStmt.isVerbose();
// Apply expr and subquery rewrites.
+ ExplainOptions explainOptions = parsedStmt.getExplainOptions();
boolean reAnalyze = false;
ExprRewriter rewriter = analyzer.getExprRewriter();
@@ -551,7 +550,7 @@ public class StmtExecutor {
if (LOG.isTraceEnabled()) {
LOG.trace("rewrittenStmt: " + parsedStmt.toSql());
}
- if (isExplain) parsedStmt.setIsExplain(isExplain, isVerbose);
+ if (explainOptions != null) parsedStmt.setIsExplain(explainOptions);
}
}
plannerProfile.setQueryAnalysisFinishTime();
@@ -742,7 +741,7 @@ public class StmtExecutor {
QueryDetailQueue.addOrUpdateQueryDetail(queryDetail);
if (queryStmt.isExplain()) {
- String explainString = planner.getExplainString(planner.getFragments(), queryStmt.isVerbose() ? TExplainLevel.VERBOSE: TExplainLevel.NORMAL.NORMAL);
+ String explainString = planner.getExplainString(planner.getFragments(), queryStmt.getExplainOptions());
handleExplainStmt(explainString);
return;
}
@@ -827,7 +826,7 @@ public class StmtExecutor {
}
if (insertStmt.getQueryStmt().isExplain()) {
- String explainString = planner.getExplainString(planner.getFragments(), TExplainLevel.VERBOSE);
+ String explainString = planner.getExplainString(planner.getFragments(), new ExplainOptions(true, false));
handleExplainStmt(explainString);
return;
}
diff --git a/fe/fe-core/src/main/jflex/sql_scanner.flex b/fe/fe-core/src/main/jflex/sql_scanner.flex
index 5abcf97..2f760d3 100644
--- a/fe/fe-core/src/main/jflex/sql_scanner.flex
+++ b/fe/fe-core/src/main/jflex/sql_scanner.flex
@@ -205,6 +205,7 @@ import org.apache.doris.qe.SqlModeHelper;
keywordMap.put("global", new Integer(SqlParserSymbols.KW_GLOBAL));
keywordMap.put("grant", new Integer(SqlParserSymbols.KW_GRANT));
keywordMap.put("grants", new Integer(SqlParserSymbols.KW_GRANTS));
+ keywordMap.put("graph", new Integer(SqlParserSymbols.KW_GRAPH));
keywordMap.put("group", new Integer(SqlParserSymbols.KW_GROUP));
keywordMap.put("grouping", new Integer(SqlParserSymbols.KW_GROUPING));
keywordMap.put("hash", new Integer(SqlParserSymbols.KW_HASH));
@@ -288,6 +289,7 @@ import org.apache.doris.qe.SqlModeHelper;
keywordMap.put("proc", new Integer(SqlParserSymbols.KW_PROC));
keywordMap.put("procedure", new Integer(SqlParserSymbols.KW_PROCEDURE));
keywordMap.put("processlist", new Integer(SqlParserSymbols.KW_PROCESSLIST));
+ keywordMap.put("profile", new Integer(SqlParserSymbols.KW_PROFILE));
keywordMap.put("properties", new Integer(SqlParserSymbols.KW_PROPERTIES));
keywordMap.put("property", new Integer(SqlParserSymbols.KW_PROPERTY));
keywordMap.put("query", new Integer(SqlParserSymbols.KW_QUERY));
diff --git a/fe/fe-core/src/test/java/org/apache/doris/planner/DistributedPlannerTest.java b/fe/fe-core/src/test/java/org/apache/doris/planner/DistributedPlannerTest.java
index 6c480b3..9bf0ed8 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/planner/DistributedPlannerTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/planner/DistributedPlannerTest.java
@@ -19,19 +19,17 @@ package org.apache.doris.planner;
import org.apache.doris.analysis.CreateDbStmt;
import org.apache.doris.analysis.CreateTableStmt;
+import org.apache.doris.analysis.ExplainOptions;
import org.apache.doris.analysis.TupleId;
import org.apache.doris.catalog.Catalog;
import org.apache.doris.common.jmockit.Deencapsulation;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.StmtExecutor;
-import org.apache.doris.thrift.TExplainLevel;
import org.apache.doris.utframe.UtFrameUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
-import mockit.Expectations;
-import mockit.Injectable;
-import mockit.Mocked;
+
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.After;
@@ -44,6 +42,10 @@ import java.util.List;
import java.util.Set;
import java.util.UUID;
+import mockit.Expectations;
+import mockit.Injectable;
+import mockit.Mocked;
+
public class DistributedPlannerTest {
private static String runningDir = "fe/mocked/DemoTest/" + UUID.randomUUID().toString() + "/";
private static ConnectContext ctx;
@@ -134,7 +136,7 @@ public class DistributedPlannerTest {
stmtExecutor.execute();
Planner planner = stmtExecutor.planner();
List<PlanFragment> fragments = planner.getFragments();
- String plan = planner.getExplainString(fragments, TExplainLevel.NORMAL);
+ String plan = planner.getExplainString(fragments, new ExplainOptions(false, false));
Assert.assertEquals(1, StringUtils.countMatches(plan, "INNER JOIN (BROADCAST)"));
sql = "explain select * from db1.tbl1 join [SHUFFLE] db1.tbl2 on tbl1.k1 = tbl2.k3";
@@ -142,7 +144,7 @@ public class DistributedPlannerTest {
stmtExecutor.execute();
planner = stmtExecutor.planner();
fragments = planner.getFragments();
- plan = planner.getExplainString(fragments, TExplainLevel.NORMAL);
+ plan = planner.getExplainString(fragments, new ExplainOptions(false, false));
Assert.assertEquals(1, StringUtils.countMatches(plan, "INNER JOIN (PARTITIONED)"));
}
}
diff --git a/fe/fe-core/src/test/java/org/apache/doris/planner/PlannerTest.java b/fe/fe-core/src/test/java/org/apache/doris/planner/PlannerTest.java
index e54aad6..eb843fc 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/planner/PlannerTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/planner/PlannerTest.java
@@ -17,15 +17,16 @@
package org.apache.doris.planner;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang3.StringUtils;
import org.apache.doris.analysis.CreateDbStmt;
import org.apache.doris.analysis.CreateTableStmt;
+import org.apache.doris.analysis.ExplainOptions;
import org.apache.doris.catalog.Catalog;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.StmtExecutor;
-import org.apache.doris.thrift.TExplainLevel;
import org.apache.doris.utframe.UtFrameUtils;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.BeforeClass;
@@ -78,7 +79,7 @@ public class PlannerTest {
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
List<PlanFragment> fragments1 = planner1.getFragments();
- String plan1 = planner1.getExplainString(fragments1, TExplainLevel.NORMAL);
+ String plan1 = planner1.getExplainString(fragments1, new ExplainOptions(false, false));
Assert.assertEquals(1, StringUtils.countMatches(plan1, "UNION"));
String sql2 = "explain select * from db1.tbl1 where k1='a' and k4=1\n"
+ "union distinct\n"
@@ -103,7 +104,7 @@ public class PlannerTest {
stmtExecutor2.execute();
Planner planner2 = stmtExecutor2.planner();
List<PlanFragment> fragments2 = planner2.getFragments();
- String plan2 = planner2.getExplainString(fragments2, TExplainLevel.NORMAL);
+ String plan2 = planner2.getExplainString(fragments2, new ExplainOptions(false, false));
Assert.assertEquals(4, StringUtils.countMatches(plan2, "UNION"));
// intersect
@@ -119,7 +120,7 @@ public class PlannerTest {
stmtExecutor3.execute();
Planner planner3 = stmtExecutor3.planner();
List<PlanFragment> fragments3 = planner3.getFragments();
- String plan3 = planner3.getExplainString(fragments3, TExplainLevel.NORMAL);
+ String plan3 = planner3.getExplainString(fragments3, new ExplainOptions(false, false));
Assert.assertEquals(1, StringUtils.countMatches(plan3, "INTERSECT"));
String sql4 = "explain select * from db1.tbl1 where k1='a' and k4=1\n"
+ "intersect distinct\n"
@@ -145,7 +146,7 @@ public class PlannerTest {
stmtExecutor4.execute();
Planner planner4 = stmtExecutor4.planner();
List<PlanFragment> fragments4 = planner4.getFragments();
- String plan4 = planner4.getExplainString(fragments4, TExplainLevel.NORMAL);
+ String plan4 = planner4.getExplainString(fragments4, new ExplainOptions(false, false));
Assert.assertEquals(3, StringUtils.countMatches(plan4, "INTERSECT"));
// except
@@ -161,7 +162,7 @@ public class PlannerTest {
stmtExecutor5.execute();
Planner planner5 = stmtExecutor5.planner();
List<PlanFragment> fragments5 = planner5.getFragments();
- String plan5 = planner5.getExplainString(fragments5, TExplainLevel.NORMAL);
+ String plan5 = planner5.getExplainString(fragments5, new ExplainOptions(false, false));
Assert.assertEquals(1, StringUtils.countMatches(plan5, "EXCEPT"));
String sql6 = "select * from db1.tbl1 where k1='a' and k4=1\n"
@@ -176,7 +177,7 @@ public class PlannerTest {
stmtExecutor6.execute();
Planner planner6 = stmtExecutor6.planner();
List<PlanFragment> fragments6 = planner6.getFragments();
- String plan6 = planner6.getExplainString(fragments6, TExplainLevel.NORMAL);
+ String plan6 = planner6.getExplainString(fragments6, new ExplainOptions(false, false));
Assert.assertEquals(1, StringUtils.countMatches(plan6, "EXCEPT"));
String sql7 = "select * from db1.tbl1 where k1='a' and k4=1\n"
@@ -191,7 +192,7 @@ public class PlannerTest {
stmtExecutor7.execute();
Planner planner7 = stmtExecutor7.planner();
List<PlanFragment> fragments7 = planner7.getFragments();
- String plan7 = planner7.getExplainString(fragments7, TExplainLevel.NORMAL);
+ String plan7 = planner7.getExplainString(fragments7, new ExplainOptions(false, false));
Assert.assertEquals(1, StringUtils.countMatches(plan7, "EXCEPT"));
// mixed
@@ -207,7 +208,7 @@ public class PlannerTest {
stmtExecutor8.execute();
Planner planner8 = stmtExecutor8.planner();
List<PlanFragment> fragments8 = planner8.getFragments();
- String plan8 = planner8.getExplainString(fragments8, TExplainLevel.NORMAL);
+ String plan8 = planner8.getExplainString(fragments8, new ExplainOptions(false, false));
Assert.assertEquals(1, StringUtils.countMatches(plan8, "UNION"));
Assert.assertEquals(1, StringUtils.countMatches(plan8, "INTERSECT"));
Assert.assertEquals(1, StringUtils.countMatches(plan8, "EXCEPT"));
@@ -236,7 +237,7 @@ public class PlannerTest {
stmtExecutor9.execute();
Planner planner9 = stmtExecutor9.planner();
List<PlanFragment> fragments9 = planner9.getFragments();
- String plan9 = planner9.getExplainString(fragments9, TExplainLevel.NORMAL);
+ String plan9 = planner9.getExplainString(fragments9, new ExplainOptions(false, false));
Assert.assertEquals(2, StringUtils.countMatches(plan9, "UNION"));
Assert.assertEquals(3, StringUtils.countMatches(plan9, "INTERSECT"));
Assert.assertEquals(2, StringUtils.countMatches(plan9, "EXCEPT"));
@@ -325,7 +326,7 @@ public class PlannerTest {
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
List<PlanFragment> fragments1 = planner1.getFragments();
- String plan1 = planner1.getExplainString(fragments1, TExplainLevel.VERBOSE);
+ String plan1 = planner1.getExplainString(fragments1, new ExplainOptions(false, false));
Assert.assertEquals(3, StringUtils.countMatches(plan1, "nullIndicatorBit=0"));
}
diff --git a/fe/fe-core/src/test/java/org/apache/doris/utframe/DorisAssert.java b/fe/fe-core/src/test/java/org/apache/doris/utframe/DorisAssert.java
index d3d8780..d54e10a 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/utframe/DorisAssert.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/utframe/DorisAssert.java
@@ -23,6 +23,7 @@ import org.apache.doris.analysis.CreateDbStmt;
import org.apache.doris.analysis.CreateMaterializedViewStmt;
import org.apache.doris.analysis.CreateTableStmt;
import org.apache.doris.analysis.DropTableStmt;
+import org.apache.doris.analysis.ExplainOptions;
import org.apache.doris.analysis.SqlParser;
import org.apache.doris.analysis.SqlScanner;
import org.apache.doris.analysis.StatementBase;
@@ -36,7 +37,6 @@ import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.QueryState;
import org.apache.doris.qe.StmtExecutor;
import org.apache.doris.system.SystemInfoService;
-import org.apache.doris.thrift.TExplainLevel;
import org.apache.commons.lang.StringUtils;
import org.junit.Assert;
@@ -169,7 +169,7 @@ public class DorisAssert {
}
}
Planner planner = stmtExecutor.planner();
- String explainString = planner.getExplainString(planner.getFragments(), TExplainLevel.NORMAL);
+ String explainString = planner.getExplainString(planner.getFragments(), new ExplainOptions(false, false));
System.out.println(explainString);
return explainString;
}
diff --git a/fe/fe-core/src/test/java/org/apache/doris/utframe/UtFrameUtils.java b/fe/fe-core/src/test/java/org/apache/doris/utframe/UtFrameUtils.java
index 8f5ed06..ab34e20 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/utframe/UtFrameUtils.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/utframe/UtFrameUtils.java
@@ -18,6 +18,7 @@
package org.apache.doris.utframe;
import org.apache.doris.analysis.Analyzer;
+import org.apache.doris.analysis.ExplainOptions;
import org.apache.doris.analysis.SqlParser;
import org.apache.doris.analysis.SqlScanner;
import org.apache.doris.analysis.StatementBase;
@@ -35,7 +36,6 @@ import org.apache.doris.qe.QueryState;
import org.apache.doris.qe.StmtExecutor;
import org.apache.doris.system.Backend;
import org.apache.doris.system.SystemInfoService;
-import org.apache.doris.thrift.TExplainLevel;
import org.apache.doris.thrift.TNetworkAddress;
import org.apache.doris.utframe.MockedBackendFactory.DefaultBeThriftServiceImpl;
import org.apache.doris.utframe.MockedBackendFactory.DefaultHeartbeatServiceImpl;
@@ -44,12 +44,12 @@ import org.apache.doris.utframe.MockedFrontend.EnvVarNotSetException;
import org.apache.doris.utframe.MockedFrontend.FeStartException;
import org.apache.doris.utframe.MockedFrontend.NotInitException;
-import org.apache.commons.io.FileUtils;
-
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
+import org.apache.commons.io.FileUtils;
+
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
@@ -215,7 +215,7 @@ public class UtFrameUtils {
stmtExecutor.execute();
if (ctx.getState().getStateType() != QueryState.MysqlStateType.ERR) {
Planner planner = stmtExecutor.planner();
- return planner.getExplainString(planner.getFragments(), TExplainLevel.NORMAL);
+ return planner.getExplainString(planner.getFragments(), new ExplainOptions(false, false));
} else {
return ctx.getState().getErrorMessage();
}
diff --git a/fe/pom.xml b/fe/pom.xml
index 4ceb929..9419c87 100644
--- a/fe/pom.xml
+++ b/fe/pom.xml
@@ -671,6 +671,13 @@ under the License.
<scope>provided</scope>
</dependency>
+ <!-- https://mvnrepository.com/artifact/hu.webarticum/tree-printer -->
+ <dependency>
+ <groupId>hu.webarticum</groupId>
+ <artifactId>tree-printer</artifactId>
+ <version>1.2</version>
+ </dependency>
+
</dependencies>
</dependencyManagement>
diff --git a/gensrc/thrift/Types.thrift b/gensrc/thrift/Types.thrift
index b872eb2..34ef5a2 100644
--- a/gensrc/thrift/Types.thrift
+++ b/gensrc/thrift/Types.thrift
@@ -189,6 +189,7 @@ enum TStmtType {
// level of verboseness for "explain" output
// TODO: should this go somewhere else?
enum TExplainLevel {
+ BRIEF,
NORMAL,
VERBOSE
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org