home / skills / doanchienthangdev / omgkit / performance-testing

This skill helps you implement comprehensive performance testing patterns to optimize speed, scalability, and resource efficiency across applications.

npx playbooks add skill doanchienthangdev/omgkit --skill performance-testing

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
8.5 KB
---
name: testing/performance-testing
description: Performance testing patterns including load testing, stress testing, benchmarking, and profiling for optimal application performance
category: testing
tags:
  - testing
  - performance
  - benchmarking
  - load-testing
  - profiling
related_skills:
  - testing/comprehensive-testing
  - devops/observability
  - backend/caching-strategies
---

# Performance Testing

Comprehensive performance testing for speed, scalability, and resource efficiency.

## Quick Start

```bash
# Run benchmarks
npm run test:performance

# Profile with Node.js
node --prof app.js
node --prof-process isolate-*.log > profile.txt

# Load testing with k6
k6 run load-test.js
```

## Types of Performance Tests

### 1. Benchmark Tests

Measure execution time of specific operations.

```javascript
import { describe, it, expect } from 'vitest';

describe('Performance Benchmarks', () => {
  function timeExecution(fn, iterations = 1000) {
    const start = performance.now();
    for (let i = 0; i < iterations; i++) {
      fn();
    }
    const end = performance.now();
    return (end - start) / iterations;
  }

  it('sorts 1000 items under 1ms', () => {
    const data = Array(1000).fill().map(() => Math.random());
    const time = timeExecution(() => [...data].sort((a, b) => a - b), 100);
    expect(time).toBeLessThan(1);
  });

  it('hashes string under 0.1ms', () => {
    const time = timeExecution(() => hashString('test-string'), 10000);
    expect(time).toBeLessThan(0.1);
  });

  it('parses JSON under 0.5ms', () => {
    const json = JSON.stringify({ users: Array(100).fill({ name: 'test' }) });
    const time = timeExecution(() => JSON.parse(json), 1000);
    expect(time).toBeLessThan(0.5);
  });
});
```

### 2. Load Tests

Simulate concurrent users to test scalability.

```javascript
// k6 load test script
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '1m', target: 50 },   // Ramp up to 50 users
    { duration: '3m', target: 50 },   // Stay at 50 users
    { duration: '1m', target: 100 },  // Ramp up to 100
    { duration: '3m', target: 100 },  // Stay at 100
    { duration: '1m', target: 0 },    // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<200'], // 95% under 200ms
    http_req_failed: ['rate<0.01'],   // Error rate < 1%
  },
};

export default function () {
  const res = http.get('https://api.example.com/users');

  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 200ms': (r) => r.timings.duration < 200,
  });

  sleep(1);
}
```

### 3. Stress Tests

Push system beyond normal limits.

```javascript
describe('Stress Tests', () => {
  it('handles 10000 concurrent connections', async () => {
    const connections = Array(10000).fill().map(async () => {
      const ws = new WebSocket('ws://localhost:3000');
      return new Promise((resolve, reject) => {
        ws.onopen = () => resolve(ws);
        ws.onerror = reject;
        setTimeout(() => reject(new Error('timeout')), 5000);
      });
    });

    const results = await Promise.allSettled(connections);
    const successful = results.filter(r => r.status === 'fulfilled');
    expect(successful.length).toBeGreaterThan(9000);
  });

  it('recovers after memory pressure', async () => {
    const allocations = [];

    // Allocate memory
    for (let i = 0; i < 100; i++) {
      allocations.push(new Array(1000000).fill('x'));
    }

    // Force GC and clear
    allocations.length = 0;
    if (global.gc) global.gc();

    // System should still function
    const result = await api.get('/health');
    expect(result.status).toBe(200);
  });
});
```

### 4. Memory Tests

Detect memory leaks and inefficiencies.

```javascript
describe('Memory Tests', () => {
  it('does not leak memory on repeated operations', async () => {
    const before = process.memoryUsage().heapUsed;

    for (let i = 0; i < 10000; i++) {
      await processRequest({ data: 'test' });
    }

    if (global.gc) global.gc();
    await new Promise(r => setTimeout(r, 100));

    const after = process.memoryUsage().heapUsed;
    const leaked = after - before;

    expect(leaked).toBeLessThan(10 * 1024 * 1024); // 10MB
  });

  it('releases event listeners', () => {
    const emitter = new EventEmitter();
    const before = emitter.listenerCount('event');

    for (let i = 0; i < 100; i++) {
      const handler = () => {};
      emitter.on('event', handler);
      emitter.off('event', handler);
    }

    const after = emitter.listenerCount('event');
    expect(after).toBe(before);
  });
});
```

## API Response Time Tests

```javascript
describe('API Performance', () => {
  const SLA = {
    p50: 50,   // 50th percentile under 50ms
    p95: 200,  // 95th percentile under 200ms
    p99: 500,  // 99th percentile under 500ms
  };

  it('meets response time SLA', async () => {
    const times = [];

    for (let i = 0; i < 1000; i++) {
      const start = performance.now();
      await api.get('/users');
      times.push(performance.now() - start);
    }

    times.sort((a, b) => a - b);

    const p50 = times[Math.floor(times.length * 0.50)];
    const p95 = times[Math.floor(times.length * 0.95)];
    const p99 = times[Math.floor(times.length * 0.99)];

    expect(p50).toBeLessThan(SLA.p50);
    expect(p95).toBeLessThan(SLA.p95);
    expect(p99).toBeLessThan(SLA.p99);
  });
});
```

## Database Performance Tests

```javascript
describe('Database Performance', () => {
  it('query executes under 10ms', async () => {
    const start = performance.now();
    await db.query('SELECT * FROM users WHERE id = ?', [1]);
    const duration = performance.now() - start;
    expect(duration).toBeLessThan(10);
  });

  it('handles 100 concurrent queries', async () => {
    const queries = Array(100).fill().map(() =>
      db.query('SELECT * FROM users LIMIT 10')
    );

    const start = performance.now();
    await Promise.all(queries);
    const duration = performance.now() - start;

    expect(duration).toBeLessThan(1000);
  });

  it('insert batch is efficient', async () => {
    const records = Array(1000).fill().map((_, i) => ({
      name: `User ${i}`,
      email: `user${i}@test.com`,
    }));

    const start = performance.now();
    await db.batchInsert('users', records);
    const duration = performance.now() - start;

    expect(duration).toBeLessThan(500);
  });
});
```

## Profiling Patterns

### CPU Profiling

```javascript
const v8Profiler = require('v8-profiler-next');

async function profileCPU(fn, name) {
  v8Profiler.startProfiling(name);

  await fn();

  const profile = v8Profiler.stopProfiling(name);
  profile.export((error, result) => {
    fs.writeFileSync(`${name}.cpuprofile`, result);
  });
}
```

### Memory Profiling

```javascript
function takeHeapSnapshot(name) {
  const snapshot = v8.writeHeapSnapshot();
  console.log(`Heap snapshot written to ${snapshot}`);
}

// In test
it('memory usage is stable', async () => {
  takeHeapSnapshot('before');

  for (let i = 0; i < 10000; i++) {
    await processData();
  }

  if (global.gc) global.gc();
  takeHeapSnapshot('after');
});
```

## Performance Budgets

```javascript
const budgets = {
  'First Contentful Paint': 1500,
  'Time to Interactive': 3500,
  'Total Bundle Size': 250 * 1024,
  'API Response Time': 200,
};

describe('Performance Budgets', () => {
  it('bundle size within budget', async () => {
    const stats = require('./dist/stats.json');
    const totalSize = stats.assets
      .filter(a => a.name.endsWith('.js'))
      .reduce((sum, a) => sum + a.size, 0);

    expect(totalSize).toBeLessThan(budgets['Total Bundle Size']);
  });
});
```

## Continuous Performance Testing

### GitHub Actions

```yaml
name: Performance Tests

on:
  push:
    branches: [main]
  pull_request:

jobs:
  performance:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - run: npm ci
      - run: npm run test:performance

      - name: Compare with baseline
        run: |
          npm run benchmark:compare -- --baseline main
```

## When to Use

- Before major releases
- After performance-critical changes
- During capacity planning
- When SLA monitoring shows degradation
- As part of CI/CD pipeline

## Anti-Patterns

1. **Testing on Development Hardware**: Use production-like environment
2. **Ignoring Warmup**: JIT compilation affects first runs
3. **Single Metric Focus**: Monitor multiple dimensions
4. **No Baseline**: Always compare against baseline
5. **Flaky Thresholds**: Use percentiles, not averages

Overview

This skill packages performance testing patterns for JavaScript applications, covering benchmarks, load tests, stress tests, memory checks, profiling, and performance budgets. It provides ready-made test patterns and CI integration to verify speed, scalability, and resource efficiency before release. The aim is repeatable, measurable performance validation that fits into CI/CD pipelines.

How this skill works

The skill supplies test templates and scripts that measure execution time, concurrent load behavior, memory usage, and database query performance. It integrates with tools like k6 for load testing, Node.js profilers and v8-profiler for CPU/memory profiling, and test runners for benchmarks and assertions. It also includes patterns for setting SLAs, performance budgets, and comparing results against baselines in CI.

When to use it

  • Before major releases or performance-sensitive launches
  • After making changes to hot paths, database queries, or infrastructure
  • During capacity planning and scalability evaluations
  • When SLA monitoring shows regressions or increased error rates
  • As part of CI/CD to prevent performance regressions

Best practices

  • Run tests in production-like environments and isolate noise sources
  • Include warmup phases before measuring to avoid JIT and cold-start artifacts
  • Use percentiles (p50/p95/p99) and thresholds instead of averages
  • Maintain a baseline and compare each run to detect regressions
  • Automate benchmarks and load tests in CI but gate long-running stress tests to scheduled pipelines
  • Monitor multiple dimensions: latency, throughput, error rate, CPU, memory, and GC

Example use cases

  • Benchmark core library functions to ensure microsecond-level performance targets
  • Run k6 load test scripts to validate API latency and error rate at target user loads
  • Stress test WebSocket or connection handling to validate resource limits and recovery behavior
  • Profile CPU and heap during simulated workloads to locate hotspots and memory leaks
  • Enforce bundle size and frontend performance budgets as part of release checks

FAQ

How do I choose between load, stress, and benchmark tests?

Use benchmarks for isolated operation timing, load tests to validate normal-scale concurrency and SLAs, and stress tests to find breaking points and recovery behavior beyond expected load.

Where should performance tests run in CI?

Run fast benchmarks and smoke load checks on pull requests; schedule longer load and stress tests on main branches or nightly pipelines to avoid slowing developer feedback loops.