mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 00:55:09 -07:00
[Client] Detect main-thread event loop stalls (#7155)
* [Client] Detect main-thread event loop stalls LagMonitor ticks the GUI event loop every 500 ms and records gaps beyond 2 s as stalls, warning with their duration and keeping a bounded ring of recent records for diagnostics. Measurement uses a monotonic QElapsedTimer so wall-clock steps and suspend do not fabricate stalls. Recorded timestamps stay in wall time for correlating with user reports. Took 1 minute Took 13 minutes Took 2 minutes * [Client] Rename LagMonitor constants to SCREAMING_SNAKE_CASE Took 15 minutes * [Client] Discard suspend-spanning gaps in LagMonitor Windows counts sleep time in its monotonic clock, so a suspend would fabricate one bogus stall per resume. Reset the clock on application state changes and drop implausibly huge gaps; extract recordGap() for testability. Took 3 minutes * [Client] Unit test LagMonitor stall recording Drives recordGap() directly to cover the threshold, plausibility cap, trim, and clear behavior without timing-dependent waits. Took 36 seconds --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
b91e872f5f
commit
88aa036f7e
6 changed files with 261 additions and 0 deletions
103
tests/lag_monitor_test.cpp
Normal file
103
tests/lag_monitor_test.cpp
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#include "client/lag_monitor.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QEvent>
|
||||
#include <QLoggingCategory>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/// Timestamps are taken at recording time; allow generous scheduler slack.
|
||||
constexpr qint64 TIMESTAMP_SLACK_MS = 10000;
|
||||
|
||||
} // namespace
|
||||
|
||||
class LagMonitorTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
LagMonitor monitor;
|
||||
};
|
||||
|
||||
TEST_F(LagMonitorTest, GapAtOrBelowThresholdIsIgnored)
|
||||
{
|
||||
monitor.recordGap(0);
|
||||
monitor.recordGap(LagMonitor::TICK_INTERVAL_MS);
|
||||
monitor.recordGap(LagMonitor::STALL_THRESHOLD_MS);
|
||||
|
||||
EXPECT_TRUE(monitor.recentStalls().isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(LagMonitorTest, GapAboveThresholdIsRecorded)
|
||||
{
|
||||
monitor.recordGap(LagMonitor::STALL_THRESHOLD_MS + 1);
|
||||
|
||||
const QList<LagMonitor::StallRecord> stalls = monitor.recentStalls();
|
||||
ASSERT_EQ(1, stalls.size());
|
||||
EXPECT_EQ(LagMonitor::STALL_THRESHOLD_MS + 1, stalls.first().durationMs);
|
||||
}
|
||||
|
||||
TEST_F(LagMonitorTest, RecordedTimestampIsFresh)
|
||||
{
|
||||
monitor.recordGap(LagMonitor::STALL_THRESHOLD_MS + 1);
|
||||
|
||||
const qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
ASSERT_EQ(1, monitor.recentStalls().size());
|
||||
EXPECT_LE(qAbs(monitor.recentStalls().first().timestampMsSinceEpoch - now), TIMESTAMP_SLACK_MS);
|
||||
}
|
||||
|
||||
TEST_F(LagMonitorTest, RecordsAreTrimmedToMaxOldestFirst)
|
||||
{
|
||||
for (int i = 0; i < LagMonitor::MAX_RECORDED_STALLS + 5; ++i) {
|
||||
monitor.recordGap(LagMonitor::STALL_THRESHOLD_MS + 1 + i);
|
||||
}
|
||||
|
||||
const QList<LagMonitor::StallRecord> stalls = monitor.recentStalls();
|
||||
ASSERT_EQ(LagMonitor::MAX_RECORDED_STALLS, stalls.size());
|
||||
EXPECT_EQ(LagMonitor::STALL_THRESHOLD_MS + 6, stalls.first().durationMs);
|
||||
EXPECT_EQ(LagMonitor::STALL_THRESHOLD_MS + 5 + LagMonitor::MAX_RECORDED_STALLS, stalls.last().durationMs);
|
||||
}
|
||||
|
||||
TEST_F(LagMonitorTest, GapAtPlausibilityCapIsKept)
|
||||
{
|
||||
monitor.recordGap(LagMonitor::MAX_PLAUSIBLE_STALL_MS);
|
||||
|
||||
ASSERT_EQ(1, monitor.recentStalls().size());
|
||||
EXPECT_EQ(LagMonitor::MAX_PLAUSIBLE_STALL_MS, monitor.recentStalls().first().durationMs);
|
||||
}
|
||||
|
||||
TEST_F(LagMonitorTest, GapBeyondPlausibilityCapIsDropped)
|
||||
{
|
||||
monitor.recordGap(LagMonitor::MAX_PLAUSIBLE_STALL_MS + 1);
|
||||
|
||||
EXPECT_TRUE(monitor.recentStalls().isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(LagMonitorTest, ClearStallsEmptiesList)
|
||||
{
|
||||
monitor.recordGap(LagMonitor::STALL_THRESHOLD_MS + 1);
|
||||
ASSERT_EQ(1, monitor.recentStalls().size());
|
||||
|
||||
monitor.clearStalls();
|
||||
|
||||
EXPECT_TRUE(monitor.recentStalls().isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(LagMonitorTest, ApplicationStateChangeDoesNotRecordAStall)
|
||||
{
|
||||
QObject probe;
|
||||
QEvent event(QEvent::ApplicationStateChange);
|
||||
|
||||
QCoreApplication::sendEvent(&probe, &event);
|
||||
|
||||
EXPECT_TRUE(monitor.recentStalls().isEmpty());
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QLoggingCategory::setFilterRules("lag_monitor.*=false");
|
||||
QCoreApplication app(argc, argv);
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue