Agent skill

load-testing

Auto-activates when user mentions load test, performance test, stress test, k6, Artillery, benchmark, or scalability testing. Expert in designing and executing performance tests.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/load-testing

SKILL.md

Load Testing & Performance Testing

Creates comprehensive load tests using k6, Artillery, and other tools to validate application performance under stress.

When This Activates

  • User says: "load test this", "performance test", "stress test", "benchmark"
  • User mentions: "k6", "Artillery", "JMeter", "Gatling", "scalability"
  • Performance validation needed
  • Pre-production testing
  • Capacity planning questions

Load Testing Tools Comparison

k6 (Recommended)

  • Best for: Modern apps, APIs, microservices
  • Language: JavaScript/TypeScript
  • Pros: Cloud-native, great metrics, scriptable
  • Use when: Testing REST APIs, GraphQL, WebSocket

Artillery

  • Best for: Quick tests, CI/CD integration
  • Language: YAML + JavaScript
  • Pros: Simple config, plugins, HTTP/WS/Socket.io
  • Use when: Simple scenarios, rapid testing

Apache JMeter

  • Best for: Enterprise apps, complex scenarios
  • Language: GUI-based + Java
  • Pros: Mature, extensive protocols, GUI
  • Use when: Legacy systems, complex workflows

k6 Load Testing

Basic Load Test

javascript
// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';

// Custom metrics
const errorRate = new Rate('errors');
const checkDuration = new Trend('check_duration');
const requests = new Counter('requests');

// Test configuration
export const options = {
  stages: [
    { duration: '2m', target: 10 },   // Ramp up to 10 users
    { duration: '5m', target: 10 },   // Stay at 10 users
    { duration: '2m', target: 50 },   // Ramp up to 50 users
    { duration: '5m', target: 50 },   // Stay at 50 users
    { duration: '2m', target: 100 },  // Ramp up to 100 users
    { duration: '5m', target: 100 },  // Stay at 100 users
    { duration: '2m', target: 0 },    // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'], // 95% < 500ms, 99% < 1s
    http_req_failed: ['rate<0.01'],                  // Error rate < 1%
    errors: ['rate<0.1'],                            // Custom error rate < 10%
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';
const API_TOKEN = __ENV.API_TOKEN || '';

export default function () {
  const params = {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${API_TOKEN}`,
    },
    tags: { name: 'GetUsers' },
  };

  // GET request
  let response = http.get(`${BASE_URL}/api/users`, params);
  
  const checkResult = check(response, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
    'has users': (r) => JSON.parse(r.body).data.length > 0,
  });

  errorRate.add(!checkResult);
  checkDuration.add(response.timings.duration);
  requests.add(1);

  // POST request
  const payload = JSON.stringify({
    name: 'Test User',
    email: `test-${Date.now()}@example.com`,
  });

  response = http.post(`${BASE_URL}/api/users`, payload, params);
  
  check(response, {
    'user created': (r) => r.status === 201,
  });

  sleep(1); // Think time between requests
}

export function handleSummary(data) {
  return {
    'summary.json': JSON.stringify(data),
    stdout: textSummary(data, { indent: ' ', enableColors: true }),
  };
}

Advanced Scenarios

javascript
// advanced-test.js
import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';

// Load test data
const users = new SharedArray('users', function () {
  return papaparse.parse(open('./users.csv'), { header: true }).data;
});

export const options = {
  scenarios: {
    // Scenario 1: Constant load
    constant_load: {
      executor: 'constant-vus',
      vus: 50,
      duration: '5m',
    },
    // Scenario 2: Ramping load
    ramping_load: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '5m', target: 100 },
        { duration: '10m', target: 100 },
        { duration: '5m', target: 0 },
      ],
      gracefulRampDown: '30s',
    },
    // Scenario 3: Stress test
    stress_test: {
      executor: 'ramping-arrival-rate',
      startRate: 50,
      timeUnit: '1s',
      preAllocatedVUs: 500,
      maxVUs: 1000,
      stages: [
        { duration: '2m', target: 100 },
        { duration: '5m', target: 200 },
        { duration: '2m', target: 500 },
        { duration: '5m', target: 500 },
        { duration: '2m', target: 0 },
      ],
    },
  },
};

export default function () {
  const user = users[Math.floor(Math.random() * users.length)];

  group('User Flow', function () {
    // 1. Login
    group('Login', function () {
      const loginRes = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
        email: user.email,
        password: user.password,
      }), {
        headers: { 'Content-Type': 'application/json' },
      });

      check(loginRes, {
        'login successful': (r) => r.status === 200,
      });

      const token = loginRes.json('token');
      
      // 2. Get profile
      group('Get Profile', function () {
        const profileRes = http.get(`${BASE_URL}/api/profile`, {
          headers: {
            'Authorization': `Bearer ${token}`,
          },
        });

        check(profileRes, {
          'profile loaded': (r) => r.status === 200,
        });
      });

      // 3. Create post
      group('Create Post', function () {
        const postRes = http.post(`${BASE_URL}/api/posts`, JSON.stringify({
          title: 'Load Test Post',
          content: 'This is a test post from k6',
        }), {
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}`,
          },
        });

        check(postRes, {
          'post created': (r) => r.status === 201,
        });
      });
    });
  });

  sleep(Math.random() * 3 + 2); // Random think time 2-5s
}

GraphQL Load Test

javascript
// graphql-test.js
import http from 'k6/http';
import { check } from 'k6';

export const options = {
  vus: 50,
  duration: '5m',
  thresholds: {
    http_req_duration: ['p(95)<1000'],
  },
};

const GRAPHQL_ENDPOINT = `${__ENV.BASE_URL}/graphql`;

export default function () {
  const query = `
    query GetPosts($first: Int!) {
      posts(first: $first) {
        edges {
          node {
            id
            title
            author {
              name
            }
          }
        }
      }
    }
  `;

  const variables = { first: 20 };

  const response = http.post(
    GRAPHQL_ENDPOINT,
    JSON.stringify({ query, variables }),
    {
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${__ENV.API_TOKEN}`,
      },
    }
  );

  check(response, {
    'no errors': (r) => !r.json('errors'),
    'has data': (r) => r.json('data.posts.edges.length') > 0,
  });
}

Artillery Load Testing

Basic Configuration

yaml
# artillery.yml
config:
  target: "http://localhost:3000"
  phases:
    - duration: 60
      arrivalRate: 10     # 10 users per second
      name: "Warm up"
    - duration: 300
      arrivalRate: 50     # 50 users per second
      name: "Sustained load"
    - duration: 120
      arrivalRate: 100    # 100 users per second
      name: "Spike"
  
  processor: "./helpers.js"
  
  variables:
    apiToken: "{{ $processEnvironment.API_TOKEN }}"
  
  plugins:
    expect: {}
    metrics-by-endpoint: {}

scenarios:
  - name: "User Registration and Login"
    flow:
      - post:
          url: "/api/auth/register"
          json:
            email: "user-{{ $randomString() }}@example.com"
            name: "Test User"
            password: "password123"
          capture:
            - json: "$.token"
              as: "authToken"
          expect:
            - statusCode: 201
            - contentType: json
            - hasProperty: token

      - get:
          url: "/api/profile"
          headers:
            Authorization: "Bearer {{ authToken }}"
          expect:
            - statusCode: 200

      - post:
          url: "/api/posts"
          headers:
            Authorization: "Bearer {{ authToken }}"
          json:
            title: "Test Post"
            content: "This is a test"
          expect:
            - statusCode: 201

  - name: "Browse Posts"
    weight: 3
    flow:
      - get:
          url: "/api/posts?page=1&limit=20"
          expect:
            - statusCode: 200
            - hasProperty: data

      - think: 2  # Wait 2 seconds

      - get:
          url: "/api/posts/{{ $randomNumber(1, 100) }}"
          expect:
            - statusCode: [200, 404]
javascript
// helpers.js
module.exports = {
  generateRandomEmail,
  logResponse,
};

function generateRandomEmail(context, events, done) {
  context.vars.email = `user-${Date.now()}@example.com`;
  return done();
}

function logResponse(requestParams, response, context, ee, next) {
  if (response.statusCode >= 400) {
    console.error(`Error ${response.statusCode}: ${response.body}`);
  }
  return next();
}

Running Tests

bash
# k6
k6 run load-test.js
k6 run --vus 100 --duration 5m load-test.js
k6 run --env BASE_URL=https://api.example.com load-test.js

# k6 Cloud
k6 cloud load-test.js

# Artillery
artillery run artillery.yml
artillery run --target https://api.example.com artillery.yml

# Artillery with environment
API_TOKEN=xxx artillery run artillery.yml

# Generate HTML report
artillery run --output report.json artillery.yml
artillery report report.json

CI/CD Integration

GitHub Actions

yaml
# .github/workflows/load-test.yml
name: Load Test

on:
  schedule:
    - cron: '0 2 * * *'  # Run daily at 2 AM
  workflow_dispatch:

jobs:
  load-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Run k6 load test
        uses: grafana/k6-action@v0.3.0
        with:
          filename: tests/load-test.js
          cloud: true
          token: ${{ secrets.K6_CLOUD_TOKEN }}
        env:
          BASE_URL: ${{ secrets.STAGING_URL }}
          API_TOKEN: ${{ secrets.API_TOKEN }}
      
      - name: Upload results
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: load-test-results
          path: summary.json

Performance Benchmarking

javascript
// benchmark.js
import http from 'k6/http';
import { check } from 'k6';

export const options = {
  scenarios: {
    baseline: {
      executor: 'constant-vus',
      vus: 1,
      duration: '1m',
      tags: { test_type: 'baseline' },
    },
  },
};

export default function () {
  const endpoints = [
    '/api/users',
    '/api/posts',
    '/api/comments',
  ];

  endpoints.forEach(endpoint => {
    const response = http.get(`${BASE_URL}${endpoint}`);
    
    check(response, {
      [`${endpoint} status 200`]: (r) => r.status === 200,
    });
  });
}

export function handleSummary(data) {
  const baseline = {
    endpoints: {},
    timestamp: new Date().toISOString(),
  };

  for (const [name, metric] of Object.entries(data.metrics)) {
    if (name.includes('http_req_duration')) {
      baseline.endpoints[name] = {
        avg: metric.values.avg,
        p95: metric.values['p(95)'],
        p99: metric.values['p(99)'],
      };
    }
  }

  return {
    'baseline.json': JSON.stringify(baseline, null, 2),
  };
}

Analyzing Results

Key Metrics to Monitor

  1. Response Time

    • Average, p95, p99
    • Target: p95 < 500ms, p99 < 1s
  2. Error Rate

    • HTTP 4xx, 5xx errors
    • Target: < 1%
  3. Throughput

    • Requests per second
    • Target: Based on requirements
  4. Concurrent Users

    • Maximum sustainable load
    • Target: Based on traffic projections

Identifying Bottlenecks

javascript
// Find slow endpoints
const slowRequests = data.metrics.http_req_duration.values;
console.log(`Average: ${slowRequests.avg}ms`);
console.log(`p95: ${slowRequests['p(95)']}ms`);
console.log(`p99: ${slowRequests['p(99)']}ms`);

// Check error patterns
const failedRequests = data.metrics.http_req_failed.values.rate;
if (failedRequests > 0.01) {
  console.error(`Error rate: ${failedRequests * 100}%`);
}

Best Practices

Test Types

  1. Smoke Test: 1-2 VUs, 1-2 minutes

    • Verify system works under minimal load
  2. Load Test: Expected normal/peak load, 5-15 minutes

    • Validate performance under typical conditions
  3. Stress Test: Beyond peak load, gradually increase

    • Find breaking point
  4. Spike Test: Sudden large increase, then drop

    • Test auto-scaling, recovery
  5. Soak Test: Moderate load, 1+ hours

    • Find memory leaks, degradation

Checklist

  • Define performance requirements (SLAs)
  • Identify critical user flows
  • Prepare test data (users, content)
  • Set up realistic scenarios
  • Configure appropriate thresholds
  • Test in staging environment first
  • Monitor system resources during tests
  • Analyze results against baselines
  • Document bottlenecks and fixes
  • Run tests regularly (CI/CD)
  • Compare results over time

Design load tests, execute tests, analyze results, provide optimization recommendations.

Didn't find tool you were looking for?

Be as detailed as possible for better results