Files
yellowjacket/docs/PROFILING.md
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

160 lines
7.2 KiB
Markdown

# 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
```bash
./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:
```bash
# 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