Compare commits

...
Author SHA1 Message Date
ChuckBuildsandClaude Opus 5 a4290e8a28 perf(vegas): report the frame rate when it is worth reporting
Two Vegas telemetry lines were 37% of a running rig's entire log volume:
"Vegas FPS" every five seconds and "Scroll progress" on its own five-second
timer, 712 lines in half an hour, every one a journal write to an SD card.

The FPS line is the interesting one, because almost none of it was news.
Measured over two hours on that rig: 1410 samples, 98.5% of them within 10%
of target. What the other 1.5% contained was a reading of 8.6fps against a
target of 60 -- a real stall, sitting invisible inside 1389 lines that read
"59.6".

So it now reports at INFO when the frame rate falls short of target, when it
recovers from a shortfall, and on a five-minute heartbeat so a healthy
marquee still shows a pulse. Everything else drops to debug.

Replaying the same two hours of real samples through the committed logic:
1410 -> 53 INFO lines, a 96% reduction, and all 21 degraded samples are
retained, worst reading included. The signal survives; the wall of "fine"
does not.

Scroll progress is demoted outright. It reports how far along a marquee is,
which is what you turn debug on to watch, not something an operator needs in
the journal on a device that scrolls all day.

543 vegas and scroll tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STMbQE4YctTacQXfbYqKuW
2026-08-20 20:26:34 -04:00
4 changed files with 42 additions and 8 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

+5 -1
View File
@@ -328,7 +328,11 @@ class ScrollHelper:
elapsed_time = current_time - (self.scroll_start_time or current_time) elapsed_time = current_time - (self.scroll_start_time or current_time)
# The image already includes display_width padding, so we only need total_scroll_width # The image already includes display_width padding, so we only need total_scroll_width
required_total_distance = self.total_scroll_width required_total_distance = self.total_scroll_width
self.logger.info( # Progress telemetry, emitted every few seconds for the whole of
# every scroll. It says how far along a marquee is, which is what
# you turn debug on to watch and not something an operator needs
# in the journal on a device that scrolls all day.
self.logger.debug(
"Scroll progress: elapsed=%.2fs, target=%.2fs, total_scrolled=%.0f/%d px (%.1f%%)", "Scroll progress: elapsed=%.2fs, target=%.2fs, total_scrolled=%.0f/%d px (%.1f%%)",
elapsed_time, elapsed_time,
self.calculated_duration, self.calculated_duration,
+37 -7
View File
@@ -31,6 +31,14 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
#: A frame rate this close to target is not news; below it is.
_FPS_HEALTHY_FRACTION = 0.9
#: A healthy marquee still reports this often, so silence means stopped
#: rather than fine.
_FPS_HEARTBEAT_INTERVAL = 300.0
def _percentile(ordered: List[float], fraction: float) -> float: def _percentile(ordered: List[float], fraction: float) -> float:
"""Nearest-rank percentile of an already-sorted list. """Nearest-rank percentile of an already-sorted list.
@@ -395,7 +403,9 @@ class VegasModeCoordinator:
duration = self.render_pipeline.get_dynamic_duration() duration = self.render_pipeline.get_dynamic_duration()
start_time = time.time() start_time = time.time()
frame_count = 0 frame_count = 0
fps_log_interval = 5.0 # Log FPS every 5 seconds fps_log_interval = 5.0 # Sample FPS every 5 seconds
last_fps_health_log = 0.0 # last INFO-level report
was_degraded = False # so the recovery is reported too
last_fps_log_time = start_time last_fps_log_time = start_time
fps_frame_count = 0 fps_frame_count = 0
# A mean hides stutter completely. At 120fps a five-second window is # A mean hides stutter completely. At 120fps a five-second window is
@@ -448,16 +458,36 @@ class VegasModeCoordinator:
frame_count += 1 frame_count += 1
fps_frame_count += 1 fps_frame_count += 1
# Periodic FPS logging # Periodic FPS logging. Reported at INFO only when the frame rate
# is actually worth an operator's attention -- a shortfall against
# target, or the recovery from one -- with a slow heartbeat so a
# healthy marquee still shows a pulse.
#
# Measured over two hours on a running rig: 1410 samples, 98.5%
# of them within 10% of target. The 1.5% that were not included a
# reading of 8.6fps against a target of 60 -- a real stall, and
# completely invisible inside 1389 lines reading "59.6".
current_time = time.time() current_time = time.time()
if current_time - last_fps_log_time >= fps_log_interval: if current_time - last_fps_log_time >= fps_log_interval:
fps = fps_frame_count / (current_time - last_fps_log_time) fps = fps_frame_count / (current_time - last_fps_log_time)
p99 = _percentile(sorted(frame_times), 0.99) p99 = _percentile(sorted(frame_times), 0.99)
logger.info( target = self.vegas_config.target_fps
"Vegas FPS: %.1f (target: %d, frames: %d) p99 %.1fms worst %.1fms", degraded = target > 0 and fps < target * _FPS_HEALTHY_FRACTION
fps, self.vegas_config.target_fps, fps_frame_count, due = current_time - last_fps_health_log >= _FPS_HEARTBEAT_INTERVAL
p99 * 1000.0, frame_worst * 1000.0 if degraded or was_degraded or due:
) logger.info(
"Vegas FPS: %.1f (target: %d, frames: %d) p99 %.1fms worst %.1fms",
fps, target, fps_frame_count,
p99 * 1000.0, frame_worst * 1000.0
)
last_fps_health_log = current_time
else:
logger.debug(
"Vegas FPS: %.1f (target: %d, frames: %d) p99 %.1fms worst %.1fms",
fps, target, fps_frame_count,
p99 * 1000.0, frame_worst * 1000.0
)
was_degraded = degraded
last_fps_log_time = current_time last_fps_log_time = current_time
fps_frame_count = 0 fps_frame_count = 0
frame_worst = 0.0 frame_worst = 0.0