Performance audit
Systematic performance investigation workflow. Use when performance regressions are suspected, before and after optimisation work, or as a pre-release gate for performance-critical features. Produces a baseline, identifies the real bottleneck, and validates improvement with hard numbers.From its SKILL.md
npx -y skills add RBraga01/a-team --skill performance-auditAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 13 stars13 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.2 KB, as published. Nobody here has run it
Performance Audit
The Law
A PERFORMANCE CLAIM WITHOUT NUMBERS IS AN OPINION.
"I think it's faster" is not a result.
"p95 latency dropped from 480ms to 42ms on a 1000-item dataset" is a result.
When to Use
- Before any optimisation work (establish baseline first)
- When a performance regression is reported
- As a pre-release gate on performance-critical features
- After an optimisation to validate the improvement
Audit Workflow
Step 1: Define the Performance Goal
Before measuring anything, define what "good" looks like:
| Category | Example target |
|---|---|
| API latency | p95 < 200ms at 100 concurrent users |
| Page load | First Contentful Paint < 1.5s on 4G |
| Mobile frame rate | 60fps during scroll, 0 jank frames |
| Build time | Full build < 3 minutes |
| Memory | Peak heap < 512MB under load |
| Bundle size | JS bundle < 250KB gzipped |
If no target exists, the first audit establishes the baseline that becomes the target.
Step 2: Measure the Baseline
Run the benchmark before any changes. Record:
- Tool used and exact command
- Input size and conditions
- Raw numbers (min, p50, p95, p99, max)
- System state (CPU, memory, concurrent users)
## Baseline — YYYY-MM-DD
**Feature:** User search endpoint
**Tool:** k6
**Command:** `k6 run --vus 50 --duration 60s scripts/search-load-test.js`
**Input:** 1000-user dataset, query="test"
| Metric | Value |
|--------|-------|
| p50 | 45ms |
| p95 | 380ms |
| p99 | 720ms |
| Error rate | 0.2% |
| Throughput | 210 req/s |
Step 3: Profile — Find the Real Bottleneck
Use the appropriate tool for the stack. Read the output. Don't guess.
Web APIs
# Node.js — CPU profile
node --prof app.js
# Run load test, then:
node --prof-process isolate-*.log > profile.txt
# Python — flame graph
pip install py-spy
py-spy record -o profile.svg --pid $(pgrep -f "python app.py")
# Go — pprof
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# Database — slow query log
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT ...;
Frontend
# Bundle analysis
npx webpack-bundle-analyzer stats.json
npx source-map-explorer dist/main.js
# Runtime: Chrome DevTools → Performance tab
# Record interaction → identify long tasks (>50ms)
# Lighthouse CI
npx lhci autorun
Mobile
- Android: Android Studio → Profiler → CPU/Memory/Network
- iOS: Xcode → Instruments → Time Profiler
- Flutter:
flutter run --profile→ DevTools → Performance overlay
Step 4: Report the Finding
State the bottleneck with evidence, not assumption:
## Bottleneck Found
**Location:** `UserSearchService.search()` — lines 45-67
**Evidence:** pprof shows 78% of CPU time in this function
**Root cause:** For each of 50 search results, a separate `getUserById()` DB call is made (N+1 query)
**Impact:** 47 sequential DB calls per request × ~8ms each = ~376ms per request
Step 5: Apply One Change
Single change. Document hypothesis and expected impact before applying:
## Change Applied
**Hypothesis:** Replacing per-item DB lookups with a single JOIN will eliminate 46 of the 47 DB calls
**Change:** `UserSearchService.search()` — replace loop + `getUserById()` with `getUsersByIds(ids)` JOIN
**Files modified:** `src/services/UserSearchService.ts`
**Expected impact:** p95 latency from 380ms to < 50ms
Step 6: Re-Measure and Compare
Run the exact same benchmark from Step 2.
## After Optimisation — YYYY-MM-DD
**Change:** N+1 query replaced with single JOIN
| Metric | Before | After | Delta |
|--------|--------|-------|-------|
| p50 | 45ms | 12ms | -73% |
| p95 | 380ms | 38ms | -90% |
| p99 | 720ms | 65ms | -91% |
| Error rate | 0.2% | 0.1% | -50% |
| Throughput | 210 req/s | 890 req/s | +324% |
**Verdict:** IMPROVEMENT CONFIRMED
**Root cause validated:** DB calls per request: 47 → 1
**No regression:** error rate stable, memory unchanged
Step 7: Verdict
| Verdict | Criteria |
|---|---|
| IMPROVEMENT CONFIRMED | Measurable improvement on target metric, no regression elsewhere |
| INCONCLUSIVE | No measurable change — hypothesis was wrong; identify next bottleneck |
| REGRESSION | Change made things worse — revert immediately, re-diagnose |
| GOAL MET | Target from Step 1 achieved — further optimisation not required |
Performance Budget (Pre-Release Gate)
For release-critical features, define performance budgets in docs/performance-budgets.md:
api_latency:
endpoint: POST /api/search
p95_ms: 200
measured_with: k6
load: 100 concurrent users
bundle_size:
entry: main.js
max_gzip_kb: 250
measured_with: webpack-bundle-analyzer
mobile_fps:
screen: SearchScreen
min_fps: 60
tool: Android Profiler
CI fails if any budget is exceeded. Performance is a feature — it regresses like any other feature.