blob: ea5083cefb93751cfc5d9c0cf9b5659710f9c647 [file]
/*
* 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.
*/
#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,
/*min_num_per_window=*/1);
h.Add(1);
std::this_thread::sleep_for(std::chrono::milliseconds(26));
h.Add(2);
HistogramStats s = h.GetStats();
EXPECT_EQ(s.count, 2);
EXPECT_DOUBLE_EQ(s.min, 1);
EXPECT_DOUBLE_EQ(s.max, 2);
EXPECT_NEAR(s.average, 1.5, 1e-12);
}
TEST(HistogramWindowingImplTest, TestResetWhenBeyondMaxSpan) {
HistogramWindowingImpl h(/*num_windows=*/2, /*micros_per_window=*/1000,
/*min_num_per_window=*/1);
h.Add(1);
std::this_thread::sleep_for(std::chrono::milliseconds(2));
h.Add(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));
h.Add(3);
HistogramStats s = h.GetStats();
EXPECT_EQ(s.count, 1);
EXPECT_DOUBLE_EQ(s.min, 3);
EXPECT_DOUBLE_EQ(s.max, 3);
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,
/*min_num_per_window=*/1);
HistogramWindowingImpl h2(/*num_windows=*/4, /*micros_per_window=*/1000ULL * 1000ULL,
/*min_num_per_window=*/1);
h1.Add(1);
h1.Add(2);
h2.Add(3);
h1.Merge(h2);
HistogramStats s = h1.GetStats();
EXPECT_EQ(s.count, 3);
EXPECT_DOUBLE_EQ(s.sum, 6);
EXPECT_DOUBLE_EQ(s.min, 1);
EXPECT_DOUBLE_EQ(s.max, 3);
}
TEST(HistogramWindowingImplTest, TestMergeDifferentParamsFallback) {
HistogramWindowingImpl h1(/*num_windows=*/4, /*micros_per_window=*/1000ULL * 1000ULL,
/*min_num_per_window=*/1);
HistogramWindowingImpl h2(/*num_windows=*/8, /*micros_per_window=*/1000ULL * 1000ULL,
/*min_num_per_window=*/1);
h2.Add(10);
h2.Add(20);
h1.Merge(h2);
HistogramStats s = h1.GetStats();
EXPECT_EQ(s.count, 2);
EXPECT_DOUBLE_EQ(s.min, 10);
EXPECT_DOUBLE_EQ(s.max, 20);
}
TEST(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,
/*min_num_per_window=*/100);
// Fill current window but keep it below min_num.
for (int32_t i = 0; i < 99; ++i) {
h.Add(1);
}
// Cross at least one window.
std::this_thread::sleep_for(std::chrono::milliseconds(75));
h.Add(2); // 100th sample; advancement check happens before this add.
HistogramStats s1 = h.GetStats();
EXPECT_EQ(s1.count, 100);
EXPECT_DOUBLE_EQ(s1.min, 1);
EXPECT_DOUBLE_EQ(s1.max, 2);
// 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));
h.Add(1000);
HistogramStats s2 = h.GetStats();
EXPECT_EQ(s2.count, 1);
EXPECT_DOUBLE_EQ(s2.min, 1000);
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,
/*min_num_per_window=*/1);
constexpr uint64_t n = 10000;
for (uint64_t i = 1; i <= n; ++i) {
h.Add(static_cast<double>(i));
}
HistogramStats s = h.GetStats();
EXPECT_EQ(s.count, n);
EXPECT_DOUBLE_EQ(s.min, 1);
EXPECT_DOUBLE_EQ(s.max, static_cast<double>(n));
EXPECT_DOUBLE_EQ(s.sum, static_cast<double>(n) * static_cast<double>(n + 1) / 2.0);
EXPECT_NEAR(s.average, (static_cast<double>(n) + 1.0) / 2.0, 1e-12);
}
TEST(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,
/*min_num_per_window=*/1);
h.Add(1);
std::this_thread::sleep_for(std::chrono::milliseconds(3));
h.Add(2); // should advance to next window.
// Cross max_span (4ms) relative to the current window start.
// max_span = 4ms here.
std::this_thread::sleep_for(std::chrono::milliseconds(7));
h.Add(3); // should reset.
HistogramStats s = h.GetStats();
EXPECT_EQ(s.count, 1);
EXPECT_DOUBLE_EQ(s.min, 3);
EXPECT_DOUBLE_EQ(s.max, 3);
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,
/*min_num_per_window=*/1);
h.Add(1);
h.Add(2);
h.Add(3);
h.Add(4);
const HistogramStats before = h.GetStats();
auto cloned_base = h.Clone();
auto cloned = std::dynamic_pointer_cast<HistogramWindowingImpl>(cloned_base);
ASSERT_TRUE(cloned != nullptr);
const HistogramStats cloned_before = cloned->GetStats();
EXPECT_EQ(cloned_before.count, before.count);
EXPECT_DOUBLE_EQ(cloned_before.sum, before.sum);
EXPECT_DOUBLE_EQ(cloned_before.min, before.min);
EXPECT_DOUBLE_EQ(cloned_before.max, before.max);
EXPECT_DOUBLE_EQ(cloned_before.average, before.average);
EXPECT_DOUBLE_EQ(cloned_before.stddev, before.stddev);
EXPECT_DOUBLE_EQ(cloned_before.p50, before.p50);
EXPECT_DOUBLE_EQ(cloned_before.p90, before.p90);
EXPECT_DOUBLE_EQ(cloned_before.p95, before.p95);
EXPECT_DOUBLE_EQ(cloned_before.p99, before.p99);
EXPECT_DOUBLE_EQ(cloned_before.p999, before.p999);
// Mutating original should not affect cloned.
h.Add(100);
const HistogramStats after_original = h.GetStats();
const HistogramStats after_clone = cloned->GetStats();
EXPECT_EQ(after_clone.count, cloned_before.count);
EXPECT_EQ(after_original.count, cloned_before.count + 1);
// Mutating cloned should not affect original.
cloned->Add(200);
const HistogramStats after_clone2 = cloned->GetStats();
const HistogramStats after_original2 = h.GetStats();
EXPECT_EQ(after_original2.count, after_original.count);
EXPECT_EQ(after_clone2.count, cloned_before.count + 1);
}
} // namespace paimon::test