This is an automated email from the ASF dual-hosted git repository.
lxy-9602 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 8d84e265 test(metrics): make histogram windowing tests deterministic
(#458)
8d84e265 is described below
commit 8d84e265d693ed274f1467c865215c799ca6b8cf
Author: 小明同学 <[email protected]>
AuthorDate: Fri Oct 23 14:52:18 2026 +0800
test(metrics): make histogram windowing tests deterministic (#458)
---
src/paimon/common/metrics/histogram_windowing.cpp | 14 +--
src/paimon/common/metrics/histogram_windowing.h | 14 +--
.../common/metrics/histogram_windowing_test.cpp | 114 ++++++++++++++-------
3 files changed, 92 insertions(+), 50 deletions(-)
diff --git a/src/paimon/common/metrics/histogram_windowing.cpp
b/src/paimon/common/metrics/histogram_windowing.cpp
index 9b0565fb..c0b98945 100644
--- a/src/paimon/common/metrics/histogram_windowing.cpp
+++ b/src/paimon/common/metrics/histogram_windowing.cpp
@@ -40,7 +40,7 @@ void HistogramWindowingImpl::Init() {
std::lock_guard<std::mutex> guard(mu_);
window_start_micros_.assign(num_windows_, std::nullopt);
histograms_.assign(num_windows_, nullptr);
- const uint64_t now = AlignWindowStartMicros(NowMicros());
+ const uint64_t now = AlignWindowStartMicros(now_micros_());
ResetLocked(now);
}
@@ -131,7 +131,7 @@ void HistogramWindowingImpl::Add(double value) {
std::shared_ptr<HistogramImpl> hist;
{
std::lock_guard<std::mutex> guard(mu_);
- const uint64_t now = NowMicros();
+ const uint64_t now = now_micros_();
const uint64_t aligned_now = AlignWindowStartMicros(now);
// Ignore if clock moves backwards.
if (aligned_now < current_window_start_micros_) {
@@ -158,7 +158,7 @@ HistogramStats HistogramWindowingImpl::GetStats() const {
uint64_t aligned_now = 0;
{
std::lock_guard<std::mutex> guard(mu_);
- aligned_now = AlignWindowStartMicros(NowMicros());
+ aligned_now = AlignWindowStartMicros(now_micros_());
// Note: do NOT force AdvanceLocked here, to keep semantics consistent
with RocksDB's
// min_num_per_window behavior (windows advance upon Add() after
enough samples).
hists.reserve(histograms_.size());
@@ -201,7 +201,7 @@ void HistogramWindowingImpl::Merge(const Histogram& other) {
std::shared_ptr<HistogramImpl> target;
{
std::lock_guard<std::mutex> guard(mu_);
- const uint64_t aligned_now = AlignWindowStartMicros(NowMicros());
+ const uint64_t aligned_now = AlignWindowStartMicros(now_micros_());
if (aligned_now < current_window_start_micros_) {
return;
}
@@ -231,7 +231,7 @@ void HistogramWindowingImpl::Merge(const Histogram& other) {
std::shared_ptr<HistogramImpl> target;
{
std::lock_guard<std::mutex> guard(mu_);
- const uint64_t aligned_now = AlignWindowStartMicros(NowMicros());
+ const uint64_t aligned_now = AlignWindowStartMicros(now_micros_());
if (aligned_now < current_window_start_micros_) {
return;
}
@@ -264,7 +264,7 @@ void HistogramWindowingImpl::Merge(const Histogram& other) {
{
std::lock_guard<std::mutex> guard(mu_);
- const uint64_t aligned_now = AlignWindowStartMicros(NowMicros());
+ const uint64_t aligned_now = AlignWindowStartMicros(now_micros_());
if (aligned_now < current_window_start_micros_) {
return;
}
@@ -285,7 +285,7 @@ void HistogramWindowingImpl::Merge(const Histogram& other) {
std::shared_ptr<Histogram> HistogramWindowingImpl::Clone() const {
auto cloned = std::make_shared<HistogramWindowingImpl>(num_windows_,
micros_per_window_,
-
min_num_per_window_);
+
min_num_per_window_, now_micros_);
std::lock_guard<std::mutex> guard(mu_);
std::lock_guard<std::mutex> guard2(cloned->mu_);
diff --git a/src/paimon/common/metrics/histogram_windowing.h
b/src/paimon/common/metrics/histogram_windowing.h
index 5244457e..658c38d3 100644
--- a/src/paimon/common/metrics/histogram_windowing.h
+++ b/src/paimon/common/metrics/histogram_windowing.h
@@ -20,9 +20,11 @@
#pragma once
#include <cstdint>
+#include <functional>
#include <memory>
#include <mutex>
#include <optional>
+#include <utility>
#include <vector>
#include "paimon/common/metrics/histogram.h"
@@ -37,16 +39,15 @@ namespace paimon {
class HistogramWindowingImpl : public Histogram {
public:
// Default: 3 windows, 60s per window , 100 per window (paimon-java
default)
- HistogramWindowingImpl()
- : num_windows_(3), micros_per_window_(60 * 1000ULL * 1000ULL),
min_num_per_window_(100) {
- Init();
- }
+ HistogramWindowingImpl() : HistogramWindowingImpl(3, 60 * 1000ULL *
1000ULL, 100) {}
HistogramWindowingImpl(uint64_t num_windows, uint64_t micros_per_window,
- uint64_t min_num_per_window)
+ uint64_t min_num_per_window,
+ std::function<uint64_t()> now_micros = NowMicros)
: num_windows_(num_windows),
micros_per_window_(micros_per_window),
- min_num_per_window_(min_num_per_window) {
+ min_num_per_window_(min_num_per_window),
+ now_micros_(std::move(now_micros)) {
Init();
}
@@ -69,6 +70,7 @@ class HistogramWindowingImpl : public Histogram {
const uint64_t num_windows_;
const uint64_t micros_per_window_;
const uint64_t min_num_per_window_;
+ const std::function<uint64_t()> now_micros_;
mutable std::mutex mu_;
diff --git a/src/paimon/common/metrics/histogram_windowing_test.cpp
b/src/paimon/common/metrics/histogram_windowing_test.cpp
index ea5083ce..624f53d7 100644
--- a/src/paimon/common/metrics/histogram_windowing_test.cpp
+++ b/src/paimon/common/metrics/histogram_windowing_test.cpp
@@ -19,19 +19,26 @@
#include "paimon/common/metrics/histogram_windowing.h"
-#include <chrono>
-#include <thread>
-
#include "gtest/gtest.h"
namespace paimon::test {
-TEST(HistogramWindowingImplTest, TestAdvanceAndAggregateAcrossWindows) {
- // Use a relatively large span to avoid flakiness around boundary
(aligned_now - start == span).
- HistogramWindowingImpl h(/*num_windows=*/4, /*micros_per_window=*/25000,
+class HistogramWindowingImplTest : public ::testing::Test {
+ protected:
+ HistogramWindowingImpl CreateHistogram(uint64_t num_windows, uint64_t
micros_per_window,
+ uint64_t min_num_per_window) {
+ return HistogramWindowingImpl(num_windows, micros_per_window,
min_num_per_window,
+ [this]() { return now_micros_; });
+ }
+
+ uint64_t now_micros_ = 1000000;
+};
+
+TEST_F(HistogramWindowingImplTest, TestAdvanceAndAggregateAcrossWindows) {
+ auto h = CreateHistogram(/*num_windows=*/4, /*micros_per_window=*/1000,
/*min_num_per_window=*/1);
h.Add(1);
- std::this_thread::sleep_for(std::chrono::milliseconds(26));
+ now_micros_ += 1000;
h.Add(2);
HistogramStats s = h.GetStats();
@@ -41,16 +48,16 @@ TEST(HistogramWindowingImplTest,
TestAdvanceAndAggregateAcrossWindows) {
EXPECT_NEAR(s.average, 1.5, 1e-12);
}
-TEST(HistogramWindowingImplTest, TestResetWhenBeyondMaxSpan) {
- HistogramWindowingImpl h(/*num_windows=*/2, /*micros_per_window=*/1000,
+TEST_F(HistogramWindowingImplTest, TestResetWhenBeyondMaxSpan) {
+ auto h = CreateHistogram(/*num_windows=*/2, /*micros_per_window=*/1000,
/*min_num_per_window=*/1);
h.Add(1);
- std::this_thread::sleep_for(std::chrono::milliseconds(2));
+ now_micros_ += 1000;
h.Add(2);
+ ASSERT_EQ(h.GetStats().count, 2);
- // Sleep long enough so that aligned_now - current_window_start >=
- // micros_per_window*num_windows.
- std::this_thread::sleep_for(std::chrono::milliseconds(3));
+ // Move beyond max_span relative to the current window start.
+ now_micros_ += 3000;
h.Add(3);
HistogramStats s = h.GetStats();
@@ -60,11 +67,26 @@ TEST(HistogramWindowingImplTest,
TestResetWhenBeyondMaxSpan) {
EXPECT_NEAR(s.average, 3.0, 1e-12);
}
-TEST(HistogramWindowingImplTest, TestMergeSameParams) {
- // Use a large window to avoid relying on wall clock for this test.
- HistogramWindowingImpl h1(/*num_windows=*/4, /*micros_per_window=*/1000ULL
* 1000ULL,
+TEST_F(HistogramWindowingImplTest, TestExpiresAtMaxSpan) {
+ auto h = CreateHistogram(/*num_windows=*/2, /*micros_per_window=*/1000,
+ /*min_num_per_window=*/1);
+ h.Add(3);
+
+ now_micros_ += 1999;
+ ASSERT_EQ(h.GetStats().count, 1);
+ now_micros_ += 1;
+ EXPECT_EQ(h.GetStats().count, 0);
+
+ h.Add(4);
+ HistogramStats s = h.GetStats();
+ EXPECT_EQ(s.count, 1);
+ EXPECT_DOUBLE_EQ(s.sum, 4);
+}
+
+TEST_F(HistogramWindowingImplTest, TestMergeSameParams) {
+ auto h1 = CreateHistogram(/*num_windows=*/4, /*micros_per_window=*/1000,
/*min_num_per_window=*/1);
- HistogramWindowingImpl h2(/*num_windows=*/4, /*micros_per_window=*/1000ULL
* 1000ULL,
+ auto h2 = CreateHistogram(/*num_windows=*/4, /*micros_per_window=*/1000,
/*min_num_per_window=*/1);
h1.Add(1);
h1.Add(2);
@@ -78,10 +100,10 @@ TEST(HistogramWindowingImplTest, TestMergeSameParams) {
EXPECT_DOUBLE_EQ(s.max, 3);
}
-TEST(HistogramWindowingImplTest, TestMergeDifferentParamsFallback) {
- HistogramWindowingImpl h1(/*num_windows=*/4, /*micros_per_window=*/1000ULL
* 1000ULL,
+TEST_F(HistogramWindowingImplTest, TestMergeDifferentParamsFallback) {
+ auto h1 = CreateHistogram(/*num_windows=*/4, /*micros_per_window=*/1000,
/*min_num_per_window=*/1);
- HistogramWindowingImpl h2(/*num_windows=*/8, /*micros_per_window=*/1000ULL
* 1000ULL,
+ auto h2 = CreateHistogram(/*num_windows=*/8, /*micros_per_window=*/1000,
/*min_num_per_window=*/1);
h2.Add(10);
h2.Add(20);
@@ -93,12 +115,27 @@ TEST(HistogramWindowingImplTest,
TestMergeDifferentParamsFallback) {
EXPECT_DOUBLE_EQ(s.max, 20);
}
-TEST(HistogramWindowingImplTest, TestMinNumPerWindow100Case) {
+TEST_F(HistogramWindowingImplTest, TestMergePlainHistogram) {
+ auto h = CreateHistogram(/*num_windows=*/4, /*micros_per_window=*/1000,
+ /*min_num_per_window=*/1);
+ HistogramImpl other;
+ other.Add(10);
+ other.Add(20);
+
+ now_micros_ += 1000;
+ h.Merge(other);
+ HistogramStats s = h.GetStats();
+ EXPECT_EQ(s.count, 2);
+ EXPECT_DOUBLE_EQ(s.sum, 30);
+ EXPECT_DOUBLE_EQ(s.min, 10);
+ EXPECT_DOUBLE_EQ(s.max, 20);
+}
+
+TEST_F(HistogramWindowingImplTest, TestMinNumPerWindow100Case) {
// Validate min_num_per_window behavior:
// - window advancement is gated by sample count
// - if the histogram doesn't advance in time, it may get reset once
beyond max span
- // Use a larger window to reduce wall-clock sensitivity and avoid
flakiness.
- HistogramWindowingImpl h(/*num_windows=*/3, /*micros_per_window=*/50000,
+ auto h = CreateHistogram(/*num_windows=*/3, /*micros_per_window=*/50000,
/*min_num_per_window=*/100);
// Fill current window but keep it below min_num.
@@ -107,7 +144,7 @@ TEST(HistogramWindowingImplTest,
TestMinNumPerWindow100Case) {
}
// Cross at least one window.
- std::this_thread::sleep_for(std::chrono::milliseconds(75));
+ now_micros_ += 75000;
h.Add(2); // 100th sample; advancement check happens before this add.
HistogramStats s1 = h.GetStats();
@@ -117,7 +154,7 @@ TEST(HistogramWindowingImplTest,
TestMinNumPerWindow100Case) {
// Cross enough time so that aligned_now - current_window_start >= max_span
// (max_span = num_windows * micros_per_window = 150ms here).
- std::this_thread::sleep_for(std::chrono::milliseconds(175));
+ now_micros_ += 175000;
h.Add(1000);
HistogramStats s2 = h.GetStats();
@@ -126,9 +163,8 @@ TEST(HistogramWindowingImplTest,
TestMinNumPerWindow100Case) {
EXPECT_DOUBLE_EQ(s2.max, 1000);
}
-TEST(HistogramWindowingImplTest, TestLargeDatasetInSingleWindow) {
- // Use a large window to avoid relying on wall clock.
- HistogramWindowingImpl h(/*num_windows=*/4, /*micros_per_window=*/60ULL *
1000ULL * 1000ULL,
+TEST_F(HistogramWindowingImplTest, TestLargeDatasetInSingleWindow) {
+ auto h = CreateHistogram(/*num_windows=*/4, /*micros_per_window=*/1000,
/*min_num_per_window=*/1);
constexpr uint64_t n = 10000;
@@ -144,18 +180,18 @@ TEST(HistogramWindowingImplTest,
TestLargeDatasetInSingleWindow) {
EXPECT_NEAR(s.average, (static_cast<double>(n) + 1.0) / 2.0, 1e-12);
}
-TEST(HistogramWindowingImplTest, TestCrossMaxSpanAfterAdvance) {
+TEST_F(HistogramWindowingImplTest, TestCrossMaxSpanAfterAdvance) {
// Build multiple windows, then cross max_span so that all previous
windows are dropped.
- HistogramWindowingImpl h(/*num_windows=*/2, /*micros_per_window=*/2000,
+ auto h = CreateHistogram(/*num_windows=*/2, /*micros_per_window=*/2000,
/*min_num_per_window=*/1);
h.Add(1);
- std::this_thread::sleep_for(std::chrono::milliseconds(3));
+ now_micros_ += 2000;
h.Add(2); // should advance to next window.
+ ASSERT_EQ(h.GetStats().count, 2);
- // Cross max_span (4ms) relative to the current window start.
- // max_span = 4ms here.
- std::this_thread::sleep_for(std::chrono::milliseconds(7));
+ // Reach max_span (4ms) relative to the current window start.
+ now_micros_ += 4000;
h.Add(3); // should reset.
HistogramStats s = h.GetStats();
@@ -165,9 +201,8 @@ TEST(HistogramWindowingImplTest,
TestCrossMaxSpanAfterAdvance) {
EXPECT_DOUBLE_EQ(s.sum, 3);
}
-TEST(HistogramWindowingImplTest, TestCloneConsistencyAndIndependence) {
- // Use a large window to avoid relying on wall clock and window
advancement.
- HistogramWindowingImpl h(/*num_windows=*/4, /*micros_per_window=*/60ULL *
1000ULL * 1000ULL,
+TEST_F(HistogramWindowingImplTest, TestCloneConsistencyAndIndependence) {
+ auto h = CreateHistogram(/*num_windows=*/4, /*micros_per_window=*/1000,
/*min_num_per_window=*/1);
h.Add(1);
h.Add(2);
@@ -206,6 +241,11 @@ TEST(HistogramWindowingImplTest,
TestCloneConsistencyAndIndependence) {
const HistogramStats after_original2 = h.GetStats();
EXPECT_EQ(after_original2.count, after_original.count);
EXPECT_EQ(after_clone2.count, cloned_before.count + 1);
+
+ // Clones share the same time source, while keeping independent samples.
+ now_micros_ += 4000;
+ EXPECT_EQ(h.GetStats().count, 0);
+ EXPECT_EQ(cloned->GetStats().count, 0);
}
} // namespace paimon::test