mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 00:55:09 -07:00
[Network] Measure real server round-trip times (#7153)
* [Network] Measure real server round-trip times Time each command container from send to response with QElapsedTimer, aggregate samples in a fixed-size ring buffer (last/median/p95/max), and emit aggregated pingStatsUpdated at most once per second so the hot path stays free of signal traffic. Stats are cleared on disconnect. Forward the signal through ConnectionController for UI consumers. Unit-tested in latency_tracker_test. Took 37 minutes Took 4 minutes # Commit time for manual adjustment: # Took 8 minutes * Move params to struct, more informative debug Took 56 seconds Took 53 seconds Took 2 minutes Took 33 seconds --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
157e7022cd
commit
b91e872f5f
12 changed files with 389 additions and 2 deletions
|
|
@ -12,6 +12,7 @@ add_test(NAME server_card_counter_test COMMAND server_card_counter_test)
|
|||
add_test(NAME server_counter_test COMMAND server_counter_test)
|
||||
add_test(NAME server_rate_limiter_test COMMAND server_rate_limiter_test)
|
||||
add_test(NAME warning_categories_test COMMAND warning_categories_test)
|
||||
add_test(NAME latency_tracker_test COMMAND latency_tracker_test)
|
||||
|
||||
add_test(NAME deck_hash_performance_test COMMAND deck_hash_performance_test)
|
||||
set_tests_properties(deck_hash_performance_test PROPERTIES TIMEOUT 5)
|
||||
|
|
@ -29,6 +30,7 @@ add_executable(server_card_counter_test server_card_counter_test.cpp)
|
|||
add_executable(server_counter_test server_counter_test.cpp)
|
||||
add_executable(server_rate_limiter_test server_rate_limiter_test.cpp)
|
||||
add_executable(warning_categories_test warning_categories_test.cpp)
|
||||
add_executable(latency_tracker_test latency_tracker_test.cpp)
|
||||
|
||||
find_package(GTest)
|
||||
|
||||
|
|
@ -66,6 +68,7 @@ if(NOT GTEST_FOUND)
|
|||
add_dependencies(server_counter_test gtest)
|
||||
add_dependencies(server_rate_limiter_test gtest)
|
||||
add_dependencies(warning_categories_test gtest)
|
||||
add_dependencies(latency_tracker_test gtest)
|
||||
endif()
|
||||
|
||||
include_directories(${GTEST_INCLUDE_DIRS})
|
||||
|
|
@ -100,6 +103,9 @@ target_link_libraries(
|
|||
target_link_libraries(
|
||||
warning_categories_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
|
||||
)
|
||||
target_link_libraries(
|
||||
latency_tracker_test libcockatrice_network Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
|
||||
)
|
||||
|
||||
add_subdirectory(card_zone_algorithms)
|
||||
add_subdirectory(carddatabase)
|
||||
|
|
|
|||
149
tests/latency_tracker_test.cpp
Normal file
149
tests/latency_tracker_test.cpp
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <libcockatrice/network/client/abstract/latency_tracker.h>
|
||||
|
||||
TEST(LatencyTrackerTest, EmptyTrackerYieldsZeroedStats)
|
||||
{
|
||||
LatencyTracker tracker;
|
||||
|
||||
const auto stats = tracker.stats();
|
||||
|
||||
EXPECT_EQ(0, stats.sampleCount);
|
||||
EXPECT_EQ(0, stats.lastMs);
|
||||
EXPECT_EQ(0, stats.medianMs);
|
||||
EXPECT_EQ(0, stats.p95Ms);
|
||||
EXPECT_EQ(0, stats.maxMs);
|
||||
}
|
||||
|
||||
TEST(LatencyTrackerTest, SingleSampleIsEveryStatistic)
|
||||
{
|
||||
LatencyTracker tracker;
|
||||
tracker.addSample(42);
|
||||
|
||||
const auto stats = tracker.stats();
|
||||
|
||||
EXPECT_EQ(1, stats.sampleCount);
|
||||
EXPECT_EQ(42, stats.lastMs);
|
||||
EXPECT_EQ(42, stats.medianMs);
|
||||
EXPECT_EQ(42, stats.p95Ms);
|
||||
EXPECT_EQ(42, stats.maxMs);
|
||||
}
|
||||
|
||||
TEST(LatencyTrackerTest, OddSizedWindowMedianAndP95)
|
||||
{
|
||||
LatencyTracker tracker;
|
||||
for (qint64 ms : {qint64(50), qint64(10), qint64(30), qint64(20), qint64(40)}) {
|
||||
tracker.addSample(ms);
|
||||
}
|
||||
|
||||
const auto stats = tracker.stats();
|
||||
|
||||
EXPECT_EQ(5, stats.sampleCount);
|
||||
EXPECT_EQ(40, stats.lastMs);
|
||||
EXPECT_EQ(30, stats.medianMs);
|
||||
// nearest-rank p95 of 5 samples: ceil(4.75) = 5th smallest
|
||||
EXPECT_EQ(50, stats.p95Ms);
|
||||
EXPECT_EQ(50, stats.maxMs);
|
||||
}
|
||||
|
||||
TEST(LatencyTrackerTest, EvenSizedWindowMedianIsAverageOfMiddleTwo)
|
||||
{
|
||||
LatencyTracker tracker;
|
||||
for (qint64 ms : {qint64(10), qint64(20), qint64(30), qint64(40)}) {
|
||||
tracker.addSample(ms);
|
||||
}
|
||||
|
||||
const auto stats = tracker.stats();
|
||||
|
||||
EXPECT_EQ(4, stats.sampleCount);
|
||||
EXPECT_EQ(25, stats.medianMs);
|
||||
// nearest-rank p95 of 4 samples: ceil(3.8) = 4th smallest
|
||||
EXPECT_EQ(40, stats.p95Ms);
|
||||
}
|
||||
|
||||
TEST(LatencyTrackerTest, WindowEvictsOldestSamples)
|
||||
{
|
||||
LatencyTracker tracker;
|
||||
for (int i = 0; i <= 99; ++i) {
|
||||
tracker.addSample(i);
|
||||
}
|
||||
|
||||
const auto stats = tracker.stats();
|
||||
|
||||
EXPECT_EQ(LatencyTracker::WindowSize, stats.sampleCount);
|
||||
EXPECT_EQ(99, stats.lastMs);
|
||||
EXPECT_EQ(99, stats.maxMs);
|
||||
// window now contains 36..99 (64 samples)
|
||||
EXPECT_EQ(67, stats.medianMs); // (67 + 68) / 2 with integer division
|
||||
EXPECT_EQ(96, stats.p95Ms); // ceil(0.95 * 64) - 1 = index 60 -> 36 + 60
|
||||
}
|
||||
|
||||
TEST(LatencyTrackerTest, ClearResetsAllState)
|
||||
{
|
||||
LatencyTracker tracker;
|
||||
for (int i = 0; i <= 99; ++i) {
|
||||
tracker.addSample(i);
|
||||
}
|
||||
|
||||
tracker.clear();
|
||||
const auto cleared = tracker.stats();
|
||||
EXPECT_EQ(0, cleared.sampleCount);
|
||||
|
||||
tracker.addSample(7);
|
||||
const auto stats = tracker.stats();
|
||||
EXPECT_EQ(1, stats.sampleCount);
|
||||
EXPECT_EQ(7, stats.lastMs);
|
||||
EXPECT_EQ(7, stats.medianMs);
|
||||
}
|
||||
|
||||
TEST(LatencyTrackerTest, LastSampleSurvivesWraparound)
|
||||
{
|
||||
LatencyTracker tracker;
|
||||
for (int i = 0; i < LatencyTracker::WindowSize; ++i) {
|
||||
tracker.addSample(i);
|
||||
}
|
||||
tracker.addSample(1000);
|
||||
|
||||
EXPECT_EQ(1000, tracker.stats().lastMs);
|
||||
}
|
||||
|
||||
TEST(LatencyTrackerTest, RecentSamplesAreChronologicalOldestFirst)
|
||||
{
|
||||
LatencyTracker tracker;
|
||||
for (qint64 ms : {qint64(50), qint64(10), qint64(30)}) {
|
||||
tracker.addSample(ms);
|
||||
}
|
||||
|
||||
const QList<qint64> samples = tracker.recentSamples();
|
||||
|
||||
EXPECT_EQ((QList<qint64>{50, 10, 30}), samples);
|
||||
}
|
||||
|
||||
TEST(LatencyTrackerTest, RecentSamplesFollowRingBufferWraparound)
|
||||
{
|
||||
LatencyTracker tracker;
|
||||
for (int i = 0; i <= 99; ++i) {
|
||||
tracker.addSample(i);
|
||||
}
|
||||
|
||||
const QList<qint64> samples = tracker.recentSamples();
|
||||
|
||||
ASSERT_EQ(LatencyTracker::WindowSize, samples.size());
|
||||
EXPECT_EQ(36, samples.first());
|
||||
EXPECT_EQ(99, samples.last());
|
||||
}
|
||||
|
||||
TEST(LatencyTrackerTest, RecentSamplesEmptyOnFreshAndClearedTracker)
|
||||
{
|
||||
LatencyTracker tracker;
|
||||
EXPECT_TRUE(tracker.recentSamples().isEmpty());
|
||||
|
||||
tracker.addSample(5);
|
||||
tracker.clear();
|
||||
EXPECT_TRUE(tracker.recentSamples().isEmpty());
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue