Skip to content

perf: cheap per-frame and per-fetch savings - #725

Merged
ChuckBuilds merged 8 commits into
mainfrom
perf/cheap-wins
Oct 3, 2026
Merged

ChuckBuilds merged 8 commits into
mainfrom
perf/cheap-wins

Conversation

@ChuckBuilds

Copy link
Copy Markdown
Owner

Summary

Six small per-frame and per-fetch savings, none changing behaviour:

  • odds fetch no longer pretty-prints every response for a debug line when DEBUG is off
  • drop a redundant full-frame np.ascontiguousarray in the scroll integer-slice path (~45 µs → ~21 µs per frame at 512x64 on a Pi 4)
  • MALLOC_ARENA_MAX=2 for ledmatrix-web.service, matching the display unit (harden: cap malloc arenas, warn on unit drift, and grant the portal's sudo commands #476)
  • parse core ESPN responses via response_json (orjson when installed, stdlib fallback)
  • "Scroll frame stats" goes to INFO only for degraded windows plus a 5-minute heartbeat; every window is still logged at DEBUG
  • changelog entry

Type of change

  • Refactor (no functional change)

Test plan

  • Ran the test suite (pytest): full suite passes, apart from 8 test_live_status_fields.py tests that need psutil and fail the same way on main. Each change has a test (byte-identical output for the slice path, real requests.Response objects for the JSON fallback, the systemd unit line, the log-level gating).
  • Ran on a real Raspberry Pi with hardware (not yet soaked)

Documentation

  • I updated the relevant doc in docs/ if developer behavior changed (docs/SCROLL_PERFORMANCE.md, CHANGELOG.md)

Plugin compatibility

  • No plugin breakage expected

Notes for reviewer

On an existing install the startup drift check will report the web unit as changed until install_web_service.sh is re-run, as for any template change.

🤖 Generated with Claude Code

ChuckBuilds and others added 6 commits October 1, 2026 22:36
get_odds() logged the raw ESPN body with an f-string around
json.dumps(raw_data, indent=2), so every fetched response was pretty-printed
and thrown away with DEBUG off. The json.dumps debug lines in
_extract_espn_data had the same cost. Those are now guarded with
isEnabledFor(DEBUG), and the other debug f-strings on the fetch path use
%-style arguments. Same messages at DEBUG; nothing else changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uc5DAbSwGGUTm3MrCtpC2m
_get_visible_portion_integer() ran np.ascontiguousarray() on the visible
column slice and then tobytes(), copying the frame twice. tobytes() on the
non-contiguous view already returns C-order bytes, so the first copy bought
nothing. A test pins the bytes against the old expression. On a Pi 4 the
bytes step at 512x64 went from ~45 us to ~21 us a frame (timeit, best of 5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uc5DAbSwGGUTm3MrCtpC2m
ledmatrix.service has set MALLOC_ARENA_MAX=2 since #476: glibc gives each
allocating thread its own arena (up to 8 x CPU count) and never hands a grown
one back. ledmatrix-web.service runs a threaded Flask server with the same
exposure but had no cap. Both installers render the template with sed, so the
line installs unchanged; on an existing install the startup drift check
reports the unit as changed until install_service.sh or
install_web_service.sh is re-run, as for any template change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uc5DAbSwGGUTm3MrCtpC2m
APIHelper.get/post, BaseOddsManager.get_odds, LogoDownloader's two team
fetches and DynamicTeamResolver's rankings fetch called response.json(),
the stdlib parser, while background_data_service and espn_dates already use
src.common.json_body.response_json (orjson when installed, ~1.7x faster on a
Pi 4, holding the GIL for less of the parse). response_json falls back to
response.json() on anything orjson rejects, so a bad body still raises
requests' JSONDecodeError, which every one of these call sites already
catches; a test pins that with real requests.Response objects. Without
orjson nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uc5DAbSwGGUTm3MrCtpC2m
Every scroller logged a "Scroll frame stats" line at INFO every 5 s, so a
healthy rig's journal was mostly these lines. As Vegas's FPS line already
does, the line now goes to INFO only for a degraded window (fps below 0.9 of
the rate the window was locked to, 1 / its median, or more than 1% of frames
stalled), the window after one, and a 5-minute heartbeat per scroller; every
window is still logged at DEBUG. The window, the stats and the line's text
are unchanged. docs/SCROLL_PERFORMANCE.md says how to see every window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uc5DAbSwGGUTm3MrCtpC2m
@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 21 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ca199379-d6ac-4b0a-a25f-d9494025b510
📥 Commits

Reviewing files that changed from the base of the PR and between 7bb85c0 and a036fa3.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • docs/SCROLL_PERFORMANCE.md
  • src/base_odds_manager.py
  • src/common/api_helper.py
  • src/common/scroll_helper.py
  • src/dynamic_team_resolver.py
  • src/logo_downloader.py
  • systemd/ledmatrix-web.service
  • test/test_base_odds_manager.py
  • test/test_cache_stale_header.py
  • test/test_scroll_helper.py
  • test/test_systemd_malloc_arenas.py
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 7 complexity

Metric Results
Complexity 7

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@ChuckBuilds
ChuckBuilds merged commit ed0753c into main Oct 3, 2026
15 checks passed
@ChuckBuilds
ChuckBuilds deleted the perf/cheap-wins branch October 3, 2026 18:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant