Systematic performance profiling for Node.js, Python, and Go applications. Identifies CPU, memory, and I/O bottlenecks; generates flamegraphs; analyzes bundle sizes; optimizes database queries; detects memory leaks; and runs load tests with k6 and Artillery. Always measures before and after.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Instruções da origem · Visualização somente leitura
name
performance-profiler
description
Systematic performance profiling for Node.js, Python, and Go applications. Identifies CPU, memory, and I/O bottlenecks; generates flamegraphs; analyzes bundle sizes; optimizes database queries; detects memory leaks; and runs load tests with k6 and Artillery. Always measures before and after.
Systematic performance profiling for Node.js, Python, and Go applications. Identifies CPU, memory, and I/O bottlenecks; generates flamegraphs; analyzes bundle sizes; optimizes database queries; detects memory leaks; and runs load tests with k6 and Artillery. Always measures before and after.
Core Capabilities
CPU profiling — flamegraphs for Node.js, py-spy for Python, pprof for Go
# Method 2: Node.js built-in profiler
node --prof dist/server.js
# After running some load:
node --prof-process isolate-*.log | head -100
# Method 3: V8 CPU profiler via inspector
node --inspect dist/server.js
# Open Chrome DevTools → Performance → Record
Heap Snapshot / Memory Leak Detection
// Add to your server for on-demand heap snapshotsimport v8 from'v8'import fs from'fs'// Endpoint: POST /debug/heap-snapshot (protect with auth!)
app.post('/debug/heap-snapshot', (req, res) => {
const filename = `heap-${Date.now()}.heapsnapshot`const snapshot = v8.writeHeapSnapshot(filename)
res.json({ snapshot })
})
# Take snapshots over time and compare in Chrome DevTools
curl -X POST http://localhost:3000/debug/heap-snapshot
# Wait 5 minutes of load
curl -X POST http://localhost:3000/debug/heap-snapshot
# Open both snapshots in Chrome → Memory → Compare
Detect Event Loop Blocking
// Add blocked-at to detect synchronous blockingimport blocked from'blocked-at'blocked((time, stack) => {
console.warn(`Event loop blocked for ${time}ms`)
console.warn(stack.join('\n'))
}, { threshold: 100 }) // Alert if blocked > 100ms
Node.js Memory Profiling Script
// scripts/memory-profile.mjs// Run: node --experimental-vm-modules scripts/memory-profile.mjsimport { createRequire } from'module'constrequire = createRequire(import.meta.url)
functionformatBytes(bytes) {
return (bytes / 1024 / 1024).toFixed(2) + ' MB'
}
functionmeasureMemory(label) {
const mem = process.memoryUsage()
console.log(`\n[${label}]`)
console.log(` RSS: ${formatBytes(mem.rss)}`)
console.log(` Heap Used: ${formatBytes(mem.heapUsed)}`)
console.log(` Heap Total:${formatBytes(mem.heapTotal)}`)
console.log(` External: ${formatBytes(mem.external)}`)
return mem
}
const baseline = measureMemory('Baseline')
// Simulate your operationfor (let i = 0; i < 1000; i++) {
// Replace with your actual operationconst result = awaitsomeOperation()
}
const after = measureMemory('After 1000 operations')
console.log(`\n[Delta]`)
console.log(` Heap Used: +${formatBytes(after.heapUsed - baseline.heapUsed)}`)
// If heap keeps growing across GC cycles, you have a leakglobal.gc?.() // Run with --expose-gc flagconst afterGC = measureMemory('After GC')
if (afterGC.heapUsed > baseline.heapUsed * 1.1) {
console.warn('⚠️ Possible memory leak detected (>10% growth after GC)')
}
Python Profiling
CPU Profiling with py-spy
# Install
pip install py-spy
# Profile a running process (no code changes needed)
py-spy top --pid $(pgrep -f "uvicorn")
# Generate flamegraph SVG
py-spy record -o flamegraph.svg --pid $(pgrep -f "uvicorn") --duration 30
# Profile from the start
py-spy record -o flamegraph.svg -- python -m uvicorn app.main:app
# Open flamegraph.svg in browser — look for wide bars = hot code paths
cProfile for function-level profiling
# scripts/profile_endpoint.pyimport cProfile
import pstats
import io
from app.services.task_service import TaskService
defrun():
service = TaskService()
for _ inrange(100):
service.list_tasks(user_id="user_1", page=1, limit=20)
profiler = cProfile.Profile()
profiler.enable()
run()
profiler.disable()
# Print top 20 functions by cumulative time
stream = io.StringIO()
stats = pstats.Stats(profiler, stream=stream)
stats.sort_stats('cumulative')
stats.print_stats(20)
print(stream.getvalue())
Memory profiling with memory_profiler
# pip install memory-profilerfrom memory_profiler import profile
@profiledefmy_function():
# Function to profile
data = load_large_dataset()
result = process(data)
return result
# Run with line-by-line memory tracking
python -m memory_profiler scripts/profile_function.py
# Output:# Line # Mem usage Increment Line Contents# ================================================# 10 45.3 MiB 45.3 MiB def my_function():# 11 78.1 MiB 32.8 MiB data = load_large_dataset()# 12 156.2 MiB 78.1 MiB result = process(data)
Go Profiling with pprof
// main.go — add pprof endpointsimport _ "net/http/pprof"import"net/http"funcmain() {
// pprof endpoints at /debug/pprof/gofunc() {
log.Println(http.ListenAndServe(":6060", nil))
}()
// ... rest of your app
}
# CPU profile (30s)
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30
# Memory profile
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/heap
# Goroutine leak detection
curl http://localhost:6060/debug/pprof/goroutine?debug=1
# In pprof UI: "Flame Graph" view → find the tallest bars
# Find the largest chunks
pnpm build 2>&1 | grep -E "^\s+(λ|○|●)" | sort -k4 -rh | head -20
# Check if a specific package is too large# Visit: https://bundlephobia.com/package/moment@2.29.4# moment: 67.9kB gzipped → replace with date-fns (13.8kB) or dayjs (6.9kB)# Find duplicate packages
pnpm dedupe --check
# Visualize what's in a chunk
npx source-map-explorer .next/static/chunks/*.js
Common bundle wins
// Before: import entire lodashimport _ from'lodash'// 71kB// After: import only what you needimport debounce from'lodash/debounce'// 2kB// Before: moment.jsimport moment from'moment'// 67kB// After: dayjsimport dayjs from'dayjs'// 7kB// Before: static import (always in bundle)importHeavyChartfrom'@/components/HeavyChart'// After: dynamic import (loaded on demand)constHeavyChart = dynamic(() =>import('@/components/HeavyChart'), {
loading: () =><Skeleton />,
})
Database Query Optimization
Find slow queries
-- PostgreSQL: enable pg_stat_statementsCREATE EXTENSION IF NOTEXISTS pg_stat_statements;
-- Top 20 slowest queriesSELECT
round(mean_exec_time::numeric, 2) AS mean_ms,
calls,
round(total_exec_time::numeric, 2) AS total_ms,
round(stddev_exec_time::numeric, 2) AS stddev_ms,
left(query, 80) AS query
FROM pg_stat_statements
WHERE calls >10ORDERBY mean_exec_time DESC
LIMIT 20;
-- Reset statsSELECT pg_stat_statements_reset();
# MySQL slow query log
mysql -e "SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 0.1;"tail -f /var/log/mysql/slow-query.log
EXPLAIN ANALYZE
-- Always use EXPLAIN (ANALYZE, BUFFERS) for real timing
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT t.*, u.name as assignee_name
FROM tasks t
LEFTJOIN users u ON u.id = t.assignee_id
WHERE t.project_id ='proj_123'AND t.deleted_at ISNULLORDERBY t.created_at DESC
LIMIT 20;
-- Look for:-- Seq Scan on large table → needs index-- Nested Loop with high rows → N+1, consider JOIN or batch-- Sort → can index handle the sort?-- Hash Join → fine for moderate sizes
Detect N+1 Queries
// Add query logging in devimport { db } from'./client'// Drizzle: enable loggingconst db = drizzle(pool, { logger: true })
// Or use a query counter middlewarelet queryCount = 0
db.$on('query', () => queryCount++)
// In tests:
queryCount = 0const tasks = awaitgetTasksWithAssignees(projectId)
expect(queryCount).toBe(1) // Fail if it's 21 (1 + 20 N+1s)
# Django: detect N+1 with django-silk or nplusonefrom nplusone.ext.django.middleware import NPlusOneMiddleware
MIDDLEWARE = ['nplusone.ext.django.middleware.NPlusOneMiddleware']
NPLUSONE_RAISE = True# Raise exception on N+1 in tests
Fix N+1 — Before/After
// Before: N+1 (1 query for tasks + N queries for assignees)const tasks = await db.select().from(tasksTable)
for (const task of tasks) {
task.assignee = await db.select().from(usersTable)
.where(eq(usersTable.id, task.assigneeId))
.then(r => r[0])
}
// After: 1 query with JOINconst tasks = await db
.select({
id: tasksTable.id,
title: tasksTable.title,
assigneeName: usersTable.name,
assigneeEmail: usersTable.email,
})
.from(tasksTable)
.leftJoin(usersTable, eq(usersTable.id, tasksTable.assigneeId))
.where(eq(tasksTable.projectId, projectId))
# Run load test
k6 run tests/load/api-load-test.js \
--env BASE_URL=https://staging.myapp.com
# With Grafana output
k6 run --out influxdb=http://localhost:8086/k6 tests/load/api-load-test.js
Before/After Measurement Template
## Performance Optimization: [What You Fixed]**Date:** 2026-03-01
**Engineer:** @username
**Ticket:** PROJ-123
### Problem
[1-2 sentences: what was slow, how was it observed]
### Root Cause
[What the profiler revealed]
### Baseline (Before)
| Metric | Value |
|--------|-------|
| P50 latency | 480ms |
| P95 latency | 1,240ms |
| P99 latency | 3,100ms |
| RPS @ 50 VUs | 42 |
| Error rate | 0.8% |
| DB queries/req | 23 (N+1) |
Profiler evidence: [link to flamegraph or screenshot]
### Fix Applied
[What changed — code diff or description]
### After
| Metric | Before | After | Delta |
|--------|--------|-------|-------|
| P50 latency | 480ms | 48ms | -90% |
| P95 latency | 1,240ms | 120ms | -90% |
| P99 latency | 3,100ms | 280ms | -91% |
| RPS @ 50 VUs | 42 | 380 | +804% |
| Error rate | 0.8% | 0% | -100% |
| DB queries/req | 23 | 1 | -96% |
### Verification
Load test run: [link to k6 output]
Optimization Checklist
Quick wins (check these first)
Database
□ Missing indexes on WHERE/ORDER BY columns
□ N+1 queries (check query count per request)
□ Loading all columns when only 2-3 needed (SELECT *)
□ No LIMIT on unbounded queries
□ Missing connection pool (creating new connection per request)
Node.js
□ Sync I/O (fs.readFileSync) in hot path
□ JSON.parse/stringify of large objects in hot loop
□ Missing caching for expensive computations
□ No compression (gzip/brotli) on responses
□ Dependencies loaded in request handler (move to module level)
Bundle
□ Moment.js → dayjs/date-fns
□ Lodash (full) → lodash/function imports
□ Static imports of heavy components → dynamic imports
□ Images not optimized / not using next/image
□ No code splitting on routes
API
□ No pagination on list endpoints
□ No response caching (Cache-Control headers)
□ Serial awaits that could be parallel (Promise.all)
□ Fetching related data in a loop instead of JOIN
Common Pitfalls
Optimizing without measuring — you'll optimize the wrong thing
Testing in development — profile against production-like data volumes
Ignoring P99 — P50 can look fine while P99 is catastrophic
Premature optimization — fix correctness first, then performance
Not re-measuring — always verify the fix actually improved things
Load testing production — use staging with production-size data
Best Practices
Baseline first, always — record metrics before touching anything
One change at a time — isolate the variable to confirm causation
Profile with realistic data — 10 rows in dev, millions in prod — different bottlenecks
Set performance budgets — p(95) < 200ms in CI thresholds with k6
Monitor continuously — add Datadog/Prometheus metrics for key paths