mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-24 10:23:02 -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
87
cockatrice/src/client/lag_monitor.h
Normal file
87
cockatrice/src/client/lag_monitor.h
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* @file lag_monitor.h
|
||||
* @ingroup Client
|
||||
*/
|
||||
|
||||
#ifndef LAG_MONITOR_H
|
||||
#define LAG_MONITOR_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QElapsedTimer>
|
||||
#include <QList>
|
||||
#include <QLoggingCategory>
|
||||
#include <QObject>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(LagMonitorLog, "lag_monitor");
|
||||
|
||||
class QEvent;
|
||||
class QTimer;
|
||||
|
||||
/**
|
||||
* @brief Detects main-thread event loop stalls ("UI freezes") from the inside.
|
||||
*
|
||||
* A timer is expected to fire every TICK_INTERVAL_MS of wall time. When the
|
||||
* observed gap greatly exceeds that interval, some other task blocked the
|
||||
* event loop for roughly the overshooting duration. This is what separates
|
||||
* "my client froze" from "the network is lagging" in user reports.
|
||||
*
|
||||
* Gaps that span an application state change (suspend, minimize, focus
|
||||
* loss) are discarded, and implausibly huge gaps are dropped, so operating
|
||||
* system power events do not fabricate stalls. This handling is load-bearing
|
||||
* on Windows, where the monotonic clock used by Qt counts sleep time.
|
||||
*
|
||||
* Healthy operation costs one timer wakeup per tick and two integer
|
||||
* comparisons. Allocations happen only when a stall is actually recorded.
|
||||
*/
|
||||
class LagMonitor : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct StallRecord
|
||||
{
|
||||
qint64 timestampMsSinceEpoch = 0; ///< when the stalled period ended
|
||||
qint64 durationMs = 0; ///< approximate length of the freeze; measured tick to tick, so it can exceed the true
|
||||
///< stall by up to TICK_INTERVAL_MS
|
||||
};
|
||||
|
||||
static constexpr int TICK_INTERVAL_MS = 500;
|
||||
static constexpr int STALL_THRESHOLD_MS = 2000;
|
||||
static constexpr int MAX_RECORDED_STALLS = 32;
|
||||
|
||||
/// Gaps beyond this are treated as suspend artifacts rather than stalls.
|
||||
static constexpr qint64 MAX_PLAUSIBLE_STALL_MS = 600000;
|
||||
|
||||
explicit LagMonitor(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Stalls recorded during this session, oldest first.
|
||||
*
|
||||
* Intended consumers are log output and the diagnostics export. The list
|
||||
* holds at most MAX_RECORDED_STALLS entries.
|
||||
*/
|
||||
QList<StallRecord> recentStalls() const;
|
||||
|
||||
void clearStalls();
|
||||
|
||||
/**
|
||||
* @brief Feeds a measured tick-to-tick gap through the detection logic.
|
||||
*
|
||||
* Split out of checkTick so threshold, plausibility, and trim behavior
|
||||
* stay unit-testable without real timing.
|
||||
*/
|
||||
void recordGap(qint64 gapMs);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void checkTick();
|
||||
|
||||
private:
|
||||
QTimer *timer;
|
||||
QElapsedTimer tickClock; ///< monotonic clock, so wall clock steps do not fabricate stalls
|
||||
QList<StallRecord> stalls;
|
||||
};
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue