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

yiguolei 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 6221a673341 [fix](spill) Record the spill read deserialize timer 
(#67041)
6221a673341 is described below

commit 6221a6733412e8a4f63b2843eff97bd5316ce7f6
Author: Xiangyi Zhu <[email protected]>
AuthorDate: Mon Aug 24 14:21:23 2026 +0800

    [fix](spill) Record the spill read deserialize timer (#67041)
    
    ### What problem does this PR solve?
    
    Issue Number: close #67040
    
    Problem Summary:
    
    `SpillReadDeserializeBlockTime` is always `0` in query profiles — the
    time spent deserializing spilled blocks has never been recorded.
    
    The counter is registered by `SpillReadCounters::init` under the shared
    constant `profile::SPILL_READ_DESERIALIZE_BLOCK_TIME`, whose value is
    `"SpillReadDeserializeBlockTime"`:
    
    ```cpp
    // be/src/exec/operator/spill_counters.h
    spill_read_deserialize_block_timer =
            ADD_TIMER_WITH_LEVEL(profile, 
profile::SPILL_READ_DESERIALIZE_BLOCK_TIME, 1);
    ```
    
    But `SpillFileReader` looked it up with a hand-written literal that is
    missing an `s`:
    
    ```cpp
    // be/src/exec/spill/spill_file_reader.cpp
    _deserialize_timer = 
custom_profile->get_counter("SpillReadDerializeBlockTime");
    //                                                        ^^^ Derialize
    ```
    
    `get_counter()` returns `nullptr` for the unknown name, and
    `ScopedTimer` returns early on a null counter, so
    `SCOPED_TIMER(_deserialize_timer)` in `SpillFileReader::read()` silently
    measures nothing.
    
    **Why existing tests did not catch it:** `spill_file_test.cpp` and
    `spill_repartitioner_test.cpp` registered the counters by hand and
    copied the same misspelling, so the reader's lookup resolved in tests
    while returning null in a real query.
    
    Changes:
    
    1. `SpillFileReader` now resolves every read counter through the
    `profile::` name constants instead of string literals, and `DCHECK`s
    that each one resolves, so a future rename fails loudly instead of
    silently dropping a counter.
    2. The unit tests reuse `SpillWriteCounters::init` /
    `SpillReadCounters::init` rather than re-listing the names, so the
    registered names and units cannot drift from production again.
    3. `spillable_operator_test_helper` registered `SpillReadFileTime` and
    `SpillReadDeserializeBlockTime` as `TUnit::UNIT` instead of timers. That
    was harmless while the timer resolved to null, but trips the
    `DCHECK_EQ(counter->type(), TUnit::TIME_NS)` inside `ScopedTimer` once
    it resolves, so it is corrected by the same reuse.
    
    ### Release note
    
    Fix `SpillReadDeserializeBlockTime` always being 0 in query profiles.
    
    ### Check List (For Author)
    
    - Test
        - [x] Unit Test
    
    Added `SpillFileTest.ReadDeserializeTimerIsRecorded`, which fails before
    this
    change: with the test registering the canonical name, the reader's
    misspelled
    lookup yields a null counter and the timer stays at 0 after a real read.
    The
    test also asserts the misspelled name is absent, so it cannot be
    reintroduced.
    
    - Behavior changed:
        - [x] No.
    
    Profile-only fix. The counter already existed and was already reported;
    it was
          simply never updated. No counter is added, removed, or renamed.
    
    - Does this need documentation?
        - [x] No.
---
 be/src/exec/spill/spill_file_reader.cpp            | 27 ++++---
 .../operator/spillable_operator_test_helper.cpp    | 37 ++++-----
 be/test/vec/spill/spill_file_test.cpp              | 90 +++++++++++++++++-----
 be/test/vec/spill/spill_repartitioner_test.cpp     | 35 ++++-----
 4 files changed, 120 insertions(+), 69 deletions(-)

diff --git a/be/src/exec/spill/spill_file_reader.cpp 
b/be/src/exec/spill/spill_file_reader.cpp
index 7c6107e4a3e..736064d720b 100644
--- a/be/src/exec/spill/spill_file_reader.cpp
+++ b/be/src/exec/spill/spill_file_reader.cpp
@@ -30,6 +30,7 @@
 #include "io/fs/local_file_system.h"
 #include "runtime/exec_env.h"
 #include "runtime/query_context.h"
+#include "runtime/runtime_profile_counter_names.h"
 #include "runtime/runtime_state.h"
 #include "util/debug_points.h"
 #include "util/slice.h"
@@ -43,16 +44,24 @@ SpillFileReader::SpillFileReader(RuntimeState* state, 
RuntimeProfile* profile,
         : _spill_dir(std::move(spill_dir)),
           _part_count(part_count),
           _resource_ctx(state->get_query_ctx()->resource_ctx()) {
-    // Internalize counter setup
-    RuntimeProfile* custom_profile = profile->get_child("CustomCounters");
+    // Internalize counter setup. The counters themselves are registered by 
the owning
+    // operator (SpillReadCounters::init), so look them up by the shared name 
constants:
+    // a literal that drifts from the constant silently yields a null counter, 
which
+    // turns every SCOPED_TIMER/COUNTER_UPDATE on it into a no-op.
+    RuntimeProfile* custom_profile = 
profile->get_child(profile::CUSTOM_COUNTERS);
     DCHECK(custom_profile != nullptr);
-    _read_file_timer = custom_profile->get_counter("SpillReadFileTime");
-    _deserialize_timer = 
custom_profile->get_counter("SpillReadDerializeBlockTime");
-    _read_block_count = custom_profile->get_counter("SpillReadBlockCount");
-    _read_block_data_size = custom_profile->get_counter("SpillReadBlockBytes");
-    _read_file_size = custom_profile->get_counter("SpillReadFileBytes");
-    _read_rows_count = custom_profile->get_counter("SpillReadRows");
-    _read_file_count = custom_profile->get_counter("SpillReadFileCount");
+    auto get_counter = [&](const char* name) {
+        auto* counter = custom_profile->get_counter(name);
+        DCHECK(counter != nullptr) << "spill read counter is not registered: " 
<< name;
+        return counter;
+    };
+    _read_file_timer = get_counter(profile::SPILL_READ_FILE_TIME);
+    _deserialize_timer = 
get_counter(profile::SPILL_READ_DESERIALIZE_BLOCK_TIME);
+    _read_block_count = get_counter(profile::SPILL_READ_BLOCK_COUNT);
+    _read_block_data_size = get_counter(profile::SPILL_READ_BLOCK_BYTES);
+    _read_file_size = get_counter(profile::SPILL_READ_FILE_BYTES);
+    _read_rows_count = get_counter(profile::SPILL_READ_ROWS);
+    _read_file_count = get_counter(profile::SPILL_READ_FILE_COUNT);
 }
 
 Status SpillFileReader::open() {
diff --git a/be/test/exec/operator/spillable_operator_test_helper.cpp 
b/be/test/exec/operator/spillable_operator_test_helper.cpp
index dd39a8aa6b3..72bbc0bca43 100644
--- a/be/test/exec/operator/spillable_operator_test_helper.cpp
+++ b/be/test/exec/operator/spillable_operator_test_helper.cpp
@@ -28,6 +28,7 @@
 #include <memory>
 #include <vector>
 
+#include "exec/operator/spill_counters.h"
 #include "io/fs/local_file_system.h"
 #include "testutil/creators.h"
 
@@ -42,26 +43,22 @@ void SpillableOperatorTestHelper::SetUp() {
 
     ADD_COUNTER_WITH_LEVEL(common_profile.get(), "MemoryUsage", TUnit::BYTES, 
1);
     ADD_TIMER_WITH_LEVEL(common_profile.get(), "ExecTime", 1);
-    ADD_TIMER_WITH_LEVEL(custom_profile.get(), "SpillTotalTime", 1);
-    ADD_TIMER_WITH_LEVEL(custom_profile.get(), "SpillWriteTime", 1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), 
"SpillWriteTaskWaitInQueueCount", TUnit::UNIT, 1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), "SpillWriteTaskCount", 
TUnit::UNIT, 1);
-    ADD_TIMER_WITH_LEVEL(custom_profile.get(), 
"SpillWriteTaskWaitInQueueTime", 1);
-    ADD_TIMER_WITH_LEVEL(custom_profile.get(), "SpillWriteFileTime", 1);
-    ADD_TIMER_WITH_LEVEL(custom_profile.get(), "SpillWriteSerializeBlockTime", 
1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), "SpillWriteBlockCount", 
TUnit::UNIT, 1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), "SpillWriteBlockBytes", 
TUnit::BYTES, 1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), "SpillWriteFileBytes", 
TUnit::BYTES, 1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), "SpillWriteRows", 
TUnit::UNIT, 1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), "SpillReadFileTime", 
TUnit::UNIT, 1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), 
"SpillReadDeserializeBlockTime", TUnit::UNIT, 1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), "SpillReadBlockCount", 
TUnit::UNIT, 1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), "SpillReadBlockBytes", 
TUnit::UNIT, 1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), "SpillReadFileBytes", 
TUnit::UNIT, 1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), "SpillReadRows", TUnit::UNIT, 
1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), "SpillReadFileCount", 
TUnit::UNIT, 1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), "SpillWriteFileTotalCount", 
TUnit::UNIT, 1);
-    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), "SpillWriteFileCurrentBytes", 
TUnit::UNIT, 1);
+
+    // Reuse the production initializers so both the names and the TUnit of 
every spill
+    // counter match what SpillFileReader/SpillFileWriter expect. Registering 
a timer as
+    // TUnit::UNIT by hand trips the DCHECK inside ScopedTimer.
+    SpillWriteCounters write_counters;
+    write_counters.init(custom_profile.get());
+    SpillReadCounters read_counters;
+    read_counters.init(custom_profile.get());
+
+    // Source-only extras, see 
PipelineXSpillLocalState::init_spill_{write,read}_counters.
+    ADD_TIMER_WITH_LEVEL(custom_profile.get(), profile::SPILL_TOTAL_TIME, 1);
+    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), 
profile::SPILL_WRITE_FILE_BYTES, TUnit::BYTES, 1);
+    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), 
profile::SPILL_WRITE_FILE_TOTAL_COUNT, TUnit::UNIT,
+                           1);
+    ADD_COUNTER_WITH_LEVEL(custom_profile.get(), 
profile::SPILL_WRITE_FILE_CURRENT_BYTES,
+                           TUnit::BYTES, 1);
 
     operator_profile->add_child(custom_profile.get(), true);
     operator_profile->add_child(common_profile.get(), true);
diff --git a/be/test/vec/spill/spill_file_test.cpp 
b/be/test/vec/spill/spill_file_test.cpp
index 9df4bf626f3..c4d4f140635 100644
--- a/be/test/vec/spill/spill_file_test.cpp
+++ b/be/test/vec/spill/spill_file_test.cpp
@@ -32,6 +32,7 @@
 #include "core/block/block.h"
 #include "core/data_type/data_type_number.h"
 #include "core/data_type/data_type_string.h"
+#include "exec/operator/spill_counters.h"
 #include "exec/pipeline/pipeline_fragment_context.h"
 #include "exec/spill/spill_file_manager.h"
 #include "exec/spill/spill_file_reader.h"
@@ -62,27 +63,24 @@ protected:
         _common_profile->AddHighWaterMarkCounter("MemoryUsage", TUnit::BYTES, 
"", 1);
         ADD_TIMER_WITH_LEVEL(_common_profile.get(), "ExecTime", 1);
 
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), "SpillTotalTime", 1);
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), "SpillWriteTime", 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), 
"SpillWriteTaskWaitInQueueCount", TUnit::UNIT,
+        // Register exactly what PipelineXSpillLocalState registers in 
production, by
+        // reusing the same initializers. Hand-copying the counter names here 
is what let
+        // a misspelled lookup in SpillFileReader 
("SpillReadDerializeBlockTime") go
+        // unnoticed: the test registered the same misspelling, so the lookup 
"worked"
+        // here while silently returning null in a real query.
+        SpillWriteCounters write_counters;
+        write_counters.init(_custom_profile.get());
+        SpillReadCounters read_counters;
+        read_counters.init(_custom_profile.get());
+
+        // Source-only extras, see 
PipelineXSpillLocalState::init_spill_{write,read}_counters.
+        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), profile::SPILL_TOTAL_TIME, 
1);
+        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), 
profile::SPILL_WRITE_FILE_BYTES, TUnit::BYTES,
                                1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillWriteTaskCount", 
TUnit::UNIT, 1);
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), 
"SpillWriteTaskWaitInQueueTime", 1);
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), "SpillWriteFileTime", 1);
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), 
"SpillWriteSerializeBlockTime", 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillWriteBlockCount", 
TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillWriteBlockBytes", 
TUnit::BYTES, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillWriteFileBytes", 
TUnit::BYTES, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillWriteRows", 
TUnit::UNIT, 1);
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), "SpillReadFileTime", 1);
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), 
"SpillReadDerializeBlockTime", 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillReadBlockCount", 
TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillReadBlockBytes", 
TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillReadFileBytes", 
TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillReadRows", 
TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillReadFileCount", 
TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), 
"SpillWriteFileTotalCount", TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), 
"SpillWriteFileCurrentBytes", TUnit::UNIT, 1);
+        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), 
profile::SPILL_WRITE_FILE_TOTAL_COUNT,
+                               TUnit::UNIT, 1);
+        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), 
profile::SPILL_WRITE_FILE_CURRENT_BYTES,
+                               TUnit::BYTES, 1);
 
         _profile->add_child(_custom_profile.get(), true);
         _profile->add_child(_common_profile.get(), true);
@@ -1541,6 +1539,58 @@ TEST_F(SpillFileTest, ReadCounters) {
     ASSERT_GT(read_file_size->value(), 0);
 }
 
+// Regression test: SpillFileReader used to look up the deserialize timer 
under a
+// misspelled name ("SpillReadDerializeBlockTime"), so get_counter() returned 
null and
+// SCOPED_TIMER silently recorded nothing. The counter stayed at 0 in every 
profile.
+TEST_F(SpillFileTest, ReadDeserializeTimerIsRecorded) {
+    SpillFileSPtr spill_file;
+    auto st = ExecEnv::GetInstance()->spill_file_mgr()->create_spill_file(
+            "test_query/read_deserialize_timer", spill_file);
+    ASSERT_TRUE(st.ok());
+
+    {
+        SpillFileWriterSPtr writer;
+        st = spill_file->create_writer(_runtime_state.get(), _profile.get(), 
writer);
+        ASSERT_TRUE(st.ok());
+
+        auto block = _create_int_block({1, 2, 3, 4, 5});
+        st = writer->write_block(_runtime_state.get(), block);
+        ASSERT_TRUE(st.ok());
+
+        st = writer->close();
+        ASSERT_TRUE(st.ok());
+    }
+
+    // The timer is registered by SpillReadCounters::init under the canonical 
name and
+    // must still be untouched before any read happens.
+    auto* deserialize_timer =
+            
_custom_profile->get_counter(profile::SPILL_READ_DESERIALIZE_BLOCK_TIME);
+    ASSERT_TRUE(deserialize_timer != nullptr)
+            << "counter name drifted from " << 
profile::SPILL_READ_DESERIALIZE_BLOCK_TIME;
+    ASSERT_EQ(deserialize_timer->value(), 0);
+
+    auto reader = spill_file->create_reader(_runtime_state.get(), 
_profile.get());
+    st = reader->open();
+    ASSERT_TRUE(st.ok());
+
+    Block block;
+    bool eos = false;
+    st = reader->read(&block, &eos);
+    ASSERT_TRUE(st.ok());
+    ASSERT_EQ(block.rows(), 5);
+
+    st = reader->close();
+    ASSERT_TRUE(st.ok());
+
+    // Deserializing a real block must land on the canonical counter. This is 
0 whenever
+    // the reader's lookup name does not match what the operator registered.
+    ASSERT_GT(deserialize_timer->value(), 0);
+
+    // The misspelled name must not exist: if it reappears, some caller 
registered it and
+    // the two spellings will drift apart again.
+    ASSERT_TRUE(_custom_profile->get_counter("SpillReadDerializeBlockTime") == 
nullptr);
+}
+
 // ═══════════════════════════════════════════════════════════════════════
 // SpillDataDir tests
 // ═══════════════════════════════════════════════════════════════════════
diff --git a/be/test/vec/spill/spill_repartitioner_test.cpp 
b/be/test/vec/spill/spill_repartitioner_test.cpp
index 01da4719cad..f6fa6de73e3 100644
--- a/be/test/vec/spill/spill_repartitioner_test.cpp
+++ b/be/test/vec/spill/spill_repartitioner_test.cpp
@@ -25,6 +25,7 @@
 
 #include "core/block/block.h"
 #include "core/data_type/data_type_number.h"
+#include "exec/operator/spill_counters.h"
 #include "exec/partitioner/partitioner.h"
 #include "exec/spill/spill_file.h"
 #include "exec/spill/spill_file_manager.h"
@@ -54,27 +55,21 @@ protected:
         _common_profile->AddHighWaterMarkCounter("MemoryUsage", TUnit::BYTES, 
"", 1);
         ADD_TIMER_WITH_LEVEL(_common_profile.get(), "ExecTime", 1);
 
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), "SpillTotalTime", 1);
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), "SpillWriteTime", 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), 
"SpillWriteTaskWaitInQueueCount", TUnit::UNIT,
+        // Reuse the production initializers so the registered names cannot 
drift from
+        // what SpillFileReader/SpillFileWriter look up.
+        SpillWriteCounters write_counters;
+        write_counters.init(_custom_profile.get());
+        SpillReadCounters read_counters;
+        read_counters.init(_custom_profile.get());
+
+        // Source-only extras, see 
PipelineXSpillLocalState::init_spill_{write,read}_counters.
+        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), profile::SPILL_TOTAL_TIME, 
1);
+        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), 
profile::SPILL_WRITE_FILE_BYTES, TUnit::BYTES,
                                1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillWriteTaskCount", 
TUnit::UNIT, 1);
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), 
"SpillWriteTaskWaitInQueueTime", 1);
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), "SpillWriteFileTime", 1);
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), 
"SpillWriteSerializeBlockTime", 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillWriteBlockCount", 
TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillWriteBlockBytes", 
TUnit::BYTES, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillWriteFileBytes", 
TUnit::BYTES, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillWriteRows", 
TUnit::UNIT, 1);
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), "SpillReadFileTime", 1);
-        ADD_TIMER_WITH_LEVEL(_custom_profile.get(), 
"SpillReadDerializeBlockTime", 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillReadBlockCount", 
TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillReadBlockBytes", 
TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillReadFileBytes", 
TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillReadRows", 
TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), "SpillReadFileCount", 
TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), 
"SpillWriteFileTotalCount", TUnit::UNIT, 1);
-        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), 
"SpillWriteFileCurrentBytes", TUnit::UNIT, 1);
+        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), 
profile::SPILL_WRITE_FILE_TOTAL_COUNT,
+                               TUnit::UNIT, 1);
+        ADD_COUNTER_WITH_LEVEL(_custom_profile.get(), 
profile::SPILL_WRITE_FILE_CURRENT_BYTES,
+                               TUnit::BYTES, 1);
 
         _profile->add_child(_custom_profile.get(), true);
         _profile->add_child(_common_profile.get(), true);


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

Reply via email to