performance-profiling
About
This Claude skill profiles application performance across backend, frontend, and database layers to identify and optimize bottlenecks. It is used for investigating slow requests, improving response times, and optimizing resource usage against key performance indicators. The skill can utilize tools like Bash and Grep to analyze code and system performance.
Documentation
Performance Profiling Skill
This skill helps you profile and optimize application performance across the stack.
When to Use This Skill
- Investigating slow requests
- Optimizing API response times
- Reducing Lambda cold starts
- Improving database query performance
- Optimizing frontend rendering
- Reducing bundle size
- Improving Time to First Byte (TTFB)
Performance Metrics
Key Performance Indicators
Backend (API):
- Response time: < 500ms (p95)
- Cold start: < 2s
- Memory usage: < 512MB
- Error rate: < 1%
Frontend (Web):
- LCP: < 2.5s
- FID: < 100ms
- CLS: < 0.1
- TTFB: < 600ms
- FCP: < 1.8s
Database:
- Query time: < 100ms
- Connection pool: 80% max
- Cache hit rate: > 80%
Lambda Performance Profiling
CloudWatch Metrics
# Get Lambda duration metrics
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Duration \
--dimensions Name=FunctionName,Value=sgcarstrends-api-prod \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 300 \
--statistics Average,Maximum,Minimum
# Get memory usage
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name MemoryUtilization \
--dimensions Name=FunctionName,Value=sgcarstrends-api-prod \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 300 \
--statistics Average,Maximum
# Get invocation count
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Invocations \
--dimensions Name=FunctionName,Value=sgcarstrends-api-prod \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 300 \
--statistics Sum
Performance Logging
// apps/api/src/middleware/performance.ts
import { Context, Next } from "hono";
import { log } from "@sgcarstrends/utils/logger";
export const performanceMiddleware = async (c: Context, next: Next) => {
const start = performance.now();
const memoryBefore = process.memoryUsage();
await next();
const duration = performance.now() - start;
const memoryAfter = process.memoryUsage();
log.info("Request completed", {
method: c.req.method,
path: c.req.path,
status: c.res.status,
duration: Math.round(duration),
memory: {
heapUsed: Math.round((memoryAfter.heapUsed - memoryBefore.heapUsed) / 1024 / 1024),
external: Math.round((memoryAfter.external - memoryBefore.external) / 1024 / 1024),
},
});
// Warn on slow requests
if (duration > 1000) {
log.warn("Slow request detected", {
path: c.req.path,
duration: Math.round(duration),
});
}
c.header("X-Response-Time", `${Math.round(duration)}ms`);
};
Database Query Profiling
Query Timing
// packages/database/src/profiler.ts
import { performance } from "perf_hooks";
import { log } from "@sgcarstrends/utils/logger";
export const profileQuery = async <T>(
name: string,
query: () => Promise<T>
): Promise<T> => {
const start = performance.now();
try {
const result = await query();
const duration = performance.now() - start;
log.info("Query executed", {
query: name,
duration: Math.round(duration),
});
if (duration > 100) {
log.warn("Slow query detected", {
query: name,
duration: Math.round(duration),
});
}
return result;
} catch (error) {
const duration = performance.now() - start;
log.error("Query failed", error as Error, {
query: name,
duration: Math.round(duration),
});
throw error;
}
};
// Usage
const cars = await profileQuery("cars.findMany", () =>
db.query.cars.findMany({ limit: 100 })
);
Query Analysis
-- PostgreSQL: Enable query logging
ALTER DATABASE sgcarstrends SET log_statement = 'all';
ALTER DATABASE sgcarstrends SET log_duration = on;
ALTER DATABASE sgcarstrends SET log_min_duration_statement = 100; -- Log queries > 100ms
-- Analyze slow queries
SELECT
query,
calls,
total_time,
mean_time,
max_time
FROM pg_stat_statements
WHERE mean_time > 100
ORDER BY mean_time DESC
LIMIT 20;
-- Check query plan
EXPLAIN ANALYZE
SELECT * FROM cars
WHERE make = 'Toyota'
ORDER BY month DESC
LIMIT 100;
API Profiling
Node.js Built-in Profiler
# Profile API locally
NODE_OPTIONS="--prof" pnpm -F @sgcarstrends/api dev
# Generate profile after running
node --prof-process isolate-*.log > profile.txt
# Analyze profile
cat profile.txt | head -100
Clinic.js
# Install clinic
pnpm add -g clinic
# Profile with Clinic Doctor
clinic doctor -- node apps/api/dist/index.js
# Profile with Clinic Flame (flamegraph)
clinic flame -- node apps/api/dist/index.js
# Profile with Clinic Bubbleprof (async operations)
clinic bubbleprof -- node apps/api/dist/index.js
Custom Profiling
// apps/api/src/profiler.ts
interface PerformanceEntry {
name: string;
duration: number;
timestamp: Date;
}
class Profiler {
private entries: PerformanceEntry[] = [];
start(name: string): () => void {
const startTime = performance.now();
return () => {
const duration = performance.now() - startTime;
this.entries.push({
name,
duration,
timestamp: new Date(),
});
if (duration > 100) {
log.warn("Slow operation", {
operation: name,
duration: Math.round(duration),
});
}
};
}
getStats() {
const grouped = this.entries.reduce((acc, entry) => {
if (!acc[entry.name]) {
acc[entry.name] = {
count: 0,
total: 0,
min: Infinity,
max: 0,
};
}
const stats = acc[entry.name];
stats.count++;
stats.total += entry.duration;
stats.min = Math.min(stats.min, entry.duration);
stats.max = Math.max(stats.max, entry.duration);
return acc;
}, {} as Record<string, { count: number; total: number; min: number; max: number }>);
return Object.entries(grouped).map(([name, stats]) => ({
name,
count: stats.count,
avg: stats.total / stats.count,
min: stats.min,
max: stats.max,
}));
}
reset() {
this.entries = [];
}
}
export const profiler = new Profiler();
// Usage
const end = profiler.start("fetchCarData");
const data = await fetchCarData();
end();
Frontend Performance
React Profiler
// apps/web/src/components/ProfilerWrapper.tsx
import { Profiler, ProfilerOnRenderCallback } from "react";
const onRender: ProfilerOnRenderCallback = (
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) => {
console.log({
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
});
// Log slow renders
if (actualDuration > 16) { // > 1 frame at 60fps
console.warn("Slow render detected", {
component: id,
duration: actualDuration,
});
}
};
export function ProfilerWrapper({ children }: { children: React.ReactNode }) {
if (process.env.NODE_ENV === "development") {
return (
<Profiler id="App" onRender={onRender}>
{children}
</Profiler>
);
}
return <>{children}</>;
}
Chrome DevTools Performance
# 1. Open Chrome DevTools (F12)
# 2. Go to Performance tab
# 3. Click Record
# 4. Perform actions to profile
# 5. Stop recording
# 6. Analyze:
# - FCP, LCP, CLS
# - Long tasks
# - Layout shifts
# - JavaScript execution time
Lighthouse CI
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Run Lighthouse CI
uses: treosh/lighthouse-ci-action@v10
with:
urls: |
https://staging.sgcarstrends.com
https://staging.sgcarstrends.com/cars
https://staging.sgcarstrends.com/coe
uploadArtifacts: true
temporaryPublicStorage: true
Memory Profiling
Node.js Heap Snapshot
// apps/api/src/debug.ts
import { writeHeapSnapshot } from "v8";
import { log } from "@sgcarstrends/utils/logger";
export const captureHeapSnapshot = () => {
const filename = `heap-${Date.now()}.heapsnapshot`;
writeHeapSnapshot(filename);
log.info("Heap snapshot captured", { filename });
return filename;
};
// Usage: Call on high memory usage
if (process.memoryUsage().heapUsed > 400 * 1024 * 1024) { // > 400MB
captureHeapSnapshot();
}
Memory Leak Detection
// apps/api/src/memory-monitor.ts
import { log } from "@sgcarstrends/utils/logger";
class MemoryMonitor {
private baseline: NodeJS.MemoryUsage | null = null;
start() {
this.baseline = process.memoryUsage();
log.info("Memory monitoring started", {
baseline: this.formatMemory(this.baseline),
});
}
check() {
const current = process.memoryUsage();
if (!this.baseline) return;
const diff = {
heapUsed: current.heapUsed - this.baseline.heapUsed,
external: current.external - this.baseline.external,
rss: current.rss - this.baseline.rss,
};
log.info("Memory usage", {
current: this.formatMemory(current),
diff: this.formatMemory(diff),
});
// Warn on memory growth
if (diff.heapUsed > 100 * 1024 * 1024) { // > 100MB growth
log.warn("High memory growth detected", {
growth: Math.round(diff.heapUsed / 1024 / 1024),
});
}
}
private formatMemory(mem: Partial<NodeJS.MemoryUsage>) {
return Object.entries(mem).reduce((acc, [key, value]) => {
acc[key] = `${Math.round((value || 0) / 1024 / 1024)}MB`;
return acc;
}, {} as Record<string, string>);
}
}
export const memoryMonitor = new MemoryMonitor();
Performance Optimization
Lambda Optimization
// infra/api.ts
export function API({ stack }: StackContext) {
const api = new Function(stack, "api", {
handler: "apps/api/src/index.handler",
runtime: "nodejs20.x",
architecture: "arm64", // Graviton2 for better performance
memory: "1024 MB", // More memory = faster CPU
timeout: "30 seconds",
environment: {
NODE_ENV: "production",
LOG_LEVEL: "info",
},
nodejs: {
esbuild: {
minify: true,
bundle: true,
target: "node20",
sourcemap: true,
external: ["@aws-sdk/*"], // Don't bundle AWS SDK
},
},
});
}
Caching Strategy
// apps/api/src/cache.ts
import { redis } from "@sgcarstrends/utils";
export const withCache = async <T>(
key: string,
ttl: number,
fn: () => Promise<T>
): Promise<T> => {
const start = performance.now();
// Check cache
const cached = await redis.get(key);
if (cached) {
const duration = performance.now() - start;
log.info("Cache hit", {
key,
duration: Math.round(duration),
});
return JSON.parse(cached as string);
}
// Cache miss - execute function
const result = await fn();
await redis.set(key, JSON.stringify(result), { ex: ttl });
const duration = performance.now() - start;
log.info("Cache miss", {
key,
duration: Math.round(duration),
});
return result;
};
// Usage
const cars = await withCache("cars:all", 3600, async () => {
return await db.query.cars.findMany();
});
Performance Testing
Load Testing with k6
// load-test.js
import http from "k6/http";
import { check, sleep } from "k6";
export const options = {
stages: [
{ duration: "2m", target: 100 }, // Ramp up to 100 users
{ duration: "5m", target: 100 }, // Stay at 100 users
{ duration: "2m", target: 200 }, // Ramp up to 200 users
{ duration: "5m", target: 200 }, // Stay at 200 users
{ duration: "2m", target: 0 }, // Ramp down to 0 users
],
thresholds: {
http_req_duration: ["p(95)<500"], // 95% of requests < 500ms
http_req_failed: ["rate<0.01"], // < 1% failures
},
};
export default function () {
const res = http.get("https://api.sgcarstrends.com/api/v1/cars/makes");
check(res, {
"status is 200": (r) => r.status === 200,
"response time < 500ms": (r) => r.timings.duration < 500,
});
sleep(1);
}
# Run load test
k6 run load-test.js
# Run with more virtual users
k6 run --vus 500 --duration 5m load-test.js
Performance Dashboard
CloudWatch Dashboard
// infra/dashboard.ts
import { aws_cloudwatch as cloudwatch } from "aws-cdk-lib";
export function PerformanceDashboard({ stack }: StackContext) {
new cloudwatch.Dashboard(stack, "PerformanceDashboard", {
dashboardName: "sgcarstrends-performance",
widgets: [
[
// Lambda duration
new cloudwatch.GraphWidget({
title: "API Response Time",
left: [
new cloudwatch.Metric({
namespace: "AWS/Lambda",
metricName: "Duration",
dimensionsMap: {
FunctionName: "sgcarstrends-api-prod",
},
statistic: "Average",
}),
],
}),
// Error rate
new cloudwatch.GraphWidget({
title: "Error Rate",
left: [
new cloudwatch.Metric({
namespace: "AWS/Lambda",
metricName: "Errors",
dimensionsMap: {
FunctionName: "sgcarstrends-api-prod",
},
statistic: "Sum",
}),
],
}),
],
[
// Memory usage
new cloudwatch.GraphWidget({
title: "Memory Usage",
left: [
new cloudwatch.Metric({
namespace: "AWS/Lambda",
metricName: "MemoryUtilization",
dimensionsMap: {
FunctionName: "sgcarstrends-api-prod",
},
statistic: "Average",
}),
],
}),
// Invocations
new cloudwatch.GraphWidget({
title: "Invocations",
left: [
new cloudwatch.Metric({
namespace: "AWS/Lambda",
metricName: "Invocations",
dimensionsMap: {
FunctionName: "sgcarstrends-api-prod",
},
statistic: "Sum",
}),
],
}),
],
],
});
}
Best Practices
1. Profile Before Optimizing
// ❌ Optimize without profiling
// Just guessing what's slow
// ✅ Profile first, then optimize
const end = profiler.start("operation");
await operation();
end();
// Check profiler stats to find bottleneck
console.log(profiler.getStats());
2. Set Performance Budgets
// lighthouse.config.js
module.exports = {
ci: {
collect: {
numberOfRuns: 3,
},
assert: {
assertions: {
"categories:performance": ["error", { minScore: 0.9 }],
"first-contentful-paint": ["error", { maxNumericValue: 1800 }],
"largest-contentful-paint": ["error", { maxNumericValue: 2500 }],
"cumulative-layout-shift": ["error", { maxNumericValue: 0.1 }],
},
},
},
};
3. Monitor Continuously
// ✅ Continuous monitoring
setInterval(() => {
memoryMonitor.check();
}, 60000); // Check every minute
// ✅ Log performance metrics
app.use(performanceMiddleware);
Troubleshooting
High Response Times
# 1. Check CloudWatch metrics
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Duration \
--dimensions Name=FunctionName,Value=sgcarstrends-api-prod \
--statistics Average,Maximum \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 300
# 2. Check logs for slow operations
aws logs filter-log-events \
--log-group-name /aws/lambda/sgcarstrends-api-prod \
--filter-pattern "duration > 1000"
# 3. Profile locally
NODE_OPTIONS="--prof" pnpm -F @sgcarstrends/api dev
# 4. Optimize bottleneck
Memory Issues
# 1. Check memory usage
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name MemoryUtilization \
--dimensions Name=FunctionName,Value=sgcarstrends-api-prod \
--statistics Average,Maximum
# 2. Capture heap snapshot
# Add to code: captureHeapSnapshot()
# 3. Analyze with Chrome DevTools
# Load .heapsnapshot file in DevTools Memory tab
# 4. Fix memory leaks
References
- Node.js Profiling: https://nodejs.org/en/docs/guides/simple-profiling
- Chrome DevTools: https://developer.chrome.com/docs/devtools/performance
- Lighthouse: https://developer.chrome.com/docs/lighthouse
- k6 Load Testing: https://k6.io/docs
- Related files:
apps/api/src/middleware/performance.ts- Performance middleware- Root CLAUDE.md - Performance guidelines
Best Practices Summary
- Profile First: Always profile before optimizing
- Monitor Continuously: Track performance metrics
- Set Budgets: Define performance budgets and enforce them
- Log Performance: Log slow operations automatically
- Cache Aggressively: Cache expensive operations
- Optimize Critical Path: Focus on user-facing operations
- Load Test: Test under realistic load
- Memory Awareness: Monitor memory usage and prevent leaks
Quick Install
/plugin add https://github.com/sgcarstrends/sgcarstrends/tree/main/performance-profilingCopy and paste this command in Claude Code to install this skill
GitHub 仓库
Related Skills
subagent-driven-development
DevelopmentThis skill executes implementation plans by dispatching a fresh subagent for each independent task, with code review between tasks. It enables fast iteration while maintaining quality gates through this review process. Use it when working on mostly independent tasks within the same session to ensure continuous progress with built-in quality checks.
algorithmic-art
MetaThis Claude Skill creates original algorithmic art using p5.js with seeded randomness and interactive parameters. It generates .md files for algorithmic philosophies, plus .html and .js files for interactive generative art implementations. Use it when developers need to create flow fields, particle systems, or other computational art while avoiding copyright issues.
executing-plans
DesignUse the executing-plans skill when you have a complete implementation plan to execute in controlled batches with review checkpoints. It loads and critically reviews the plan, then executes tasks in small batches (default 3 tasks) while reporting progress between each batch for architect review. This ensures systematic implementation with built-in quality control checkpoints.
cost-optimization
OtherThis Claude Skill helps developers optimize cloud costs through resource rightsizing, tagging strategies, and spending analysis. It provides a framework for reducing cloud expenses and implementing cost governance across AWS, Azure, and GCP. Use it when you need to analyze infrastructure costs, right-size resources, or meet budget constraints.
