mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-10 09:06:36 +00:00
fix(perf): recorder review fixes -- period adoption, timed frames, snapshot copy, watchdog stop
From review (CodeRabbit): - The first refresh period is adopted only once two windows in a row agree. One loaded startup window, most frames a refresh late, used to fix a period twice the real one for the life of the process. - totals.timed_frames counts the frames judged against a known period, and frame_soak rates late and early frames over it. Before, frames seen before any period was known counted as on time, so a short run could PASS having judged nothing; now it has no verdict. - snapshot() copies totals. render_bench kept the snapshot object and differenced it against a later one sharing the same live dict, so every graded run reported zero frames. - held_refresh_hz uses the p50 bucket's midpoint, not its upper edge (a 1-2% low bias, the size of the idle-vs-held gap it exists to show). - StallWatchdog.stop() and FrameTimingRecorder.close(); DisplayManager's cleanup() calls it, so the watchdog no longer outlives its manager. - A stall that outlasts the 2s scroll-state expiry still gets its end reported (tracking is dropped only past GAP_SECONDS). - test: EMULATOR through monkeypatch; docs: the 1s resume rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+14
-5
@@ -130,6 +130,9 @@ def build_report(before, after, preview: bool) -> Dict[str, Any]:
|
||||
delta = diff(before, after)
|
||||
totals = delta["totals"]
|
||||
frames = totals["scroll_frames"]
|
||||
# The rates are over frames judged against a known refresh period. Stats
|
||||
# from a recorder that predates the count fall back to every frame.
|
||||
timed = totals.get("timed_frames", frames) if "timed_frames" in totals else frames
|
||||
hours = delta["seconds"] / 3600.0 if delta["seconds"] > 0 else 0.0
|
||||
bucket_ms = after.get("bucket_ms", 0.25)
|
||||
report = {
|
||||
@@ -141,12 +144,13 @@ def build_report(before, after, preview: bool) -> Dict[str, Any]:
|
||||
"scroll_frames": frames,
|
||||
"static_frames": totals["static_frames"],
|
||||
"late_frames": totals["late_frames"],
|
||||
"late_pct": round(100.0 * totals["late_frames"] / frames, 3) if frames else None,
|
||||
"timed_frames": timed,
|
||||
"late_pct": round(100.0 * totals["late_frames"] / timed, 3) if timed else None,
|
||||
"missed_refreshes": totals["missed_refreshes"],
|
||||
"late_by": totals["late_by"],
|
||||
"early_frames": totals.get("early_frames", 0),
|
||||
"early_pct": (round(100.0 * totals.get("early_frames", 0) / frames, 3)
|
||||
if frames else None),
|
||||
"early_pct": (round(100.0 * totals.get("early_frames", 0) / timed, 3)
|
||||
if timed else None),
|
||||
"freeze_by": totals.get("freeze_by", {}),
|
||||
"freezes": totals["freezes"],
|
||||
"freezes_per_hour": round(totals["freezes"] / hours, 1) if hours else None,
|
||||
@@ -161,8 +165,13 @@ def build_report(before, after, preview: bool) -> Dict[str, Any]:
|
||||
# bit-banging the panel and pushing frames at once); a widening gap between
|
||||
# the two is a render-cost regression even when nothing is late.
|
||||
typical = (report["timing_ms"].get("interval_per_hold") or {}).get("p50")
|
||||
report["held_refresh_hz"] = (round(1000.0 / typical, 1)
|
||||
if isinstance(typical, (int, float)) and typical else None)
|
||||
# percentiles() reports a bucket's upper edge; the midpoint is the better
|
||||
# estimate, and half a 0.25ms bucket is already ~1% at 100Hz -- the size
|
||||
# of the idle-vs-held gap this number exists to show.
|
||||
if isinstance(typical, (int, float)) and typical > bucket_ms / 2:
|
||||
report["held_refresh_hz"] = round(1000.0 / (typical - bucket_ms / 2), 1)
|
||||
else:
|
||||
report["held_refresh_hz"] = None
|
||||
return report
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user