Skip to content

Commit f33c5cf

Browse files
[opt](profile) Add ExecutedByFrontend in profile (#39942)
`Executed By Frontend: true` means query is executed on FE. <img width="561" alt="image" src="https://github.com/user-attachments/assets/ce6899f2-55a5-4972-810b-1805f2a0db16">
1 parent 375870d commit f33c5cf

File tree

3 files changed

+102
-1
lines changed

3 files changed

+102
-1
lines changed

fe/fe-core/src/main/java/org/apache/doris/common/profile/SummaryProfile.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public class SummaryProfile {
6969
public static final String PHYSICAL_PLAN = "Physical Plan";
7070
public static final String DISTRIBUTED_PLAN = "Distributed Plan";
7171
public static final String SYSTEM_MESSAGE = "System Message";
72+
public static final String EXECUTED_BY_FRONTEND = "Executed By Frontend";
7273
// Execution Summary
7374
public static final String EXECUTION_SUMMARY_PROFILE_NAME = "Execution Summary";
7475
public static final String ANALYSIS_TIME = "Analysis Time";
@@ -175,7 +176,8 @@ public class SummaryProfile {
175176
PARALLEL_FRAGMENT_EXEC_INSTANCE,
176177
TRACE_ID,
177178
TRANSACTION_COMMIT_TIME,
178-
SYSTEM_MESSAGE
179+
SYSTEM_MESSAGE,
180+
EXECUTED_BY_FRONTEND
179181
);
180182

181183
// Ident of each item. Default is 0, which doesn't need to present in this Map.
@@ -905,6 +907,10 @@ public void setSystemMessage(String msg) {
905907
summaryProfile.addInfoString(SYSTEM_MESSAGE, msg);
906908
}
907909

910+
public void setExecutedByFrontend(boolean executedByFrontend) {
911+
summaryProfile.addInfoString(EXECUTED_BY_FRONTEND, String.valueOf(executedByFrontend));
912+
}
913+
908914
public void write(DataOutput output) throws IOException {
909915
Text.writeString(output, GsonUtils.GSON.toJson(this));
910916
}

fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java

+5
Original file line numberDiff line numberDiff line change
@@ -1783,6 +1783,11 @@ private void handleQueryStmt() throws Exception {
17831783
sendResultSet(resultSet.get(), ((Queriable) parsedStmt).getFieldInfos());
17841784
isHandleQueryInFe = true;
17851785
LOG.info("Query {} finished", DebugUtil.printId(context.queryId));
1786+
if (context.getSessionVariable().enableProfile()) {
1787+
if (profile != null) {
1788+
this.profile.getSummaryProfile().setExecutedByFrontend(true);
1789+
}
1790+
}
17861791
return;
17871792
}
17881793
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
import groovy.json.JsonOutput
19+
import groovy.json.JsonSlurper
20+
import groovy.json.StringEscapeUtils
21+
22+
def getProfileList = {
23+
def dst = 'http://' + context.config.feHttpAddress
24+
def conn = new URL(dst + "/rest/v1/query_profile").openConnection()
25+
conn.setRequestMethod("GET")
26+
def encoding = Base64.getEncoder().encodeToString((context.config.feHttpUser + ":" +
27+
(context.config.feHttpPassword == null ? "" : context.config.feHttpPassword)).getBytes("UTF-8"))
28+
conn.setRequestProperty("Authorization", "Basic ${encoding}")
29+
return conn.getInputStream().getText()
30+
}
31+
32+
def getProfile = { id ->
33+
def dst = 'http://' + context.config.feHttpAddress
34+
def conn = new URL(dst + "/api/profile/text/?query_id=$id").openConnection()
35+
conn.setRequestMethod("GET")
36+
def encoding = Base64.getEncoder().encodeToString((context.config.feHttpUser + ":" +
37+
(context.config.feHttpPassword == null ? "" : context.config.feHttpPassword)).getBytes("UTF-8"))
38+
conn.setRequestProperty("Authorization", "Basic ${encoding}")
39+
// set conn parameters
40+
41+
return conn.getInputStream().getText()
42+
}
43+
44+
suite('test_execute_by_frontend') {
45+
sql """
46+
CREATE TABLE if not exists `test_execute_by_frontend` (
47+
`id` INT,
48+
`name` varchar(32)
49+
)ENGINE=OLAP
50+
UNIQUE KEY(`id`)
51+
DISTRIBUTED BY HASH(`id`) BUCKETS 1
52+
PROPERTIES (
53+
"replication_allocation" = "tag.location.default: 1"
54+
);
55+
"""
56+
57+
sql "set enable_profile=true"
58+
def simpleSql1 = "select * from test_execute_by_frontend"
59+
sql "${simpleSql1}"
60+
simpleSql2 = """select cast("1" as Int)"""
61+
sql "${simpleSql2}"
62+
def isRecorded = false
63+
def wholeString = getProfileList()
64+
List profileData = new JsonSlurper().parseText(wholeString).data.rows
65+
String queryId1 = "";
66+
String queryId2 = "";
67+
68+
for (final def profileItem in profileData) {
69+
if (profileItem["Sql Statement"].toString() == simpleSql1) {
70+
isRecorded = true
71+
queryId1 = profileItem["Profile ID"].toString()
72+
assertEquals("internal", profileItem["Default Catalog"].toString())
73+
}
74+
if (profileItem["Sql Statement"].toString() == simpleSql2) {
75+
queryId2 = profileItem["Profile ID"].toString()
76+
}
77+
}
78+
79+
assertTrue(isRecorded)
80+
81+
String profileContent1 = getProfile(queryId1)
82+
def executionProfileIdx1 = profileContent1.indexOf("Executed By Frontend: true")
83+
assertTrue(executionProfileIdx1 > 0)
84+
String profileContent2 = getProfile(queryId2)
85+
def executionProfileIdx2 = profileContent2.indexOf("Executed By Frontend: true")
86+
assertTrue(executionProfileIdx2 > 0)
87+
88+
sql """ SET enable_profile = false """
89+
sql """ DROP TABLE IF EXISTS test_execute_by_frontend """
90+
}

0 commit comments

Comments
 (0)