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
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user