Port WLED optimizations to KC loop: fix FPS metrics, add keepalive and auto-refresh test
- Fix KC fps_actual to use frame-to-frame timestamps (was inflated by measuring before sleep) - Add fps_potential, fps_current, frames_skipped, frames_keepalive metrics to KC loop - Add keepalive broadcast for static frames so WS clients stay in sync - Expose all KC metrics in get_kc_target_state() and update UI card to show 7 metrics - Add auto-refresh play/pause button to KC test lightbox (polls every ~1s) - Fix WebSocket color swatches computing hex from r,g,b when hex field is absent - Fix WebSocket auth crash by using get_config() instead of module-level config variable - Fix lightbox closing when clicking auto-refresh button (event bubbling) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1241,7 +1241,10 @@ class ProcessorManager:
|
||||
|
||||
frame_time = 1.0 / target_fps
|
||||
fps_samples: List[float] = []
|
||||
prev_frame_time_stamp = time.time()
|
||||
prev_capture = None # Track previous ScreenCapture for change detection
|
||||
last_broadcast_time = 0.0 # Timestamp of last WS broadcast (for keepalive)
|
||||
send_timestamps: collections.deque = collections.deque() # for fps_current
|
||||
|
||||
rectangles = state._resolved_rectangles
|
||||
|
||||
@@ -1264,7 +1267,18 @@ class ProcessorManager:
|
||||
|
||||
# Skip processing if the frame hasn't changed
|
||||
if capture is prev_capture:
|
||||
# Keepalive: re-broadcast last colors so WS clients stay in sync
|
||||
if state.latest_colors and (loop_start - last_broadcast_time) >= 1.0:
|
||||
await self._broadcast_kc_colors(target_id, state.latest_colors)
|
||||
last_broadcast_time = time.time()
|
||||
send_timestamps.append(last_broadcast_time)
|
||||
state.metrics.frames_keepalive += 1
|
||||
state.metrics.frames_skipped += 1
|
||||
# Update fps_current even on skip
|
||||
now_ts = time.time()
|
||||
while send_timestamps and send_timestamps[0] < now_ts - 1.0:
|
||||
send_timestamps.popleft()
|
||||
state.metrics.fps_current = len(send_timestamps)
|
||||
await asyncio.sleep(frame_time)
|
||||
continue
|
||||
prev_capture = capture
|
||||
@@ -1281,17 +1295,31 @@ class ProcessorManager:
|
||||
|
||||
# Broadcast to WebSocket clients
|
||||
await self._broadcast_kc_colors(target_id, colors)
|
||||
last_broadcast_time = time.time()
|
||||
send_timestamps.append(last_broadcast_time)
|
||||
|
||||
# Update metrics
|
||||
state.metrics.frames_processed += 1
|
||||
state.metrics.last_update = datetime.utcnow()
|
||||
|
||||
loop_time = time.time() - loop_start
|
||||
fps_samples.append(1.0 / loop_time if loop_time > 0 else 0)
|
||||
# Calculate actual FPS from frame-to-frame interval
|
||||
now = time.time()
|
||||
interval = now - prev_frame_time_stamp
|
||||
prev_frame_time_stamp = now
|
||||
fps_samples.append(1.0 / interval if interval > 0 else 0)
|
||||
if len(fps_samples) > 10:
|
||||
fps_samples.pop(0)
|
||||
state.metrics.fps_actual = sum(fps_samples) / len(fps_samples)
|
||||
|
||||
# Potential FPS = how fast the pipeline could run without throttle
|
||||
processing_time = now - loop_start
|
||||
state.metrics.fps_potential = 1.0 / processing_time if processing_time > 0 else 0
|
||||
|
||||
# Update fps_current: count sends in last 1 second
|
||||
while send_timestamps and send_timestamps[0] < now - 1.0:
|
||||
send_timestamps.popleft()
|
||||
state.metrics.fps_current = len(send_timestamps)
|
||||
|
||||
except Exception as e:
|
||||
state.metrics.errors_count += 1
|
||||
state.metrics.last_error = str(e)
|
||||
@@ -1357,13 +1385,18 @@ class ProcessorManager:
|
||||
raise ValueError(f"KC target {target_id} not found")
|
||||
|
||||
state = self._kc_targets[target_id]
|
||||
metrics = state.metrics
|
||||
return {
|
||||
"target_id": target_id,
|
||||
"processing": state.is_running,
|
||||
"fps_actual": round(state.metrics.fps_actual, 1) if state.is_running else None,
|
||||
"fps_actual": round(metrics.fps_actual, 1) if state.is_running else None,
|
||||
"fps_potential": metrics.fps_potential if state.is_running else None,
|
||||
"fps_target": state.settings.fps,
|
||||
"last_update": state.metrics.last_update.isoformat() if state.metrics.last_update else None,
|
||||
"errors": [state.metrics.last_error] if state.metrics.last_error else [],
|
||||
"frames_skipped": metrics.frames_skipped if state.is_running else None,
|
||||
"frames_keepalive": metrics.frames_keepalive if state.is_running else None,
|
||||
"fps_current": metrics.fps_current if state.is_running else None,
|
||||
"last_update": metrics.last_update,
|
||||
"errors": [metrics.last_error] if metrics.last_error else [],
|
||||
}
|
||||
|
||||
def get_kc_target_metrics(self, target_id: str) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user