Files
yellowjacket/docs/PROFILING.md
T
yonlu 1ec8f82ef8 docs(14-04): create performance profiling guide
- Backend profiling with pprof: profile types, flame graph reading, common hotspots
- Frontend profiling with Chrome DevTools: Performance panel, Memory panel
- Diagnostic workflows for scrolling jank, slow navigation, memory growth
- References scripts/profile.sh for quick access to all profile types
2026-03-14 13:52:43 -04:00

7.2 KiB

Performance Profiling Guide

Practical guide for diagnosing performance issues in YellowJacket. This covers backend (Go/pprof), frontend (Chrome DevTools), and specific diagnostic workflows.

1. Backend Profiling (Go / pprof)

Setup

make dev starts the app with a pprof server on :6060. No extra configuration needed — block and mutex profiling are enabled automatically in dev builds.

Quick Start

./scripts/profile.sh              # Interactive menu
./scripts/profile.sh cpu          # 30s CPU profile (flame graph in browser)
./scripts/profile.sh heap         # Current memory usage
./scripts/profile.sh health       # Goroutine count, heap, GC stats

Profile Types

Profile Use When What It Shows
CPU Something is slow Time spent in each function (flame graph)
Heap Memory growing Current allocations by location
Allocs GC pressure Where allocations happen (even freed)
Goroutine Hangs/deadlocks All goroutines and their stack traces
Block Lock contention Where goroutines block on mutexes/channels
Mutex Mutex bottleneck Mutex contention hotspots
Trace Scheduling issues Timeline of goroutine scheduling, GC pauses, syscalls

Reading Flame Graphs

  • Wide bars = more time spent in that function
  • Look for unexpectedly wide bars (functions taking more time than they should)
  • Bottom of the stack = entry points; top = leaf functions where time is actually spent
  • Use the search box to filter by package (e.g. library, queue, database)
  • Click a bar to zoom into that subtree; click "Root" to zoom back out

Common YellowJacket Hotspots

Function What to Check
database.GetAllTracks Large library — check SQL query time, consider pagination
library.extractMetadata Scan performance — check per-format timing in scan metrics
queue.SetQueue Large queues — Phase 1/2 dedup and index rebuild
coverart.Generate* Thumbnail generation — check per-tier timing
database.SearchTracks FTS5 query performance — check query complexity

Manual pprof Access

If you prefer direct access without the script:

# CPU profile (30 seconds, opens flame graph)
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30

# Heap profile
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/heap

# Goroutine dump (text)
curl http://localhost:6060/debug/pprof/goroutine?debug=1

# Execution trace (5 seconds)
curl -o trace.out http://localhost:6060/debug/trace?seconds=5
go tool trace trace.out

2. Frontend Profiling (Chrome DevTools)

YellowJacket uses Wails (WebView2 on Linux/Windows, WebKit on macOS). On dev builds, Chrome DevTools is available for frontend profiling.

Opening DevTools

Press Ctrl+Shift+I in a Wails dev build (or right-click → Inspect).

Performance Panel (Scrolling & Rendering)

  1. Open the Performance panel
  2. Click Record (circle icon)
  3. Perform the action you want to profile (scroll, navigate, etc.)
  4. Click Stop
  5. Analyze the timeline:
Bar Color Meaning Target
Yellow JavaScript execution < 5ms per frame
Purple Rendering / layout Minimal during scroll
Green Painting Thin bars = composited (good)
Grey Idle Expected between frames

Target: each frame should complete in < 16ms for 60fps scrolling.

Key Metrics for Scroll Smoothness

  • Frame time: Should be consistently < 16ms. Check the FPS row at the top.
  • Layout recalculation: Should NOT happen during scrolling. If it does, CSS contain isn't working or a style is being read/written in a scroll handler.
  • Paint area: Should be minimal. Large paint rects during scroll indicate missing will-change: transform on the scroll container.
  • JS execution during scroll: Should be minimal. lit-virtualizer handles virtualization, but renderItem callbacks run for each new item entering the viewport.

Memory Panel

  1. Take a heap snapshot before an action
  2. Perform the action (navigate views, scroll extensively)
  3. Take another heap snapshot
  4. Switch to Comparison view between the two snapshots
  5. Look for growing arrays or detached DOM nodes

What to Look For

Symptom Likely Cause How to Check
Scroll jank Layout thrashing Performance panel → look for "Layout" bars during scroll
Slow navigation View recreation Performance panel → long constructors after navigate event
Memory growth Listener leaks Memory panel → compare snapshots, filter "Detached"
Slow initial load Blocking JS Performance panel → gap between DOMContentLoaded and first paint
Flickering on navigate View not cached Check viewCache in app-layout — should be cached for primary views

3. Profiling Workflow for Specific Issues

"Scrolling feels janky"

  1. Open DevTools → Performance panel
  2. Click Record → scroll the problematic view for 3-5 seconds → Stop
  3. Look at frame times — are any > 16ms?
  4. If JS is the bottleneck: Check renderItem callback time. Are closures being created per-render? (Phase 14-03 eliminated this pattern)
  5. If Layout is the bottleneck: Check if CSS contain is present on scroll containers. The app uses contain: layout style on the main panel and contain: paint on virtualizers.
  6. If Paint is the bottleneck: Check if will-change: transform is on the scroll container. All scroll-heavy components should have this after Phase 14-01.

"Navigation is slow"

  1. Open DevTools → Performance panel
  2. Click Record → navigate between views rapidly → Stop
  3. Look for long JS tasks between the navigate event and first paint
  4. Check if the view is being destroyed/recreated (look for constructor calls)
  5. After Phase 14-02 view caching: navigation between primary views (Tracks, Albums, Artists, Genres, Playlists, Settings) should show almost no JS activity — the cached DOM is simply shown/hidden

"Library operations feel slow"

  1. Run ./scripts/profile.sh cpu — capture for the duration of the operation
  2. Check the flame graph for the specific Go function
  3. For database operations: check if SQL queries are using indexes
  4. For scan operations: scan metrics are already logged — check per-file timing
  5. Run ./scripts/profile.sh trace for detailed goroutine scheduling during the operation

"Memory keeps growing"

  1. Run ./scripts/profile.sh heap at baseline
  2. Perform the suspect operation repeatedly
  3. Run ./scripts/profile.sh heap again — compare the flame graphs
  4. For frontend: use DevTools Memory panel heap snapshot comparison
  5. Common causes: event listeners not cleaned up in disconnectedCallback, store subscriptions not unsubscribed, large arrays held by closed-over references

"App feels sluggish after running for a while"

  1. ./scripts/profile.sh health — check goroutine count (should be stable, not growing)
  2. ./scripts/profile.sh heap — check if heap is much larger than expected
  3. Check GC stats: high GC cycle count with growing heap = possible memory leak
  4. Frontend: check for detached DOM nodes in DevTools Memory panel