| name | performance-profiling |
| description | Performance profiling workflows: CPU profiling (pprof, py-spy, async-profiler, 0x), memory profiling (heap analysis, leak detection), flamegraph interpretation, latency analysis (P50/P99/P99.9), and profiling anti-patterns per language (Go, Python, JVM, Node.js). |
Performance Profiling Skill
Load testing says "it's slow." Profiling tells you why. This skill covers profiling workflows for CPU, memory, and latency across languages — from running the profiler to reading the flamegraph.
When to Activate
- A service is slower than its SLO and the cause is unknown
- Post-load-test: numbers are bad, now find the bottleneck
- Reviewing a pull request that touches hot code paths
- Investigating memory growth over time (leak detection)
- Optimizing before a traffic spike
CPU Profiling
Go — pprof (built-in, zero setup)
import _ "net/http/pprof"
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
}
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
(pprof) top10
(pprof) top10 -cum
(pprof) web
(pprof) list MyFunc
go tool pprof -http=:8090 http://localhost:6060/debug/pprof/profile?seconds=30
curl http://localhost:6060/debug/pprof/goroutine?debug=2
curl "http://localhost:6060/debug/pprof/block?seconds=10" > block.pprof
go tool pprof block.pprof
curl "http://localhost:6060/debug/pprof/mutex?seconds=10" > mutex.pprof
go tool pprof mutex.pprof
Python — py-spy (sampling, zero overhead, no code changes)
pip install py-spy
py-spy top --pid 12345
py-spy record --pid 12345 -o profile.svg --duration 30
py-spy record -- python myapp.py -o profile.svg
import cProfile
import pstats
profiler = cProfile.Profile()
profiler.enable()
profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats(20)
JVM (Java/Kotlin/Scala) — async-profiler
curl -L https://github.com/async-profiler/async-profiler/releases/latest/download/async-profiler-linux-x64.tar.gz | tar xz
./profiler.sh -d 30 -f profile.html 12345
./profiler.sh -e alloc -d 30 -f alloc.html 12345
./profiler.sh -e lock -d 30 -f lock.html 12345
jcmd 12345 JFR.start duration=60s filename=recording.jfr settings=profile
jmc recording.jfr
Node.js — 0x (flamegraph generation)
npm install -g 0x
0x -- node app.js
node --prof app.js
node --prof-process isolate-*.log > processed.txt
0x -o profile/ -- node -r ts-node/register src/server.ts
Linux — perf (system-wide, language-agnostic)
sudo perf record -g -p $(pgrep myapp) sleep 30
sudo perf report
sudo perf script | stackcollapse-perf.pl | flamegraph.pl > perf.svg
Memory Profiling
Go — heap profile
curl "http://localhost:6060/debug/pprof/heap" > heap.pprof
go tool pprof heap.pprof
(pprof) top20
(pprof) inuse_objects
(pprof) alloc_objects
(pprof) web
go build -gcflags="-m=2" ./... 2>&1 | grep "escapes to heap"
Python — tracemalloc + memory-profiler
import tracemalloc
tracemalloc.start()
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:10]:
print(stat)
snapshot2 = tracemalloc.take_snapshot()
top_stats = snapshot2.compare_to(snapshot1, 'lineno')
for stat in top_stats[:10]:
print(stat)
pip install memory-profiler
import objgraph
objgraph.show_most_common_types(20)
objgraph.show_growth()
Node.js — MemLab / Chrome Memory Snapshot
npm install -g memlab
memlab run --scenario scenario.js
const v8 = require('v8');
const heapStats = v8.getHeapStatistics();
console.log(heapStats);
const inspector = require('inspector');
const session = new inspector.Session();
session.connect();
session.post('HeapProfiler.takeHeapSnapshot', null, (err, r) => {
console.log('Snapshot taken');
session.disconnect();
});
Flamegraph Interpretation
A flamegraph visualizes where CPU time is spent:
Wide bar → function spends a lot of time here (or is called often)
Tall stack → deep call chain
Flat top → leaf function where CPU is actually spent (the bottleneck)
Wide base → frequently called root function
Reading a flamegraph:
1. Find the widest bars — most time spent
2. Look at the TOP of wide towers — that's where time is actually consumed
3. Wide, flat tops = CPU-bound work (the code itself is slow)
4. Wide, tall stacks = I/O or lock wait (many layers, but nothing at top)
CPU-bound vs I/O-bound:
- CPU: top of flamegraph is full of compute (string ops, parsing, crypto)
- I/O: top has many sys_read/epoll_wait/futex calls (waiting, not computing)
Latency Analysis
Percentiles Matter
P50 (median): 50% of requests are faster than this
P95: 95% of requests are faster than this
P99: 99% of requests are faster than this
P99.9: 999 of 1000 requests are faster than this
Why tail latency matters:
- A user making 100 requests per page load will hit P99 on almost every page
- P50 being great doesn't help users who hit P99.9
- SLOs should be on P99, not P50
Common Tail Latency Causes
| Cause | Symptom | Diagnosis |
|---|
| GC pauses | Intermittent spikes at P99 | gc in flamegraph, GC logs |
| Lock contention | Flat peaks at mutex/futex | mutex profile in pprof, lock profile in async-profiler |
| Thread pool saturation | Queuing delay | Thread pool metrics, queue depth |
| Network jitter | Variation independent of code | Separate network latency measurement |
| Database slow query | Matches DB query timing | DB slow query log |
| Connection pool exhaustion | Spikes correlate with high load | Pool wait time metrics |
Profiling Anti-Patterns
| Anti-Pattern | Problem | Correct Approach |
|---|
| Profile in development only | Dev load != prod load | Profile under realistic traffic in staging |
| Profile cold start | JIT/warm-up biases results | Run load for 60s before profiling |
| Profile for 1 second | Too short, sampling noise | Profile for 30-60 seconds minimum |
| Profile without load | Idle profiling shows nothing | Run load test simultaneously |
| Only look at P50 | Misses tail latency | Profile P99/P99.9 tail |
| Optimize the first hotspot | May not be the bottleneck | Fix biggest, re-profile to confirm |
| Micro-benchmark without warmup | JIT not kicked in | Add warmup iterations before measuring |
Quick Reference — Tool Selection
| Language | CPU Profiling | Memory Profiling | Recommended |
|---|
| Go | pprof (built-in) | pprof heap | pprof — zero setup |
| Python | py-spy | tracemalloc | py-spy — no code changes |
| Java | async-profiler | async-profiler alloc | async-profiler |
| Node.js | 0x / --prof | MemLab, heapdump | 0x for flamegraphs |
| Rust | cargo flamegraph | valgrind/heaptrack | cargo flamegraph |
| C/C++ | perf | valgrind massif | perf + valgrind |
| Linux (any) | perf | valgrind | perf for system-wide |
Reference Commands
/profile — guided profiling workflow for any language
load-testing skill — generate realistic load during profiling session
observability skill — set up continuous performance metrics
web-performance skill — browser/frontend performance profiling