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 2022/07/28 07:48:08 UTC

[doris] branch master updated: [improvement](profile) add json profile and add session context (#11279)

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/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 97874dd125 [improvement](profile) add json profile and add session context (#11279)
97874dd125 is described below

commit 97874dd125b34b6229cc529ba1a9b80fc8c6d7be
Author: miswujian <38...@users.noreply.github.com>
AuthorDate: Thu Jul 28 15:48:00 2022 +0800

    [improvement](profile) add json profile and add session context (#11279)
    
    1. Add a new session varible "session_context"
    2. support export profile in json format
---
 .../org/apache/doris/analysis/SchemaTableType.java |   1 +
 .../org/apache/doris/catalog/PrimitiveType.java    |   3 +-
 .../apache/doris/common/profile/CounterNode.java   |   4 +
 .../doris/common/profile/ProfileTreeNode.java      |  36 ++++
 .../doris/common/profile/ProfileTreePrinter.java   |  39 +++-
 .../apache/doris/common/util/ProfileManager.java   |  17 +-
 .../apache/doris/common/util/RuntimeProfile.java   |   2 +-
 .../apache/doris/httpv2/rest/MetaInfoAction.java   |   2 +-
 .../httpv2/rest/manager/QueryProfileAction.java    | 212 ++++++++++++++-------
 .../java/org/apache/doris/mysql/MysqlCommand.java  |   1 +
 .../java/org/apache/doris/qe/SessionVariable.java  |  38 ++++
 .../java/org/apache/doris/qe/StmtExecutor.java     |   1 +
 .../doris/common/util/RuntimeProfileTest.java      |   2 +-
 13 files changed, 284 insertions(+), 74 deletions(-)

diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
index 127285bcb5..d4f8e68f54 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/SchemaTableType.java
@@ -68,6 +68,7 @@ public enum SchemaTableType {
     SCH_INVALID("NULL", "NULL", TSchemaTableType.SCH_INVALID);
     private static final String dbName = "INFORMATION_SCHEMA";
     private static SelectList fullSelectLists;
+
     static {
         fullSelectLists = new SelectList();
         fullSelectLists.addItem(SelectListItem.createStarItem(null));
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/PrimitiveType.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/PrimitiveType.java
index 240ecc037a..7dd410410a 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/PrimitiveType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/PrimitiveType.java
@@ -92,6 +92,7 @@ public enum PrimitiveType {
     }
 
     private static ImmutableSetMultimap<PrimitiveType, PrimitiveType> implicitCastMap;
+
     static {
         ImmutableSetMultimap.Builder<PrimitiveType, PrimitiveType> builder = ImmutableSetMultimap.builder();
         // Nulltype
@@ -1107,7 +1108,7 @@ public enum PrimitiveType {
             case DATETIMEV2: {
                 if (isTimeType) {
                     return MysqlColType.MYSQL_TYPE_TIME;
-                }  else {
+                } else {
                     return MysqlColType.MYSQL_TYPE_DATETIME;
                 }
             }
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
index 86f2d1a010..5934f05f05 100644
--- 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
@@ -27,6 +27,10 @@ public class CounterNode extends TreeNode<CounterNode> {
         counter = Pair.create(key, value);
     }
 
+    public Pair<String, String> getCounter() {
+        return counter;
+    }
+
     public String toTree(int indent) {
         StringBuilder sb = new StringBuilder();
         sb.append(debugString(indent));
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
index 45cf25d603..c6ef8b5c82 100644
--- 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
@@ -21,6 +21,8 @@ import org.apache.doris.common.TreeNode;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
 
 import java.util.List;
 
@@ -155,6 +157,40 @@ public class ProfileTreeNode extends TreeNode<ProfileTreeNode> {
         return sb.toString();
     }
 
+    public JSONObject debugStringInJson(ProfileTreePrinter.PrintLevel level, String nodeLevel) {
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("id", nodeLevel);
+        JSONObject title = new JSONObject();
+        if (!id.equals(ProfileTreeBuilder.UNKNOWN_ID)) {
+            title.put("id", id);
+        }
+        title.put("name", name);
+        jsonObject.put("title", title);
+        if (level == ProfileTreePrinter.PrintLevel.FRAGMENT) {
+            jsonObject.put("fragment", fragmentId);
+            JSONArray labels = new JSONArray();
+            if (!Strings.isNullOrEmpty(maxInstanceActiveTime)) {
+                JSONObject label = new JSONObject();
+                label.put("name", "MaxActiveTime");
+                label.put("value", maxInstanceActiveTime);
+                labels.add(label);
+            }
+            jsonObject.put("labels", labels);
+        }
+        if (level == ProfileTreePrinter.PrintLevel.INSTANCE) {
+            jsonObject.put("active", activeTime);
+            jsonObject.put("non-child", nonChild);
+            JSONArray counters = new JSONArray();
+            for (CounterNode node : counterNode.getChildren()) {
+                JSONObject counter = new JSONObject();
+                counter.put(node.getCounter().first, node.getCounter().second);
+                counters.add(counter);
+            }
+            jsonObject.put("counters", counters);
+        }
+        return jsonObject;
+    }
+
     private String printIndent(int indent) {
         String res = "";
         for (int i = 0; i < indent; i++) {
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
index 19d2f6a62f..c09a764d73 100644
--- 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
@@ -20,12 +20,14 @@ package org.apache.doris.common.profile;
 import hu.webarticum.treeprinter.BorderTreeNodeDecorator;
 import hu.webarticum.treeprinter.SimpleTreeNode;
 import hu.webarticum.treeprinter.TraditionalTreePrinter;
+import org.apache.commons.lang3.StringUtils;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
 
 public class ProfileTreePrinter {
 
-    public static enum PrintLevel {
-        FRAGMENT,
-        INSTANCE
+    public enum PrintLevel {
+        FRAGMENT, INSTANCE
     }
 
     // Fragment tree only print the entire query plan tree with node name
@@ -52,4 +54,35 @@ public class ProfileTreePrinter {
         }
         return node;
     }
+
+    public static JSONObject printFragmentTreeInJson(ProfileTreeNode root, ProfileTreePrinter.PrintLevel level) {
+        JSONObject object = new JSONObject();
+        JSONArray jsonNodes = new JSONArray();
+        JSONArray edges = new JSONArray();
+        object.put("nodes", jsonNodes);
+        object.put("edges", edges);
+        buildNodeInJson(root, level, "", "", jsonNodes, edges);
+        return object;
+    }
+
+    private static void buildNodeInJson(ProfileTreeNode profileNode, PrintLevel level, String sourceNodeId,
+            String targetNodeId, JSONArray jsonNodes, JSONArray edges) {
+        boolean isFrist = false;
+        if (StringUtils.isBlank(sourceNodeId)) {
+            isFrist = true;
+            targetNodeId = "1";
+        }
+        jsonNodes.add(profileNode.debugStringInJson(level, targetNodeId));
+        int i = 0;
+        for (ProfileTreeNode child : profileNode.getChildren()) {
+            buildNodeInJson(child, level, targetNodeId, targetNodeId + i++, jsonNodes, edges);
+        }
+        if (!isFrist) {
+            JSONObject edge = new JSONObject();
+            edge.put("id", "e" + targetNodeId);
+            edge.put("source", sourceNodeId);
+            edge.put("target", targetNodeId);
+            edges.add(edge);
+        }
+    }
 }
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 4f1a705920..7223169122 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
@@ -66,6 +66,7 @@ public class ProfileManager {
     public static final String DEFAULT_DB = "Default Db";
     public static final String SQL_STATEMENT = "Sql Statement";
     public static final String IS_CACHED = "Is Cached";
+    public static final String TRACE_ID = "Trace ID";
 
     public enum ProfileType {
         QUERY,
@@ -74,7 +75,7 @@ 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));
+                    START_TIME, END_TIME, TOTAL_TIME, QUERY_STATE, TRACE_ID));
 
     private class ProfileElement {
         public Map<String, String> infoStrings = Maps.newHashMap();
@@ -287,4 +288,18 @@ public class ProfileManager {
             readLock.unlock();
         }
     }
+
+    public String getQueryIdByTraceId(String traceId) {
+        readLock.lock();
+        try {
+            for (Map.Entry<String, ProfileElement> entry : queryIdToProfileMap.entrySet()) {
+                if (entry.getValue().infoStrings.getOrDefault(TRACE_ID, "").equals(traceId)) {
+                    return entry.getKey();
+                }
+            }
+            return "";
+        } finally {
+            readLock.unlock();
+        }
+    }
 }
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 b81db22f52..82402c294d 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
@@ -466,7 +466,7 @@ public class RuntimeProfile {
     // Returns the value to which the specified key is mapped;
     // or null if this map contains no mapping for the key.
     public String getInfoString(String key) {
-        return infoStrings.get(key);
+        return infoStrings.getOrDefault(key, "");
     }
 
     public Map<String, String> getInfoStrings() {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/MetaInfoAction.java b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/MetaInfoAction.java
index 874c4be42b..9cad674763 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/MetaInfoAction.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/MetaInfoAction.java
@@ -227,7 +227,7 @@ public class MetaInfoAction extends RestBaseController {
         Table tbl;
         try {
             db = Env.getCurrentInternalCatalog().getDbOrMetaException(fullDbName);
-            tbl = db.getTableOrMetaException(tblName, Table.TableType.OLAP);
+            tbl = db.getTableOrMetaException(tblName);
         } catch (MetaNotFoundException e) {
             return ResponseEntityBuilder.okWithCommonError(e.getMessage());
         }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/QueryProfileAction.java b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/QueryProfileAction.java
index b5f796159b..3fb21205e2 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/QueryProfileAction.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/manager/QueryProfileAction.java
@@ -39,6 +39,9 @@ import com.google.gson.JsonParser;
 import com.google.gson.reflect.TypeToken;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.jetbrains.annotations.NotNull;
+import org.json.simple.JSONObject;
+import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
@@ -195,8 +198,9 @@ public class QueryProfileAction extends RestBaseController {
                 }
             }
         } else {
-            List<List<String>> queries = ProfileManager.getInstance().getAllQueries().stream()
-                    .filter(query -> query.get(0).equals(queryId)).collect(Collectors.toList());
+            List<List<String>> queries =
+                    ProfileManager.getInstance().getAllQueries().stream().filter(query -> query.get(0).equals(queryId))
+                            .collect(Collectors.toList());
             if (!queries.isEmpty()) {
                 querySql.put("sql", queries.get(0).get(3));
             }
@@ -204,62 +208,97 @@ public class QueryProfileAction extends RestBaseController {
         return ResponseEntityBuilder.ok(querySql);
     }
 
-    // Returns the text profile for the specified query id.
-    @RequestMapping(path = "/profile/text/{query_id}", method = RequestMethod.GET)
+    /**
+     * Returns the text profile for the specified query id.
+     * There are 3 formats:
+     * 1. Text: return the entire profile of the specified query id
+     * eg: {"profile": "text_xxx"}
+     * <p>
+     * 2. Graph: return the profile in ascii graph. If fragmentId and instanceId are specified, it will
+     * return the instance profile, otherwise, it will return the fragment profile.
+     * eg: {"profile" : "graph_xxx"}
+     * <p>
+     * 3. Json: return the profile in json. If fragmentId and instanceId are specified, it will
+     * return the instance profile, otherwise, it will return the fragment profile.
+     * Json format is mainly used for front-end UI drawing.
+     * eg: {"profile" : "json_xxx"}
+     */
+    @RequestMapping(path = "/profile/{format}/{query_id}", method = RequestMethod.GET)
     public Object queryProfileText(HttpServletRequest request, HttpServletResponse response,
-                                   @PathVariable("query_id") String queryId,
-                                   @RequestParam(value = IS_ALL_NODE_PARA, required = false, defaultValue = "true")
-                                           boolean isAllNode) {
+            @PathVariable("format") String format, @PathVariable("query_id") String queryId,
+            @RequestParam(value = FRAGMENT_ID, required = false) String fragmentId,
+            @RequestParam(value = INSTANCE_ID, required = false) String instanceId,
+            @RequestParam(value = IS_ALL_NODE_PARA, required = false, defaultValue = "true") boolean isAllNode) {
+        executeCheckPassword(request, response);
+        checkGlobalAuth(ConnectContext.get().getCurrentUserIdentity(), PrivPredicate.ADMIN);
+
+        if (format.equals("text")) {
+            return getTextProfile(request, queryId, isAllNode);
+        } else if (format.equals("graph")) {
+            return getGraphProfile(request, queryId, fragmentId, instanceId, isAllNode);
+        } else if (format.equals("json")) {
+            return getJsonProfile(request, queryId, fragmentId, instanceId, isAllNode);
+        } else {
+            return ResponseEntityBuilder.badRequest("Invalid profile format: " + format);
+        }
+    }
+
+    /**
+     * Get query id by trace id
+     *
+     * @param request
+     * @param response
+     * @param traceId
+     * @param isAllNode
+     * @return
+     */
+    @RequestMapping(path = "/trace_id/{trace_id}", method = RequestMethod.GET)
+    public Object getQueryIdByTraceId(HttpServletRequest request, HttpServletResponse response,
+            @PathVariable("trace_id") String traceId,
+            @RequestParam(value = IS_ALL_NODE_PARA, required = false, defaultValue = "true") boolean isAllNode) {
         executeCheckPassword(request, response);
         checkGlobalAuth(ConnectContext.get().getCurrentUserIdentity(), PrivPredicate.ADMIN);
 
-        Map<String, String> profileMap = Maps.newHashMap();
         if (isAllNode) {
-            String httpPath = "/rest/v2/manager/query/profile/text/" + queryId;
-            ImmutableMap<String, String> arguments = ImmutableMap.<String, String>builder()
-                    .put(IS_ALL_NODE_PARA, "false").build();
-            List<String> dataList = requestAllFe(httpPath, arguments, request.getHeader(NodeAction.AUTHORIZATION));
-            if (!dataList.isEmpty()) {
+            String httpPath = "/rest/v2/manager/query/trace_id/" + traceId;
+            ImmutableMap<String, String> arguments =
+                    ImmutableMap.<String, String>builder().put(IS_ALL_NODE_PARA, "false").build();
+            List<Pair<String, Integer>> frontends = HttpUtils.getFeList();
+            ImmutableMap<String, String> header = ImmutableMap.<String, String>builder()
+                    .put(NodeAction.AUTHORIZATION, request.getHeader(NodeAction.AUTHORIZATION)).build();
+            for (Pair<String, Integer> ipPort : frontends) {
+                String url = HttpUtils.concatUrl(ipPort, httpPath, arguments);
                 try {
-                    String profile =
-                            JsonParser.parseString(dataList.get(0)).getAsJsonObject().get("profile").getAsString();
-                    profileMap.put("profile", profile);
-                    return ResponseEntityBuilder.ok(profileMap);
+                    String responseJson = HttpUtils.doGet(url, header);
+                    int code = JsonParser.parseString(responseJson).getAsJsonObject().get("code").getAsInt();
+                    if (code == HttpUtils.REQUEST_SUCCESS_CODE) {
+                        return responseJson;
+                    }
                 } catch (Exception e) {
-                    LOG.warn("parse profile text error: {}", dataList.get(0), e);
+                    LOG.warn(e);
                 }
             }
         } else {
-            String profile = ProfileManager.getInstance().getProfile(queryId);
-            if (!Strings.isNullOrEmpty(profile)) {
-                profileMap.put("profile", profile);
+            String queryId = ProfileManager.getInstance().getQueryIdByTraceId(traceId);
+            if (Strings.isNullOrEmpty(queryId)) {
+                return ResponseEntityBuilder.badRequest("Not found");
             }
+            return ResponseEntityBuilder.ok(queryId);
         }
-        return ResponseEntityBuilder.ok(profileMap);
+        return ResponseEntityBuilder.badRequest("not found query id");
     }
 
-    // Returns the fragments and instances for the specified query id.
-    // [
-    //   {
-    //     "fragment_id":"",
-    //     "time":"",
-    //     "instance_id":[
-    //       ""
-    //     ]
-    //   }
-    // ]
     @RequestMapping(path = "/profile/fragments/{query_id}", method = RequestMethod.GET)
     public Object fragments(HttpServletRequest request, HttpServletResponse response,
-                            @PathVariable("query_id") String queryId,
-                            @RequestParam(value = IS_ALL_NODE_PARA, required = false, defaultValue = "true")
-                                    boolean isAllNode) {
+            @PathVariable("query_id") String queryId,
+            @RequestParam(value = IS_ALL_NODE_PARA, required = false, defaultValue = "true") boolean isAllNode) {
         executeCheckPassword(request, response);
         checkGlobalAuth(ConnectContext.get().getCurrentUserIdentity(), PrivPredicate.ADMIN);
 
         if (isAllNode) {
             String httpPath = "/rest/v2/manager/query/profile/fragments/" + queryId;
-            ImmutableMap<String, String> arguments = ImmutableMap.<String, String>builder()
-                    .put(IS_ALL_NODE_PARA, "false").build();
+            ImmutableMap<String, String> arguments =
+                    ImmutableMap.<String, String>builder().put(IS_ALL_NODE_PARA, "false").build();
             List<Pair<String, Integer>> frontends = HttpUtils.getFeList();
             ImmutableMap<String, String> header = ImmutableMap.<String, String>builder()
                     .put(NodeAction.AUTHORIZATION, request.getHeader(NodeAction.AUTHORIZATION)).build();
@@ -285,37 +324,27 @@ public class QueryProfileAction extends RestBaseController {
         return ResponseEntityBuilder.badRequest("not found query id");
     }
 
-    // Returns the graph profile for the specified query id.
-    @RequestMapping(path = "/profile/graph/{query_id}", method = RequestMethod.GET)
-    public Object queryProfileGraph(HttpServletRequest request, HttpServletResponse response,
-                                    @PathVariable("query_id") String queryId,
-                                    @RequestParam(value = FRAGMENT_ID, required = false) String fragmentId,
-                                    @RequestParam(value = INSTANCE_ID, required = false) String instanceId,
-                                    @RequestParam(value = IS_ALL_NODE_PARA, required = false, defaultValue = "true")
-                                            boolean isAllNode) {
-        executeCheckPassword(request, response);
-        checkGlobalAuth(ConnectContext.get().getCurrentUserIdentity(), PrivPredicate.ADMIN);
+    @NotNull
+    private ResponseEntity getTextProfile(HttpServletRequest request, String queryId, boolean isAllNode) {
+        Map<String, String> profileMap = Maps.newHashMap();
+        if (isAllNode) {
+            return getProfileFromAllFrontends(request, "text", queryId, "", "");
+        } else {
+            String profile = ProfileManager.getInstance().getProfile(queryId);
+            if (!Strings.isNullOrEmpty(profile)) {
+                profileMap.put("profile", profile);
+            }
+        }
+        return ResponseEntityBuilder.ok(profileMap);
+    }
 
+    @NotNull
+    private ResponseEntity getGraphProfile(HttpServletRequest request, String queryId, String fragmentId,
+            String instanceId, boolean isAllNode) {
         Map<String, String> graph = Maps.newHashMap();
         List<String> results;
-
         if (isAllNode) {
-            String httpPath = "/rest/v2/manager/query/profile/graph/" + queryId;
-            Map<String, String> arguments = Maps.newHashMap();
-            arguments.put(FRAGMENT_ID, fragmentId);
-            arguments.put(INSTANCE_ID, instanceId);
-            arguments.put(IS_ALL_NODE_PARA, "false");
-            List<String> dataList = requestAllFe(httpPath, arguments, request.getHeader(NodeAction.AUTHORIZATION));
-            if (!dataList.isEmpty()) {
-                try {
-                    String profileGraph =
-                            JsonParser.parseString(dataList.get(0)).getAsJsonObject().get("graph").getAsString();
-                    graph.put("graph", profileGraph);
-                    return ResponseEntityBuilder.ok(graph);
-                } catch (Exception e) {
-                    LOG.warn("parse profile graph error: {}", dataList.get(0), e);
-                }
-            }
+            return getProfileFromAllFrontends(request, "graph", queryId, fragmentId, instanceId);
         } else {
             try {
                 if (Strings.isNullOrEmpty(fragmentId) || Strings.isNullOrEmpty(instanceId)) {
@@ -328,10 +357,61 @@ public class QueryProfileAction extends RestBaseController {
                 }
                 graph.put("graph", results.get(0));
             } catch (Exception e) {
-                LOG.warn("get profile graph error, queryId:{}, fragementId:{}, instanceId:{}",
-                        queryId, fragmentId, instanceId, e);
+                LOG.warn("get profile graph error, queryId:{}, fragementId:{}, instanceId:{}", queryId, fragmentId,
+                        instanceId, e);
+            }
+        }
+        return ResponseEntityBuilder.ok(graph);
+    }
+
+    @NotNull
+    private ResponseEntity getJsonProfile(HttpServletRequest request, String queryId, String fragmentId,
+            String instanceId, boolean isAllNode) {
+        Map<String, String> graph = Maps.newHashMap();
+        if (isAllNode) {
+            return getProfileFromAllFrontends(request, "json", queryId, fragmentId, instanceId);
+        } else {
+            try {
+                JSONObject json;
+                if (Strings.isNullOrEmpty(fragmentId) || Strings.isNullOrEmpty(instanceId)) {
+                    ProfileTreeNode treeRoot = ProfileManager.getInstance().getFragmentProfileTree(queryId, queryId);
+                    json = ProfileTreePrinter.printFragmentTreeInJson(treeRoot, ProfileTreePrinter.PrintLevel.FRAGMENT);
+                } else {
+                    ProfileTreeNode treeRoot = ProfileManager.getInstance()
+                            .getInstanceProfileTree(queryId, queryId, fragmentId, instanceId);
+                    json = ProfileTreePrinter.printFragmentTreeInJson(treeRoot, ProfileTreePrinter.PrintLevel.INSTANCE);
+                }
+                graph.put("profile", json.toJSONString());
+            } catch (Exception e) {
+                LOG.warn("get profile graph error, queryId:{}, fragementId:{}, instanceId:{}", queryId, fragmentId,
+                        instanceId, e);
             }
         }
         return ResponseEntityBuilder.ok(graph);
     }
+
+    @NotNull
+    private ResponseEntity getProfileFromAllFrontends(HttpServletRequest request, String format, String queryId,
+            String fragmentId, String instanceId) {
+        String httpPath = "/rest/v2/manager/query/profile/" + format + "/" + queryId;
+        ImmutableMap.Builder<String, String> builder =
+                ImmutableMap.<String, String>builder().put(IS_ALL_NODE_PARA, "false");
+        if (!Strings.isNullOrEmpty(fragmentId)) {
+            builder.put(FRAGMENT_ID, fragmentId);
+        }
+        if (!Strings.isNullOrEmpty(instanceId)) {
+            builder.put(INSTANCE_ID, instanceId);
+        }
+        List<String> dataList = requestAllFe(httpPath, builder.build(), request.getHeader(NodeAction.AUTHORIZATION));
+        Map<String, String> result = Maps.newHashMap();
+        if (!dataList.isEmpty()) {
+            try {
+                String profile = JsonParser.parseString(dataList.get(0)).getAsJsonObject().get("profile").getAsString();
+                result.put("profile", profile);
+            } catch (Exception e) {
+                return ResponseEntityBuilder.badRequest(e.getMessage());
+            }
+        }
+        return ResponseEntityBuilder.ok(result);
+    }
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlCommand.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlCommand.java
index 69c13f15a2..f8a03029d5 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlCommand.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlCommand.java
@@ -51,6 +51,7 @@ public enum MysqlCommand {
     COM_RESET_CONNECTION("COM_RESET_CONNECTION", 31);
 
     private static Map<Integer, MysqlCommand> codeMap = Maps.newHashMap();
+
     static {
         EnumSet<MysqlCommand> enumSet = EnumSet.allOf(MysqlCommand.class);
         for (MysqlCommand command : enumSet) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
index bccfed14b9..6ceb826332 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
@@ -24,6 +24,7 @@ import org.apache.doris.qe.VariableMgr.VarAttr;
 import org.apache.doris.thrift.TQueryOptions;
 import org.apache.doris.thrift.TResourceLimit;
 
+import com.google.common.base.Strings;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.json.simple.JSONObject;
@@ -177,6 +178,8 @@ public class SessionVariable implements Serializable, Writable {
 
     public static final String ENABLE_PARALLEL_OUTFILE = "enable_parallel_outfile";
 
+    public static final String ENABLE_LATERAL_VIEW = "enable_lateral_view";
+
     public static final String SQL_QUOTE_SHOW_CREATE = "sql_quote_show_create";
 
     public static final String RETURN_OBJECT_DATA_AS_BINARY = "return_object_data_as_binary";
@@ -200,6 +203,8 @@ public class SessionVariable implements Serializable, Writable {
     public static final String ENABLE_REMOVE_NO_CONJUNCTS_RUNTIME_FILTER =
             "enable_remove_no_conjuncts_runtime_filter_policy";
 
+    static final String SESSION_CONTEXT = "session_context";
+
     // session origin value
     public Map<Field, String> sessionOriginValue = new HashMap<Field, String>();
     // check stmt is or not [select /*+ SET_VAR(...)*/ ...]
@@ -476,6 +481,7 @@ public class SessionVariable implements Serializable, Writable {
     @VariableMgr.VarAttr(name = BLOCK_ENCRYPTION_MODE)
     private String blockEncryptionMode = "";
 
+
     @VariableMgr.VarAttr(name = ENABLE_PROJECTION)
     private boolean enableProjection = true;
 
@@ -497,6 +503,14 @@ public class SessionVariable implements Serializable, Writable {
     @VariableMgr.VarAttr(name = ENABLE_REMOVE_NO_CONJUNCTS_RUNTIME_FILTER)
     public boolean enableRemoveNoConjunctsRuntimeFilterPolicy = false;
 
+    /**
+     * The client can pass some special information by setting this session variable in the format: "k1:v1;k2:v2".
+     * For example, trace_id can be passed to trace the query request sent by the user.
+     * set session_context="trace_id:1234565678";
+     */
+    @VariableMgr.VarAttr(name = SESSION_CONTEXT, needForward = true)
+    public String sessionContext = "";
+
     public String getBlockEncryptionMode() {
         return blockEncryptionMode;
     }
@@ -1263,4 +1277,28 @@ public class SessionVariable implements Serializable, Writable {
         return queryOptions;
     }
 
+    /**
+     * The sessionContext is as follows:
+     * "k1:v1;k2:v2;..."
+     * Here we want to get value with key named "trace_id",
+     * Return empty string is not found.
+     *
+     * @return
+     */
+    public String getTraceId() {
+        if (Strings.isNullOrEmpty(sessionContext)) {
+            return "";
+        }
+        String[] parts = sessionContext.split(";");
+        for (String part : parts) {
+            String[] innerParts = part.split(":");
+            if (innerParts.length != 2) {
+                continue;
+            }
+            if (innerParts[0].equals("trace_id")) {
+                return innerParts[1];
+            }
+        }
+        return "";
+    }
 }
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 45f9ff3529..5bfa5fa45c 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
@@ -238,6 +238,7 @@ public class StmtExecutor implements ProfileWriter {
             summaryProfile.addInfoString(ProfileManager.SQL_STATEMENT, originStmt.originStmt);
             summaryProfile.addInfoString(ProfileManager.IS_CACHED, isCached ? "Yes" : "No");
 
+            summaryProfile.addInfoString(ProfileManager.TRACE_ID, context.getSessionVariable().getTraceId());
             plannerRuntimeProfile = new RuntimeProfile("Execution Summary");
             summaryProfile.addChild(plannerRuntimeProfile);
             profile.addChild(queryProfile);
diff --git a/fe/fe-core/src/test/java/org/apache/doris/common/util/RuntimeProfileTest.java b/fe/fe-core/src/test/java/org/apache/doris/common/util/RuntimeProfileTest.java
index 12ad3d5124..5e1c396f57 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/common/util/RuntimeProfileTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/common/util/RuntimeProfileTest.java
@@ -64,7 +64,7 @@ public class RuntimeProfileTest {
     public void testInfoStrings() {
         RuntimeProfile profile = new RuntimeProfile("profileName");
 
-        Assert.assertNull(profile.getInfoString("key"));
+        Assert.assertEquals("", profile.getInfoString("key"));
         // normal add and get
         profile.addInfoString("key", "value");
         String value = profile.getInfoString("key");


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org