Files
scylladb/test/boost/rolling_max_tracker_test.cc
Marcin Maliszkiewicz 5b2a07b408 utils: add rolling max tracker
We will use it later to track parser memory
usage via per query samples.

Tests runtime in dev: 1.6s
2026-03-12 08:56:41 +01:00

216 lines
6.2 KiB
C++

/*
* Copyright (C) 2025-present ScyllaDB
*/
/*
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
*/
#define BOOST_TEST_MODULE rolling_max_tracker
#include <boost/test/unit_test.hpp>
#include <algorithm>
#include <seastar/core/bitops.hh>
#include "utils/rolling_max_tracker.hh"
// Helper: compute the expected current_max for a given raw value.
// Mirrors the tracker's internal rounding: clamp to 1, take log2ceil,
// then raise back to a power of two.
static size_t rounded(size_t v) {
return size_t(1) << seastar::log2ceil(std::max(v, size_t(1)));
}
BOOST_AUTO_TEST_CASE(test_empty_tracker_returns_zero) {
utils::rolling_max_tracker tracker(10);
BOOST_REQUIRE_EQUAL(tracker.current_max(), 0u);
}
BOOST_AUTO_TEST_CASE(test_single_sample) {
utils::rolling_max_tracker tracker(10);
tracker.add_sample(100);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(100));
}
BOOST_AUTO_TEST_CASE(test_max_tracks_largest_in_window) {
utils::rolling_max_tracker tracker(10);
tracker.add_sample(5);
tracker.add_sample(20);
tracker.add_sample(10);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(20));
}
BOOST_AUTO_TEST_CASE(test_increasing_samples) {
utils::rolling_max_tracker tracker(5);
for (size_t i = 1; i <= 10; ++i) {
tracker.add_sample(i);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(i));
}
}
BOOST_AUTO_TEST_CASE(test_decreasing_samples) {
utils::rolling_max_tracker tracker(5);
tracker.add_sample(100);
tracker.add_sample(90);
tracker.add_sample(80);
tracker.add_sample(70);
tracker.add_sample(60);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(100));
tracker.add_sample(50);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(90));
tracker.add_sample(40);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(80));
tracker.add_sample(30);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(70));
tracker.add_sample(20);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(60));
}
BOOST_AUTO_TEST_CASE(test_max_expires_from_window) {
utils::rolling_max_tracker tracker(3);
tracker.add_sample(100);
tracker.add_sample(1);
tracker.add_sample(2);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(100));
tracker.add_sample(3);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(3));
}
BOOST_AUTO_TEST_CASE(test_new_max_replaces_smaller_entries) {
utils::rolling_max_tracker tracker(5);
tracker.add_sample(10);
tracker.add_sample(5);
tracker.add_sample(3);
tracker.add_sample(1);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(10));
tracker.add_sample(50);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(50));
tracker.add_sample(1);
tracker.add_sample(1);
tracker.add_sample(1);
tracker.add_sample(1);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(50));
tracker.add_sample(1);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(1));
}
BOOST_AUTO_TEST_CASE(test_window_size_one) {
utils::rolling_max_tracker tracker(1);
tracker.add_sample(100);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(100));
tracker.add_sample(5);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(5));
tracker.add_sample(200);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(200));
}
BOOST_AUTO_TEST_CASE(test_window_size_two) {
utils::rolling_max_tracker tracker(2);
tracker.add_sample(100);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(100));
tracker.add_sample(5);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(100));
tracker.add_sample(200);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(200));
tracker.add_sample(10);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(200));
tracker.add_sample(10);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(10));
}
BOOST_AUTO_TEST_CASE(test_equal_values) {
utils::rolling_max_tracker tracker(5);
tracker.add_sample(42);
tracker.add_sample(42);
tracker.add_sample(42);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(42));
for (int i = 0; i < 20; ++i) {
tracker.add_sample(42);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(42));
}
tracker.add_sample(100);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(100));
}
BOOST_AUTO_TEST_CASE(test_staircase_pattern) {
utils::rolling_max_tracker tracker(6);
for (size_t i = 1; i <= 5; ++i) {
tracker.add_sample(i);
}
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(5));
tracker.add_sample(4);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(5));
tracker.add_sample(3);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(5));
tracker.add_sample(2);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(5));
tracker.add_sample(1);
BOOST_REQUIRE_EQUAL(tracker.current_max(), rounded(5));
}
BOOST_AUTO_TEST_CASE(test_zero_sample_clamped_to_one) {
utils::rolling_max_tracker tracker(3);
tracker.add_sample(0);
BOOST_REQUIRE_EQUAL(tracker.current_max(), 1);
tracker.add_sample(0);
tracker.add_sample(0);
BOOST_REQUIRE_EQUAL(tracker.current_max(), 1);
}
BOOST_AUTO_TEST_CASE(test_current_max_is_upper_bound) {
// For any value, current_max() >= value (never underestimates).
utils::rolling_max_tracker tracker(1);
for (size_t v = 1; v <= 1024; ++v) {
tracker.add_sample(v);
BOOST_REQUIRE_GE(tracker.current_max(), v);
// And at most 2x the value
BOOST_REQUIRE_LE(tracker.current_max(), 2 * v);
}
}
BOOST_AUTO_TEST_CASE(test_sliding_window_correctness) {
const size_t window = 7;
const size_t n = 100;
utils::rolling_max_tracker tracker(window);
std::vector<size_t> values;
values.reserve(n);
for (size_t i = 0; i < n; ++i) {
values.push_back((i * 37 + 13) % 50);
}
for (size_t i = 0; i < n; ++i) {
tracker.add_sample(values[i]);
size_t start = (i + 1 > window) ? (i + 1 - window) : 0;
size_t expected_max = 0;
for (size_t j = start; j <= i; ++j) {
expected_max = std::max(expected_max, rounded(values[j]));
}
BOOST_REQUIRE_EQUAL(tracker.current_max(), expected_max);
}
}