Skip to content

perf(leaderboard): decode each logo once, not once per rebuild - #270

Merged
ChuckBuilds merged 2 commits into
mainfrom
fix/leaderboard-logo-cache
Aug 12, 2026
Merged

perf(leaderboard): decode each logo once, not once per rebuild#270
ChuckBuilds merged 2 commits into
mainfrom
fix/leaderboard-logo-cache

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Aug 12, 2026

Copy link
Copy Markdown
Owner

What

_get_team_logo called Image.open on every request with no cache, and _prepare_logo re-ran convert + LANCZOS resize each time. Image.open is lazy, so the PNG decode landed inside convert() — for every team, on every rebuild, for files that had not changed.

Measured on the real asset directory (page cache warmed first, to be fair):

uncached, per rebuild:      276.3 ms   (25 logos)
cached, first build:        279.4 ms
cached, every later build:    0.3 ms   — 829x

A real board carries considerably more than 25.

Why this path in particular

Leaderboard scroll content is generated on the render thread. The adapter's capture path needs the shared canvas, so it cannot be prepared in the background — render_pipeline.drain_deferred() documents this and trickles one plugin per frame to spread the cost. So work here lands directly on the Vegas scroll as visible stutter.

Cache correctness

  • Keyed on path + mtime, so a logo the downloader backfills is picked up rather than served stale forever.
  • A missing file is deliberately not cached — remembering it as absent would keep a logo downloaded moments later invisible until restart.
  • Bounded, with a wholesale clear on overflow; the next rebuild repopulates the handful it needs.
  • The prepared image is shared, not copied: callers only paste from it, and copying per team would put back a slice of the cost this removes.

What this does NOT fix

The 3.2s freeze that led me here is still present with this change in place — I re-measured on the rig and it was unchanged at 3270ms. My hypothesis that logo decoding dominated it was wrong; 276ms is not 3.2s.

Companion core work (LEDMatrix#454) now explains why that freeze was never caught: it holds the GIL, so the stall watchdog's thread could not run during it. That's now detected separately.

This PR stands on its own — it removes a real, repeated, measurable cost on the render thread — but it should not be read as the stutter fix.

Tests

test_logo_cache.py: loaded exactly once across 25 rebuilds, distinct entries per target size, a file replaced on disk is re-read, a missing logo is retried rather than remembered, the cache respects its ceiling, and a warm rebuild is more than 10x cheaper than a cold one.

Safety harness passes all 8 sizes; check_module_collisions.py clean across 43 plugins.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5

Summary by CodeRabbit

  • Performance Improvements

    • Improved leaderboard rendering with faster, reusable logo processing.
    • Automatically refreshes logos when source files change while limiting cache growth.
  • Bug Fixes

    • Missing logos can now be retried and downloaded before rendering.
    • Rendering no longer downloads logos mid-display, improving reliability.
  • Release

    • Updated the Sports Leaderboard plugin to version 1.3.2.
    • Added release history for recent logo-handling improvements.

Image.open defers the PNG decode to first access, so the decode landed
inside _prepare_logo's convert(), with a LANCZOS resize behind it -- for
every team, on every rebuild, for files that had not changed. Measured
on the real asset directory: 25 logos cost 276ms cold and 0.3ms warm.

It matters more than a normal cache miss because leaderboard scroll
content is generated on the render thread. The adapter's capture path
needs the shared canvas, so it cannot be prepared in the background
(render_pipeline.drain_deferred explains why), and the cost lands
directly on the Vegas scroll as stutter.

Keyed on path and mtime, so a logo the downloader backfills is picked up
rather than served stale. A missing file is deliberately not cached:
remembering it as absent would keep a logo downloaded moments later
invisible until restart.

Honest scope: this does not fix the 3.2s freeze that led here. That one
is still present with this change in place, and the watchdog work on the
core side now shows why it was never caught -- it holds the GIL. This
removes a real and separate cost on the same path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d0810172-d488-40e7-8dd9-abec5c85a661

📥 Commits

Reviewing files that changed from the base of the PR and between 8d287a6 and 1524979.

📒 Files selected for processing (5)
  • plugins.json
  • plugins/ledmatrix-leaderboard/image_renderer.py
  • plugins/ledmatrix-leaderboard/manager.py
  • plugins/ledmatrix-leaderboard/manifest.json
  • plugins/ledmatrix-leaderboard/test_logo_cache.py

📝 Walkthrough

Walkthrough

The Sports Leaderboard plugin now caches prepared league and team logos with file-change detection and bounded storage. Missing team logos download during leaderboard updates instead of rendering. Release metadata reports version 1.3.2, with an executable validation script.

Changes

Sports Leaderboard logo handling

Layer / File(s) Summary
Logo caching and download flow
plugins/ledmatrix-leaderboard/image_renderer.py, plugins/ledmatrix-leaderboard/manager.py
ImageRenderer caches prepared logos by path, modification time, dimensions, and rendering mode. Team logo rendering uses local files only. manager.py downloads missing logos after leaderboard updates and before layout work.
Logo cache validation
plugins/ledmatrix-leaderboard/test_logo_cache.py
The executable test script verifies cache reuse, file replacement, missing-file retries, cache bounds, warm-cache performance, local-only rendering, download backfill, and exit statuses.
Release metadata updates
plugins.json, plugins/ledmatrix-leaderboard/manifest.json
Catalog and manifest metadata now report version 1.3.2 and release date 2026-08-12. Release records describe the logo download and caching changes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Manager
  participant ImageRenderer
  participant LogoDownloader
  participant LocalLogoFiles
  Manager->>ImageRenderer: fetch leaderboard data
  ImageRenderer->>LocalLogoFiles: check team logo paths
  ImageRenderer->>LogoDownloader: download missing logos
  LogoDownloader->>LocalLogoFiles: save downloaded logos
  Manager->>ImageRenderer: build layout
  ImageRenderer->>LocalLogoFiles: load cached local logos
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: caching logo decoding to avoid repeated work during rebuilds.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/leaderboard-logo-cache

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

codacy-production Bot commented Aug 12, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 39 complexity

Metric Results
Complexity 39

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/ledmatrix-leaderboard/image_renderer.py`:
- Around line 300-304: Update image_renderer.py in the logo cache-miss path
around _prepare_logo so layout construction loads only existing local files and
never invokes the downloader; move missing-logo retry behavior into update(). In
test_logo_cache.py lines 97-115, replace immediate render retry assertions with
update()-driven retry coverage and verify that a subsequently downloaded local
file becomes visible on a later render.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c45a4c47-0fef-445b-ac92-9fd3eaf478e2

📥 Commits

Reviewing files that changed from the base of the PR and between 8dd8f07 and 8d287a6.

📒 Files selected for processing (4)
  • plugins.json
  • plugins/ledmatrix-leaderboard/image_renderer.py
  • plugins/ledmatrix-leaderboard/manifest.json
  • plugins/ledmatrix-leaderboard/test_logo_cache.py

Comment thread plugins/ledmatrix-leaderboard/image_renderer.py
…271)

* fix(leaderboard): move missing-logo downloads off the render thread

_get_team_logo ran from layout construction inside display(), and on a
cache miss it invoked download_missing_logo -- a network call on the
render thread the Vegas scroll depends on. Missing logos are now
backfilled by a new download_missing_logos(), called from update()
instead; _get_team_logo only ever reads local files, and a backfilled
logo becomes visible on the next rebuild via the existing mtime-keyed
cache.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RRsTMmLUcpd8tpN1qciEgC

* test(leaderboard): drop unused variable in logo cache test

Codacy flagged an unused local in the new test coverage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RRsTMmLUcpd8tpN1qciEgC

---------

Co-authored-by: Claude <noreply@anthropic.com>
@ChuckBuilds
ChuckBuilds merged commit 6ce527d into main Aug 12, 2026
2 of 3 checks passed
@ChuckBuilds
ChuckBuilds deleted the fix/leaderboard-logo-cache branch August 12, 2026 13:48
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.

2 participants