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

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new 482d6d12d6f branch-4.1: [feat](audit log) add queue_time_ms in 
audit_log (#66641)
482d6d12d6f is described below

commit 482d6d12d6f0c7b0f36bc2c9bd39dee1ecdaa508
Author: bobhan1 <[email protected]>
AuthorDate: Tue Aug 11 18:39:40 2026 +0800

    branch-4.1: [feat](audit log) add queue_time_ms in audit_log (#66641)
    
    ### What problem does this PR solve?
    
    Related PR: #60418
    
    Problem Summary:
    
    Backport #60418 to `branch-4.1` for CIR-21335.
    
    The audit log's `query_time` includes workload-group queue time, so
    users cannot distinguish queue delay from the remaining end-to-end query
    latency. This PR adds `queue_time_ms` to `__internal_schema.audit_log`
    and propagates it through the audit event and loader paths.
    
    `branch-4.1` already contained the `getQueueTimeMs()` helper, so the
    conflict resolution keeps that implementation and ports the missing
    schema, event, loader, call-site, unit-test, and regression-test
    changes.
    
    ### Release note
    
    Add `queue_time_ms` to `__internal_schema.audit_log`.
    
    ### Validation
    
    - `./run-fe-ut.sh --run org.apache.doris.qe.AuditEventProcessorTest`
    - `./build.sh --be --fe -j100`
    - `./run-regression-test.sh --run -d regression-test/suites/audit -s
    test_audit_log_queue_time`
    - `git diff --check upstream/branch-4.1...HEAD`
    
    ### Check List (For Author)
    
    - Test <!-- At least one of them must be included. -->
        - [x] Regression test
        - [x] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason <!-- Add your reason?  -->
    
    - Behavior changed:
        - [x] No.
        - [ ] Yes. <!-- Explain the behavior change -->
    
    - Does this need documentation?
        - [x] No.
    - [ ] Yes. <!-- Add document PR link here. eg:
    https://github.com/apache/doris-website/pull/1214 -->
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label <!-- Add branch pick label that this PR
    should merge into -->
    
    Co-authored-by: Fy <[email protected]>
---
 .../org/apache/doris/catalog/InternalSchema.java   |   2 +
 .../java/org/apache/doris/plugin/AuditEvent.java   |   7 ++
 .../org/apache/doris/plugin/audit/AuditLoader.java |   1 +
 .../java/org/apache/doris/qe/AuditLogHelper.java   |   3 +
 .../apache/doris/qe/AuditEventProcessorTest.java   |   2 +
 .../data/audit/test_audit_log_behavior.out         |   1 +
 .../suites/audit/test_audit_log_queue_time.groovy  | 129 +++++++++++++++++++++
 7 files changed, 145 insertions(+)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/InternalSchema.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/InternalSchema.java
index ac8f58d1fa7..59f29cf2d89 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/InternalSchema.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/InternalSchema.java
@@ -152,6 +152,8 @@ public class InternalSchema {
         // execution info
         AUDIT_SCHEMA.add(new ColumnDef("query_time",
                 TypeDef.create(PrimitiveType.BIGINT), 
ColumnNullableType.NULLABLE));
+        AUDIT_SCHEMA.add(new ColumnDef("queue_time_ms",
+                TypeDef.create(PrimitiveType.BIGINT), 
ColumnNullableType.NULLABLE));
         AUDIT_SCHEMA.add(new ColumnDef("cpu_time_ms",
                 TypeDef.create(PrimitiveType.BIGINT), 
ColumnNullableType.NULLABLE));
         AUDIT_SCHEMA.add(new ColumnDef("peak_memory_bytes",
diff --git a/fe/fe-core/src/main/java/org/apache/doris/plugin/AuditEvent.java 
b/fe/fe-core/src/main/java/org/apache/doris/plugin/AuditEvent.java
index 3b08dc382d4..c54c32f6a18 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/plugin/AuditEvent.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/plugin/AuditEvent.java
@@ -84,6 +84,8 @@ public class AuditEvent {
     // execution info
     @AuditField(value = "Time(ms)", colName = "query_time")
     public long queryTime = -1;
+    @AuditField(value = "QueueTimeMs", colName = "queue_time_ms")
+    public long queueTimeMs = -1;
     @AuditField(value = "CpuTimeMS", colName = "cpu_time_ms")
     public long cpuTimeMs = -1;
     @AuditField(value = "PeakMemoryBytes", colName = "peak_memory_bytes")
@@ -231,6 +233,11 @@ public class AuditEvent {
             return this;
         }
 
+        public AuditEventBuilder setQueueTimeMs(long queueTimeMs) {
+            auditEvent.queueTimeMs = queueTimeMs;
+            return this;
+        }
+
         public AuditEventBuilder setScanBytes(long scanBytes) {
             auditEvent.scanBytes = scanBytes;
             return this;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/plugin/audit/AuditLoader.java 
b/fe/fe-core/src/main/java/org/apache/doris/plugin/audit/AuditLoader.java
index 1d4aeddb3e7..bbc86646635 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/plugin/audit/AuditLoader.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/plugin/audit/AuditLoader.java
@@ -168,6 +168,7 @@ public class AuditLoader extends Plugin implements 
AuditPlugin {
 
         // execution info
         logBuffer.append(event.queryTime).append(AUDIT_TABLE_COL_SEPARATOR);
+        logBuffer.append(event.queueTimeMs).append(AUDIT_TABLE_COL_SEPARATOR);
         logBuffer.append(event.cpuTimeMs).append(AUDIT_TABLE_COL_SEPARATOR);
         
logBuffer.append(event.peakMemoryBytes).append(AUDIT_TABLE_COL_SEPARATOR);
         logBuffer.append(event.scanBytes).append(AUDIT_TABLE_COL_SEPARATOR);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/AuditLogHelper.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/AuditLogHelper.java
index d6c992412c2..07681fc8b4a 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/AuditLogHelper.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/AuditLogHelper.java
@@ -196,6 +196,8 @@ public class AuditLogHelper {
         }
         String cluster = Config.isCloudMode() ? cloudCluster : "";
         String stmtType = getStmtType(parsedStmt);
+        long queueTimeMs = getQueueTimeMs(ctx);
+
 
         AuditEventBuilder auditEventBuilder = ctx.getAuditEventBuilder();
         // ATTN: MUST reset, otherwise, the same AuditEventBuilder instance 
will be used in the next query.
@@ -214,6 +216,7 @@ public class AuditLogHelper {
                 .setErrorMessage((ctx.getState().getErrorMessage() == null ? 
"" :
                         ctx.getState().getErrorMessage().replace("\n", " 
").replace("\t", " ")))
                 .setQueryTime(elapseMs)
+                .setQueueTimeMs(queueTimeMs)
                 .setCpuTimeMs(statistics == null ? 0 : statistics.getCpuMs())
                 .setPeakMemoryBytes(statistics == null ? 0 : 
statistics.getMaxPeakMemoryBytes())
                 .setScanBytes(statistics == null ? 0 : 
statistics.getScanBytes())
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/qe/AuditEventProcessorTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/qe/AuditEventProcessorTest.java
index f80b485609c..884f9a5badf 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/qe/AuditEventProcessorTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/qe/AuditEventProcessorTest.java
@@ -58,6 +58,7 @@ public class AuditEventProcessorTest {
                 .setDb("db1")
                 .setState("EOF")
                 .setQueryTime(2000)
+                .setQueueTimeMs(2000)
                 .setScanBytes(100000)
                 .setScanRows(200000)
                 .setReturnRows(1)
@@ -68,6 +69,7 @@ public class AuditEventProcessorTest {
         Assert.assertEquals("127.0.0.1", event.clientIp);
         Assert.assertEquals(200000, event.scanRows);
         Assert.assertEquals("SELECT", event.stmtType);
+        Assert.assertEquals(2000, event.queueTimeMs);
     }
 
     @Test
diff --git a/regression-test/data/audit/test_audit_log_behavior.out 
b/regression-test/data/audit/test_audit_log_behavior.out
index ebf50840302..f0a40463e9a 100644
--- a/regression-test/data/audit/test_audit_log_behavior.out
+++ b/regression-test/data/audit/test_audit_log_behavior.out
@@ -11,6 +11,7 @@ state varchar(128)    Yes     false   \N      NONE
 error_code     int     Yes     false   \N      NONE
 error_message  text    Yes     false   \N      NONE
 query_time     bigint  Yes     false   \N      NONE
+queue_time_ms  bigint  Yes     false   \N      NONE
 cpu_time_ms    bigint  Yes     false   \N      NONE
 peak_memory_bytes      bigint  Yes     false   \N      NONE
 scan_bytes     bigint  Yes     false   \N      NONE
diff --git a/regression-test/suites/audit/test_audit_log_queue_time.groovy 
b/regression-test/suites/audit/test_audit_log_queue_time.groovy
new file mode 100644
index 00000000000..e40d1688c42
--- /dev/null
+++ b/regression-test/suites/audit/test_audit_log_queue_time.groovy
@@ -0,0 +1,129 @@
+// 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.
+
+suite("test_audit_log_queue_time", "nonConcurrent") {
+    // Check admin privilege
+    try {
+        sql "set global enable_audit_plugin = true"
+    } catch (Exception e) {
+        log.warn("skip this case, because " + e.getMessage())
+        assertTrue(e.getMessage().toUpperCase().contains("ADMIN"))
+        return
+    }
+
+    def tableName = "audit_queue_time_test"
+    def wgName = "test_queue_time_wg"
+    def testMarker = UUID.randomUUID().toString().substring(0, 8)
+
+    // Cleanup environment
+    sql "drop table if exists ${tableName}"
+    sql "drop workload group if exists ${wgName}"
+
+    // Create test table
+    sql """
+        CREATE TABLE `${tableName}` (
+          `id` bigint,
+          `name` varchar(32)
+        ) ENGINE=OLAP
+        DUPLICATE KEY(`id`)
+        DISTRIBUTED BY HASH(`id`) BUCKETS 1
+        PROPERTIES ("replication_allocation" = "tag.location.default: 1")
+    """
+
+    sql "insert into ${tableName} values (1, 'test')"
+
+    def maxConcurrency = 1
+    // Create workload group: max_concurrency=1 ensures queries queue up
+    sql """
+        create workload group ${wgName}
+        properties (
+            'max_concurrency' = '${maxConcurrency}',
+            'max_queue_size' = '10',
+            'queue_timeout' = '30000'
+        )
+    """
+
+    // Wait for workload group to take effect
+    Thread.sleep(5000)
+
+    // Truncate audit_log for easier testing
+    sql "truncate table __internal_schema.audit_log"
+
+    // Submit concurrent queries with marker for later lookup
+    def sqlSleepTime = 5
+    def queuedSqlCnt = 1
+    def threads = []
+    for (int i = 0; i < maxConcurrency + queuedSqlCnt; i++) {
+        def idx = i
+        threads << Thread.start {
+            try {
+                sql "set workload_group=${wgName}"
+                // Use sleep function to simulate long query, ensuring 
subsequent queries need to queue
+                sql """
+                    select sleep(${sqlSleepTime}), '${testMarker}_${idx}' as 
marker
+                    from ${tableName} limit 1
+                """
+            } catch (Exception e) {
+                log.warn("Query ${idx} failed: ${e.getMessage()}")
+            }
+        }
+    }
+
+    // Wait for all queries to complete
+    threads.each { it.join() }
+
+    // Wait for audit log to flush
+    Thread.sleep(5000)
+    sql "call flush_audit_log()"
+    Thread.sleep(5000)
+
+    // Verify queue_time_ms column exists
+    def schemaResult = sql "desc internal.__internal_schema.audit_log"
+    def hasQueueTimeMs = schemaResult.any { it[0] == "queue_time_ms" }
+    assertTrue(hasQueueTimeMs)
+
+    // check result
+    def retry = 10
+    def query = """
+        select query_id, queue_time_ms, stmt
+        from __internal_schema.audit_log
+        where stmt like '%${testMarker}%'
+        and queue_time_ms > 0
+        order by time
+    """
+    def auditResult = sql "${query}"
+
+    while (auditResult.isEmpty()) {
+        if (retry-- < 0) {
+            throw new RuntimeException("It has retried a few but still failed, 
you need to check it")
+        }
+        sql "call flush_audit_log()"
+        sleep(3000)
+        auditResult = sql "${query}"
+    }
+
+    auditResult.each { row ->
+        assertTrue(row[1] >= sqlSleepTime * 1000)
+    }
+
+    assertTrue(auditResult.size() >= queuedSqlCnt)
+
+    // Cleanup
+    sql "drop table if exists ${tableName}"
+    sql "drop workload group if exists ${wgName}"
+    sql "set global enable_audit_plugin = false"
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to