Performance Testing with K6

Load test your APIs with integrated K6 support

Bytedocs includes integrated K6 load testing, allowing you to test API performance directly from the documentation UI.

Availability

K6 Performance Testing is currently available only in the Laravel implementation. Support for other languages is planned.

What is K6?

K6 is Grafana's open-source load testing tool designed for testing the performance, load capacity, and reliability of APIs and websites.

Why K6?

  • Developer-friendly: Write tests in JavaScript
  • Accurate: Produces realistic load
  • Flexible: Support for various testing scenarios
  • Detailed metrics: Comprehensive performance data
  • Open source: Free and actively maintained

Prerequisites

Install K6

macOS

brew install k6

Linux

# Debian/Ubuntu
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6

# Fedora/CentOS
sudo dnf install https://dl.k6.io/rpm/repo.rpm
sudo dnf install k6

Windows

# Using Chocolatey
choco install k6

# Or download from https://k6.io/docs/getting-started/installation/

Docker

docker pull grafana/k6

Verify Installation

k6 version

Setup in Laravel

K6 integration is automatically available in Laravel once Bytedocs is installed.

Configuration

// config/bytedocs.php
'k6' => [
    'enabled' => env('BYTEDOCS_K6_ENABLED', true),
    'path' => env('BYTEDOCS_K6_PATH', null),  // Auto-detect if null
    'default_vus' => env('BYTEDOCS_K6_DEFAULT_VUS', 10),
    'default_duration' => env('BYTEDOCS_K6_DEFAULT_DURATION', '30s'),
],

Environment Variables

# .env
BYTEDOCS_K6_ENABLED=true
BYTEDOCS_K6_PATH=/usr/local/bin/k6
BYTEDOCS_K6_DEFAULT_VUS=10
BYTEDOCS_K6_DEFAULT_DURATION=30s

Running Performance Tests

Via UI

  1. Open Bytedocs at /docs
  2. Select an endpoint you want to test
  3. Click "Performance Test" button
  4. Configure test parameters:
    • Virtual Users (VUs)
    • Test duration
    • Ramp-up stages (optional)
  5. Click "Run Test"
  6. View results in real-time

Via API

POST /docs/k6/test
Content-Type: application/json

{
  "endpoint": "/api/users",
  "method": "GET",
  "vus": 10,
  "duration": "30s",
  "headers": {
    "Authorization": "Bearer token"
  }
}

Test Configuration

Constant Load

Run with a fixed number of virtual users:

{
  "test_type": "constant",
  "vus": 10,
  "duration": "30s"
}

Generated K6 script:

import http from 'k6/http';
import { sleep, check } from 'k6';

export const options = {
    vus: 10,
    duration: '30s',
};

export default function() {
    const res = http.get('http://localhost:8000/api/users');

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

    sleep(1);
}

Stages (Ramp Up/Down)

Gradually increase and decrease load:

{
  "test_type": "stages",
  "stages": [
    { "duration": "10s", "target": 10 },
    { "duration": "30s", "target": 50 },
    { "duration": "10s", "target": 0 }
  ]
}

Generated K6 script:

export const options = {
    stages: [
        { duration: '10s', target: 10 },   // Ramp up to 10
        { duration: '30s', target: 50 },   // Ramp up to 50
        { duration: '10s', target: 0 },    // Ramp down to 0
    ],
};

Stress Test

Find breaking point:

{
  "test_type": "stress",
  "stages": [
    { "duration": "2m", "target": 100 },
    { "duration": "5m", "target": 100 },
    { "duration": "2m", "target": 200 },
    { "duration": "5m", "target": 200 },
    { "duration": "2m", "target": 300 },
    { "duration": "5m", "target": 300 },
    { "duration": "10m", "target": 0 }
  ]
}

Spike Test

Sudden traffic surge:

{
  "test_type": "spike",
  "stages": [
    { "duration": "10s", "target": 10 },
    { "duration": "1m", "target": 10 },
    { "duration": "10s", "target": 1000 },  // Spike!
    { "duration": "3m", "target": 1000 },
    { "duration": "10s", "target": 10 },
    { "duration": "3m", "target": 10 },
    { "duration": "10s", "target": 0 }
  ]
}

Soak Test (Endurance)

Long-duration test:

{
  "test_type": "soak",
  "vus": 50,
  "duration": "4h"
}

Test Parameters

Virtual Users (VUs)

Number of concurrent users:

{
  "vus": 10      // 10 concurrent users
}

Guidelines:

  • 10-50: Light load, basic testing
  • 50-200: Medium load, typical usage
  • 200-1000: Heavy load, peak traffic
  • 1000+: Stress testing, extreme scenarios

Duration

Test length:

{
  "duration": "30s"   // 30 seconds
}

Formats:

  • "30s" - 30 seconds
  • "5m" - 5 minutes
  • "1h" - 1 hour
  • "2h30m" - 2 hours 30 minutes

Request Configuration

{
  "method": "POST",
  "headers": {
    "Content-Type": "application/json",
    "Authorization": "Bearer token"
  },
  "body": {
    "name": "John Doe",
    "email": "john@example.com"
  }
}

Thresholds

Define pass/fail criteria:

export const options = {
    thresholds: {
        http_req_duration: ['p(95)<500'],  // 95% of requests < 500ms
        http_req_failed: ['rate<0.01'],    // Error rate < 1%
    },
};

Understanding Results

Key Metrics

HTTP Requests

http_reqs..................: 3000  (100/s)
  • Total requests made
  • (Requests per second)

Request Duration

http_req_duration..........: avg=234ms min=89ms med=210ms max=1.2s p(90)=350ms p(95)=450ms
  • avg: Average response time
  • min: Fastest response
  • med: Median response
  • max: Slowest response
  • p(90): 90th percentile
  • p(95): 95th percentile

Failed Requests

http_req_failed............: 0.33% (10 out of 3000)
  • Percentage of failed requests
  • (Count)

Data Transfer

data_received..............: 15 MB (500 kB/s)
data_sent..................: 1.2 MB (40 kB/s)
  • Total data transferred
  • (Rate per second)

Virtual Users

vus........................: 50
vus_max....................: 50
  • Current VUs
  • Max VUs during test

Response Time Guidelines

PercentileTargetGoodAcceptablePoor
Average< 100ms< 200ms< 500ms> 500ms
P95< 200ms< 500ms< 1s> 1s
P99< 500ms< 1s< 2s> 2s

Error Rate Guidelines

Error RateStatus
0%Excellent
< 0.1%Good
0.1% - 1%Acceptable
> 1%Poor

Interpreting Results

Example Output

scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration

execution: local
script: test.js
output: -

running (0m30.0s), 00/10 VUs, 3000 complete and 0 interrupted iterations

     data_received..................: 15 MB  500 kB/s
     data_sent......................: 1.2 MB 40 kB/s
     http_req_blocked...............: avg=1.2ms    min=0s       med=0s      max=234ms   p(90)=0s      p(95)=0s
     http_req_connecting............: avg=1.1ms    min=0s       med=0s      max=233ms   p(90)=0s      p(95)=0s
     http_req_duration..............: avg=234.5ms  min=89.3ms   med=210ms   max=1.2s    p(90)=350ms   p(95)=450ms
       { expected_response:true }...: avg=234.5ms  min=89.3ms   med=210ms   max=1.2s    p(90)=350ms   p(95)=450ms
     http_req_failed................: 0.33%  ✓ 10       ✗ 2990
     http_req_receiving.............: avg=0.5ms    min=0s       med=0.3ms   max=15ms    p(90)=1.2ms   p(95)=2.1ms
     http_req_sending...............: avg=0.1ms    min=0s       med=0.05ms  max=5.2ms   p(90)=0.2ms   p(95)=0.4ms
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s
     http_req_waiting...............: avg=233.9ms  min=88.9ms   med=209.5ms max=1.19s   p(90)=349.1ms p(95)=449ms
     http_reqs......................: 3000   100/s
     iteration_duration.............: avg=1.23s    min=1.09s    med=1.21s   max=2.23s   p(90)=1.35s   p(95)=1.45s
     iterations.....................: 3000   100/s
     vus............................: 10     min=10     max=10
     vus_max........................: 10     min=10     max=10

Analysis

Good Performance:

  • Average response time: 234ms
  • P95 response time: 450ms
  • Error rate: 0.33% (acceptable)
  • Throughput: 100 req/s

⚠️ Areas to Watch:

  • Max response time: 1.2s (investigate slow requests)
  • Some connection delays (max 234ms blocked time)

Advanced K6 Features

Custom Scripts

Create advanced test scenarios:

import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { Rate } from 'k6/metrics';

const errorRate = new Rate('errors');

export const options = {
    stages: [
        { duration: '30s', target: 20 },
        { duration: '1m', target: 20 },
        { duration: '30s', target: 0 },
    ],
    thresholds: {
        errors: ['rate<0.1'],
        http_req_duration: ['p(95)<500'],
    },
};

export default function() {
    group('User Registration Flow', function() {
        // Step 1: Register
        let registerRes = http.post('http://api.example.com/register', JSON.stringify({
            name: 'John Doe',
            email: `user-${__VU}-${__ITER}@example.com`,
        }), {
            headers: { 'Content-Type': 'application/json' },
        });

        check(registerRes, {
            'registration successful': (r) => r.status === 201,
        }) || errorRate.add(1);

        // Step 2: Login
        let loginRes = http.post('http://api.example.com/login', JSON.stringify({
            email: `user-${__VU}-${__ITER}@example.com`,
            password: 'password123',
        }), {
            headers: { 'Content-Type': 'application/json' },
        });

        let token = loginRes.json('token');

        check(loginRes, {
            'login successful': (r) => r.status === 200,
            'token received': (r) => token !== undefined,
        }) || errorRate.add(1);

        // Step 3: Get Profile
        let profileRes = http.get('http://api.example.com/profile', {
            headers: {
                'Authorization': `Bearer ${token}`,
            },
        });

        check(profileRes, {
            'profile retrieved': (r) => r.status === 200,
        }) || errorRate.add(1);

        sleep(1);
    });
}

Environment-Specific Tests

const BASE_URL = __ENV.BASE_URL || 'http://localhost:8000';
const AUTH_TOKEN = __ENV.AUTH_TOKEN || '';

export default function() {
    http.get(`${BASE_URL}/api/users`, {
        headers: {
            'Authorization': `Bearer ${AUTH_TOKEN}`,
        },
    });
}

Run with environment variables:

k6 run -e BASE_URL=https://api.production.com -e AUTH_TOKEN=xyz script.js

Best Practices

1. Start Small

{
  "vus": 1,
  "duration": "10s"
}

Gradually increase load.

2. Use Realistic Scenarios

Simulate actual user behavior:

export default function() {
    // Browse homepage
    http.get('/');
    sleep(2);

    // View product
    http.get('/products/123');
    sleep(3);

    // Add to cart
    http.post('/cart', payload);
    sleep(1);

    // Checkout
    http.post('/checkout', payload);
}

3. Set Thresholds

Define success criteria:

export const options = {
    thresholds: {
        http_req_duration: ['p(95)<500', 'p(99)<1000'],
        http_req_failed: ['rate<0.01'],
    },
};

4. Test Different Scenarios

  • Happy path: Normal user flow
  • Edge cases: Max input sizes, unusual patterns
  • Error handling: Invalid inputs, auth failures

5. Monitor Server Resources

While running tests, monitor:

  • CPU usage
  • Memory usage
  • Database connections
  • Network I/O

Troubleshooting

K6 Not Found

Error: k6 executable not found

Solutions:

  1. Verify installation: k6 version
  2. Check PATH: which k6
  3. Specify path in config:
'k6' => [
    'path' => '/usr/local/bin/k6',
],

High Error Rates

Causes:

  • Server overloaded
  • Database connections exhausted
  • Timeout too short
  • Network issues

Solutions:

  1. Reduce VUs
  2. Increase server resources
  3. Optimize database queries
  4. Check network latency

Inconsistent Results

Causes:

  • Background processes
  • Network variability
  • Caching effects

Solutions:

  1. Run multiple tests
  2. Test at different times
  3. Use dedicated test environment
  4. Clear caches between tests

What's Next?