# Performance Testing Reference

Comprehensive guide to load testing, stress testing, and performance validation with K6, Locust, and Artillery.

## K6

Modern load testing tool built for developer experience with JavaScript scripting.

**Container**: `grafana/k6:latest`

### Basic Test Script

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

// Custom metrics
const errorRate = new Rate('errors');
const apiDuration = new Trend('api_duration');
const apiRequests = new Counter('api_requests');

export const options = {
  vus: 10,
  duration: '30s',
  thresholds: {
    http_req_duration: ['p(95)<500'],
    http_req_failed: ['rate<0.01'],
    errors: ['rate<0.1'],
  },
};

export default function () {
  const res = http.get('http://target:8080/api/health');

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

  errorRate.add(res.status !== 200);
  apiDuration.add(res.timings.duration);
  apiRequests.add(1);

  sleep(1);
}
```

### Test Patterns

#### Smoke Test
```javascript
// Quick sanity check
export const options = {
  vus: 1,
  duration: '1m',
  thresholds: {
    http_req_failed: ['rate<0.01'],
    http_req_duration: ['p(99)<1500'],
  },
};
```

#### Load Test
```javascript
// Normal expected load
export const options = {
  stages: [
    { duration: '5m', target: 100 },  // Ramp up
    { duration: '30m', target: 100 }, // Stay at load
    { duration: '5m', target: 0 },    // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'],
    http_req_failed: ['rate<0.01'],
  },
};
```

#### Stress Test
```javascript
// Find breaking points
export const options = {
  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: '2m', target: 400 },
    { duration: '5m', target: 400 },
    { duration: '10m', target: 0 },
  ],
};
```

#### Spike Test
```javascript
// Sudden traffic surge
export const options = {
  stages: [
    { duration: '1m', target: 10 },   // Normal load
    { duration: '10s', target: 500 }, // Spike
    { duration: '3m', target: 500 },  // Stay at spike
    { duration: '10s', target: 10 },  // Back to normal
    { duration: '3m', target: 10 },
    { duration: '1m', target: 0 },
  ],
};
```

#### Soak Test
```javascript
// Endurance testing
export const options = {
  stages: [
    { duration: '5m', target: 100 },
    { duration: '8h', target: 100 }, // Long duration
    { duration: '5m', target: 0 },
  ],
};
```

### Advanced Features

#### Scenarios

```javascript
export const options = {
  scenarios: {
    // Constant load baseline
    constant_load: {
      executor: 'constant-vus',
      vus: 50,
      duration: '10m',
    },
    // Ramping arrival rate
    ramping_arrival: {
      executor: 'ramping-arrival-rate',
      startRate: 10,
      timeUnit: '1s',
      preAllocatedVUs: 50,
      maxVUs: 200,
      stages: [
        { duration: '5m', target: 50 },
        { duration: '5m', target: 100 },
        { duration: '5m', target: 50 },
      ],
    },
    // Shared iterations
    shared_iterations: {
      executor: 'shared-iterations',
      vus: 10,
      iterations: 1000,
      maxDuration: '30m',
    },
  },
};
```

#### HTTP Requests

```javascript
import http from 'k6/http';

// GET with headers
const res = http.get('http://api.example.com/users', {
  headers: {
    'Authorization': 'Bearer ' + token,
    'Content-Type': 'application/json',
  },
});

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

const res = http.post('http://api.example.com/users', payload, {
  headers: { 'Content-Type': 'application/json' },
});

// Batch requests (parallel)
const responses = http.batch([
  ['GET', 'http://api.example.com/users'],
  ['GET', 'http://api.example.com/products'],
  ['POST', 'http://api.example.com/orders', JSON.stringify({ productId: 1 })],
]);

// File upload
const file = open('/path/to/file.txt', 'b');
const res = http.post('http://api.example.com/upload', {
  file: http.file(file, 'file.txt'),
});
```

#### Checks and Groups

```javascript
import { check, group, sleep } from 'k6';

export default function () {
  group('user flow', function () {
    group('login', function () {
      const res = http.post('http://target:8080/login', credentials);
      check(res, {
        'login successful': (r) => r.status === 200,
        'has token': (r) => r.json('token') !== undefined,
      });
    });

    group('fetch profile', function () {
      const res = http.get('http://target:8080/profile');
      check(res, {
        'profile loaded': (r) => r.status === 200,
      });
    });
  });
}
```

### Running K6

```bash
# Basic run
docker exec k6 k6 run /scripts/load-test.js

# Override options
docker exec k6 k6 run --vus 50 --duration 5m /scripts/load-test.js

# JSON output
docker exec k6 k6 run --out json=/results/metrics.json /scripts/load-test.js

# Multiple outputs
docker exec k6 k6 run --out json=/results/metrics.json --out csv=/results/metrics.csv /scripts/load-test.js

# Cloud execution
k6 cloud /scripts/load-test.js
```

## Locust

Python-based distributed load testing with real-time web UI.

**Container**: `locustio/locust:latest`
**Web UI**: http://localhost:8089

### Basic Locustfile

```python
# locust-scripts/locustfile.py
from locust import HttpUser, task, between, events
import logging

class WebsiteUser(HttpUser):
    wait_time = between(1, 5)

    def on_start(self):
        """Run on user start - login, etc."""
        self.client.post("/login", json={
            "username": "testuser",
            "password": "password123"
        })

    @task(3)  # Weight: 3x more likely
    def view_homepage(self):
        self.client.get("/")

    @task(2)
    def view_products(self):
        self.client.get("/products")

    @task(1)
    def view_cart(self):
        self.client.get("/cart")

    @task(1)
    def checkout(self):
        with self.client.post("/checkout", json={"item_id": 1}, catch_response=True) as response:
            if response.status_code == 200:
                response.success()
            else:
                response.failure(f"Checkout failed: {response.status_code}")
```

### Advanced Patterns

#### Sequential Tasks

```python
from locust import HttpUser, task, between, SequentialTaskSet

class UserBehavior(SequentialTaskSet):
    @task
    def login(self):
        self.client.post("/login", json={"user": "test", "pass": "test"})

    @task
    def browse_products(self):
        self.client.get("/products")

    @task
    def add_to_cart(self):
        self.client.post("/cart", json={"product_id": 1})

    @task
    def checkout(self):
        self.client.post("/checkout")
        self.interrupt()  # End this sequence

class WebsiteUser(HttpUser):
    wait_time = between(1, 3)
    tasks = [UserBehavior]
```

#### Custom Events

```python
from locust import events
import time

@events.request.add_listener
def on_request(request_type, name, response_time, response_length, response, **kwargs):
    if response_time > 1000:
        logging.warning(f"Slow request: {name} took {response_time}ms")

@events.test_start.add_listener
def on_test_start(environment, **kwargs):
    print("Test starting...")

@events.test_stop.add_listener
def on_test_stop(environment, **kwargs):
    print(f"Test complete. Total requests: {environment.stats.total.num_requests}")
```

#### Distributed Testing

```python
# Master node
locust --master -f locustfile.py

# Worker nodes
locust --worker --master-host=master-ip -f locustfile.py
```

### Running Locust

```bash
# Web UI mode
docker exec locust locust -f /locust/locustfile.py --host http://target:8080

# Headless mode
docker exec locust locust -f /locust/locustfile.py \
  --headless \
  --host http://target:8080 \
  -u 100 \
  -r 10 \
  -t 5m

# With HTML report
docker exec locust locust -f /locust/locustfile.py \
  --headless \
  --host http://target:8080 \
  -u 100 \
  -r 10 \
  -t 5m \
  --html=/reports/locust-report.html
```

## Artillery

Scenario-based load testing with YAML configuration.

**Container**: `artilleryio/artillery:latest`

### Basic Configuration

```yaml
# artillery-scripts/load-test.yml
config:
  target: "http://target:8080"
  phases:
    - duration: 60
      arrivalRate: 5
      name: "Warm up"
    - duration: 120
      arrivalRate: 10
      rampTo: 50
      name: "Ramp up"
    - duration: 300
      arrivalRate: 50
      name: "Sustained load"
  defaults:
    headers:
      Content-Type: "application/json"

scenarios:
  - name: "User journey"
    flow:
      - get:
          url: "/"
      - think: 1
      - post:
          url: "/api/login"
          json:
            username: "testuser"
            password: "password123"
          capture:
            - json: "$.token"
              as: "authToken"
      - get:
          url: "/api/profile"
          headers:
            Authorization: "Bearer {{ authToken }}"
      - think: 2
      - get:
          url: "/api/products"
```

### Advanced Features

#### Plugins

```yaml
config:
  target: "http://target:8080"
  plugins:
    expect: {}
    metrics-by-endpoint: {}

scenarios:
  - flow:
      - get:
          url: "/api/health"
          expect:
            - statusCode: 200
            - contentType: "application/json"
            - hasProperty: "status"
```

#### CSV Data

```yaml
config:
  target: "http://target:8080"
  payload:
    path: "users.csv"
    fields:
      - "username"
      - "password"

scenarios:
  - flow:
      - post:
          url: "/api/login"
          json:
            username: "{{ username }}"
            password: "{{ password }}"
```

#### Conditional Logic

```yaml
scenarios:
  - flow:
      - get:
          url: "/api/products"
          capture:
            - json: "$[0].id"
              as: "productId"
      - think: 1
      - post:
          url: "/api/cart"
          json:
            productId: "{{ productId }}"
          ifTrue: "productId"  # Only if product found
```

### Running Artillery

```bash
# Basic run
docker exec artillery artillery run /scripts/load-test.yml

# With report
docker exec artillery artillery run /scripts/load-test.yml --output /reports/artillery-report.json

# Generate HTML report
docker exec artillery artillery report /reports/artillery-report.json --output /reports/report.html

# Quick test
docker exec artillery artillery quick --count 10 -n 20 http://target:8080/api/health
```

## Tool Comparison

| Feature | K6 | Locust | Artillery |
|---------|----|---------| ----------|
| Language | JavaScript | Python | YAML + JS |
| Learning Curve | Medium | Low | Low |
| Distributed | Cloud | Native | Cloud |
| Web UI | No | Yes | No |
| Protocol Support | HTTP, WebSocket, gRPC | HTTP, WebSocket | HTTP, WebSocket, Socket.io |
| Best For | CI/CD, Developers | Interactive testing | Quick scenarios |
| Resource Usage | Low | Medium | Low |

## Performance Metrics

### Key Metrics to Track

| Metric | Description | Target |
|--------|-------------|--------|
| Response Time (p50) | Median response time | < 200ms |
| Response Time (p95) | 95th percentile | < 500ms |
| Response Time (p99) | 99th percentile | < 1000ms |
| Error Rate | Percentage of failed requests | < 1% |
| Throughput | Requests per second | > 1000 RPS |
| Concurrent Users | Active virtual users | Depends on SLA |

### Interpreting Results

```
Response Time Distribution:
p50  = 150ms  ← Median user experience
p75  = 250ms  ← Most users
p90  = 400ms  ← Some users experiencing slowness
p95  = 600ms  ← Starting to see issues
p99  = 1200ms ← Outliers, potential problems

If p95 >> p50, investigate:
- Database queries
- External API calls
- Resource contention
```

## Performance Testing Patterns

### Pattern 1: Baseline Establishment

```javascript
// baseline.js - Establish performance baseline
export const options = {
  scenarios: {
    baseline: {
      executor: 'constant-vus',
      vus: 10,
      duration: '5m',
    },
  },
  thresholds: {
    http_req_duration: ['p(95)<200', 'p(99)<400'],
    http_req_failed: ['rate<0.01'],
  },
};
```

### Pattern 2: Capacity Planning

```javascript
// Find maximum sustainable load
export const options = {
  scenarios: {
    find_capacity: {
      executor: 'ramping-arrival-rate',
      startRate: 10,
      timeUnit: '1s',
      preAllocatedVUs: 50,
      maxVUs: 500,
      stages: [
        { duration: '2m', target: 50 },
        { duration: '2m', target: 100 },
        { duration: '2m', target: 150 },
        { duration: '2m', target: 200 },
        { duration: '2m', target: 250 },
        { duration: '5m', target: 300 }, // Sustain
      ],
    },
  },
};
```

### Pattern 3: Regression Testing

```javascript
// Compare against baseline
const BASELINE = {
  p95: 200,
  p99: 400,
  errorRate: 0.01,
};

export const options = {
  thresholds: {
    http_req_duration: [
      `p(95)<${BASELINE.p95 * 1.1}`, // 10% tolerance
      `p(99)<${BASELINE.p99 * 1.1}`,
    ],
    http_req_failed: [`rate<${BASELINE.errorRate}`],
  },
};
```

## Best Practices

1. **Start with smoke tests** - Quick sanity check before longer tests
2. **Establish baselines** - Know your current performance before changes
3. **Use realistic data** - Test with production-like data volumes
4. **Include think time** - Simulate real user behavior
5. **Test under monitoring** - Correlate performance with system metrics
6. **Test in isolation** - Minimize external factors
7. **Warm up the system** - Allow caches to populate
8. **Document results** - Track trends over time
9. **Set meaningful thresholds** - Based on SLAs/SLOs
10. **Test regularly** - Catch regressions early
