This is an automated email from the ASF dual-hosted git repository.
chenBright pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git
The following commit(s) were added to refs/heads/master by this push:
new 4d5bb43a Fix flaky latency recorder QPS test (#3651)
4d5bb43a is described below
commit 4d5bb43a2b0cd5f32076bdde38857f9aa2afe949
Author: nkysg <[email protected]>
AuthorDate: Mon Nov 2 13:33:46 2026 +0800
Fix flaky latency recorder QPS test (#3651)
* Fix flaky latency recorder QPS test
* Fix latency recorder QPS window test coverage
* Stabilize latency recorder QPS test
* Stabilize QPS tests with fixed sampler histories
* drop friend class
---
src/bvar/latency_recorder.cpp | 12 ++--
src/bvar/latency_recorder.h | 4 ++
test/bvar_recorder_unittest.cpp | 140 +++++++++++++++++++++++++++++-----------
3 files changed, 114 insertions(+), 42 deletions(-)
diff --git a/src/bvar/latency_recorder.cpp b/src/bvar/latency_recorder.cpp
index 8c9379d8..6db4764b 100644
--- a/src/bvar/latency_recorder.cpp
+++ b/src/bvar/latency_recorder.cpp
@@ -113,10 +113,10 @@ int CDF::describe_series(
return 0;
}
-// Return random int value with expectation = `dval'
-static int64_t double_to_random_int(double dval) {
+// Return random int value with expectation = `dval'.
+int64_t double_to_random_int(double dval, double random) {
int64_t ival = static_cast<int64_t>(dval);
- if (dval > ival + butil::fast_rand_double()) {
+ if (dval > ival + random) {
ival += 1;
}
return ival;
@@ -129,7 +129,8 @@ static int64_t get_window_recorder_qps(void* arg) {
if (s.time_us <= 0) {
return 0;
}
- return double_to_random_int(s.data.num * 1000000.0 / s.time_us);
+ return double_to_random_int(
+ s.data.num * 1000000.0 / s.time_us, butil::fast_rand_double());
}
static int64_t get_recorder_count(void* arg) {
@@ -209,7 +210,8 @@ int64_t LatencyRecorder::qps(time_t window_size) const {
if (s.time_us <= 0) {
return 0;
}
- return detail::double_to_random_int(s.data.num * 1000000.0 / s.time_us);
+ return detail::double_to_random_int(
+ s.data.num * 1000000.0 / s.time_us, butil::fast_rand_double());
}
int LatencyRecorder::expose(const butil::StringPiece& prefix1,
diff --git a/src/bvar/latency_recorder.h b/src/bvar/latency_recorder.h
index 1cc09f51..8c820600 100644
--- a/src/bvar/latency_recorder.h
+++ b/src/bvar/latency_recorder.h
@@ -33,6 +33,10 @@ typedef Window<IntRecorder, SERIES_IN_SECOND> RecorderWindow;
typedef Window<Maxer<int64_t>, SERIES_IN_SECOND> MaxWindow;
typedef Window<Percentile, SERIES_IN_SECOND> PercentileWindow;
+// Round |dval| up with probability equal to its fractional part. |random|
+// must be in [0, 1); keeping it explicit makes the rule deterministic to test.
+int64_t double_to_random_int(double dval, double random);
+
// NOTE: Always use int64_t in the interfaces no matter what the impl. is.
class CDF : public Variable {
diff --git a/test/bvar_recorder_unittest.cpp b/test/bvar_recorder_unittest.cpp
index d9d3fd7f..bf654463 100644
--- a/test/bvar_recorder_unittest.cpp
+++ b/test/bvar_recorder_unittest.cpp
@@ -19,15 +19,63 @@
#include <pthread.h> // pthread_*
+#include <cmath>
#include <cstddef>
#include <memory>
#include <iostream>
+#include <vector>
#include "butil/time.h"
#include "butil/macros.h"
#include "bvar/recorder.h"
#include "bvar/latency_recorder.h"
#include <gtest/gtest.h>
+namespace bvar {
+namespace detail {
+
+// Give the real QPS APIs a fixed history without racing the sampling thread.
+// The recorder's original sampler remains registered and is restored before
+// destruction. Only this replacement sampler's queue is modified by the test.
+class LatencyRecorderTestPeer {
+public:
+ LatencyRecorderTestPeer(RecorderWindow& window, IntRecorder& recorder,
+ int count)
+ : _window(window), _original(window._sampler),
+ _sampler(new IntRecorder::sampler_type(&recorder)) {
+ CHECK_EQ(0, _sampler->set_window_size(4));
+ _sampler->take_sample(); // Allocate room for five snapshots.
+ _sampler->_q.clear();
+ for (int i = 0; i <= 4; ++i) {
+ CHECK(_sampler->_q.push(Sample<Stat>(
+ i == 4 ? Stat(count, count) : Stat(), i * 1000000L)));
+ }
+ _sampler->add_borrower();
+ _window._sampler = _sampler.get();
+ // Keep the original borrow alive until it is restored.
+ }
+
+ ~LatencyRecorderTestPeer() {
+ _window._sampler = _original;
+ _sampler->remove_borrower();
+ }
+
+ void advance() {
+ Sample<Stat> next = *_sampler->_q.bottom();
+ next.time_us += 1000000L;
+ _sampler->_q.elim_push(next);
+ }
+
+private:
+ LatencyRecorderTestPeer(const LatencyRecorderTestPeer&) = delete;
+ LatencyRecorderTestPeer& operator=(const LatencyRecorderTestPeer&) =
delete;
+ RecorderWindow& _window;
+ IntRecorder::sampler_type* _original;
+ std::unique_ptr<IntRecorder::sampler_type> _sampler;
+};
+
+} // namespace detail
+} // namespace bvar
+
namespace {
#if !WITH_BABYLON_COUNTER
TEST(RecorderTest, test_complement) {
@@ -222,6 +270,16 @@ public:
bool get_latency_sample(bvar::detail::Sample<bvar::Stat>* sample) const {
return _latency_window.get_span(sample);
}
+
+ bool get_latency_sample(time_t window_size,
+ bvar::detail::Sample<bvar::Stat>* sample) const {
+ return _latency_window.get_span(window_size, sample);
+ }
+
+ std::unique_ptr<bvar::detail::LatencyRecorderTestPeer> fixed_history(int
count) {
+ return std::unique_ptr<bvar::detail::LatencyRecorderTestPeer>(
+ new bvar::detail::LatencyRecorderTestPeer(_latency_window,
_latency, count));
+ }
};
TEST(RecorderTest, latency_recorder_window_statistics) {
@@ -276,44 +334,52 @@ TEST(RecorderTest, latency_recorder_window_statistics) {
}
TEST(RecorderTest, latency_recorder_qps_accuracy) {
- bvar::LatencyRecorder lr1(2); // set windows size to 2s
- bvar::LatencyRecorder lr2(2);
- bvar::LatencyRecorder lr3(2);
- bvar::LatencyRecorder lr4(2);
- usleep(3000000); // wait sampler to sample 3 times
-
- auto write = [](bvar::LatencyRecorder& lr, int times) {
- for (int i = 0; i < times; ++i) {
- lr << 1;
- }
- };
- write(lr1, 10);
- write(lr2, 11);
- write(lr3, 3);
- write(lr4, 1);
- usleep(1000000); // wait sampler to sample 1 time
-
- auto read = [](bvar::LatencyRecorder& lr, double exp_qps, int window_size
= 0) {
- int64_t qps_sum = 0;
- int64_t exp_qps_int = (int64_t)exp_qps;
- for (int i = 0; i < 1000; ++i) {
- int64_t qps = window_size ? lr.qps(window_size): lr.qps();
- EXPECT_GE(qps, exp_qps_int - 1);
- EXPECT_LE(qps, exp_qps_int + 1);
- qps_sum += qps;
+ for (int count : {10, 11, 3, 1}) {
+ for (time_t window : {2, 3}) {
+ SCOPED_TRACE(::testing::Message() << "count=" << count
+ << " window=" << window);
+ // Default qps() uses two seconds; explicit qps(3) must override
+ // a four-second default.
+ LatencyRecorderWithWindowSample lr(window == 2 ? 2 : 4);
+ auto history = lr.fixed_history(count);
+ bvar::detail::Sample<bvar::Stat> sample;
+ ASSERT_TRUE(lr.get_latency_sample(window, &sample));
+ ASSERT_EQ(count, sample.data.num);
+ ASSERT_EQ(window * 1000000L, sample.time_us);
+
+ const double expected = count / static_cast<double>(window);
+ int64_t total = 0;
+ const int trials = 10000;
+ for (int i = 0; i < trials; ++i) {
+ const int64_t value = window == 2 ? lr.qps() : lr.qps(window);
+ ASSERT_GE(value, static_cast<int64_t>(std::floor(expected)));
+ ASSERT_LE(value, static_cast<int64_t>(std::ceil(expected)));
+ total += value;
+ }
+ // Exercise rounding through the public APIs, including low QPS.
+ // For independent uniform draws, Hoeffding bounds the chance of
+ // this tolerance failing at less than 2*exp(-50) per case.
+ EXPECT_NEAR(expected, total / static_cast<double>(trials), 0.05);
+
+ // Move the batch out of the requested window. Duration stays the
+ // same while count becomes zero, reproducing the rollover case.
+ for (time_t i = 0; i < window; ++i) {
+ history->advance();
+ }
+ ASSERT_TRUE(lr.get_latency_sample(window, &sample));
+ EXPECT_EQ(0, sample.data.num);
+ EXPECT_EQ(window * 1000000L, sample.time_us);
+ EXPECT_EQ(0, window == 2 ? lr.qps() : lr.qps(window));
}
- double err = fabs(qps_sum / 1000.0 - exp_qps);
- return err;
- };
- ASSERT_GT(0.1, read(lr1, 10/2.0));
- ASSERT_GT(0.1, read(lr2, 11/2.0));
- ASSERT_GT(0.1, read(lr3, 3/2.0));
- ASSERT_GT(0.1, read(lr4, 1/2.0));
-
- ASSERT_GT(0.1, read(lr1, 10/3.0, 3));
- ASSERT_GT(0.1, read(lr2, 11/3.0, 3));
- ASSERT_GT(0.1, read(lr3, 3/3.0, 3));
- ASSERT_GT(0.1, read(lr4, 1/3.0, 3));
+ }
+}
+
+TEST(RecorderTest, latency_recorder_qps_rounding) {
+ // A random value equal to the fractional part rounds down.
+ EXPECT_EQ(10, bvar::detail::double_to_random_int(10.25, 0.25));
+ EXPECT_EQ(11, bvar::detail::double_to_random_int(10.25, 0.24));
+ EXPECT_EQ(10, bvar::detail::double_to_random_int(10.0, 0.0));
+ EXPECT_EQ(11, bvar::detail::double_to_random_int(10.75, 0.1));
}
} // namespace
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]