Netspective Logo
Automated Testing

Performance Testing

Load testing, stress testing, and performance benchmarking strategies

Performance testing ensures your application can handle expected (and unexpected) load while maintaining acceptable response times. For regulated systems, performance is often a compliance requirement affecting patient safety or data integrity.

Types of Performance Testing

TypePurposeWhen to Use
Load TestingTest expected loadBefore release, after major changes
Stress TestingFind breaking pointsCapacity planning
Spike TestingHandle sudden load increasesTraffic surge scenarios
Endurance TestingLong-term stabilityMemory leaks, resource exhaustion
Scalability TestingScaling behaviorCloud infrastructure planning

Performance Requirements

Defining SLAs and SLOs

MetricDefinitionTypical Target
Response Time (P50)Median response time< 200ms
Response Time (P95)95th percentile< 500ms
Response Time (P99)99th percentile< 1000ms
ThroughputRequests per secondVaries by endpoint
Error RateFailed requests< 0.1%
AvailabilityUptime percentage99.9%

For Regulated Systems

SLA Example for Healthcare System:

- Patient lookup API: P95 < 500ms
- Critical alerts: P99 < 100ms
- Report generation: < 30s for standard reports
- Concurrent users: Support 500 simultaneous users
- Availability: 99.99% during business hours

Load Testing with k6

Basic Load Test

// 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: 0 },   // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95% of requests under 500ms
    http_req_failed: ['rate<0.01'],    // Error rate under 1%
  },
};

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

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

  sleep(1);
}

Testing API Endpoints

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

const BASE_URL = 'https://api.example.com';

export default function () {
  // Login
  group('Authentication', function () {
    const loginRes = http.post(`${BASE_URL}/auth/login`, {
      email: 'user@example.com',
      password: 'password123',
    });

    check(loginRes, {
      'login successful': (r) => r.status === 200,
      'has token': (r) => r.json('token') !== undefined,
    });

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

    // Use token for subsequent requests
    const headers = { Authorization: `Bearer ${token}` };

    // Get user profile
    group('User Profile', function () {
      const profileRes = http.get(`${BASE_URL}/profile`, { headers });

      check(profileRes, {
        'profile loaded': (r) => r.status === 200,
        'has user data': (r) => r.json('id') !== undefined,
      });
    });

    // List resources
    group('List Resources', function () {
      const resourcesRes = http.get(`${BASE_URL}/resources`, { headers });

      check(resourcesRes, {
        'resources loaded': (r) => r.status === 200,
        'is array': (r) => Array.isArray(r.json()),
      });
    });
  });
}

Stress Testing

Finding Breaking Points

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

export const options = {
  stages: [
    { duration: '2m', target: 100 },   // Below normal load
    { duration: '5m', target: 100 },
    { duration: '2m', target: 200 },   // Normal load
    { duration: '5m', target: 200 },
    { duration: '2m', target: 300 },   // Around breaking point
    { duration: '5m', target: 300 },
    { duration: '2m', target: 400 },   // Beyond breaking point
    { duration: '5m', target: 400 },
    { duration: '10m', target: 0 },    // Recovery
  ],
};

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

  check(res, {
    'is healthy': (r) => r.status === 200,
  });
}

Spike Testing

// spike-test.js
export const options = {
  stages: [
    { duration: '1m', target: 50 },    // Normal load
    { duration: '10s', target: 500 },  // Spike!
    { duration: '3m', target: 500 },   // Stay at spike
    { duration: '10s', target: 50 },   // Recovery
    { duration: '3m', target: 50 },
    { duration: '10s', target: 500 },  // Another spike
    { duration: '3m', target: 500 },
    { duration: '1m', target: 0 },
  ],
};

Database Performance Testing

Query Performance

import sql from 'k6/x/sql';

const db = sql.open('postgres', 'postgres://user:pass@localhost:5432/test');

export function setup() {
  // Seed test data if needed
}

export default function () {
  // Test simple query
  const results = sql.query(db, 'SELECT * FROM users WHERE id = $1', [1]);

  check(results, {
    'query returns data': (r) => r.length > 0,
  });
}

export function teardown() {
  db.close();
}

Connection Pool Testing

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

export const options = {
  scenarios: {
    // Simulate connection exhaustion
    high_concurrency: {
      executor: 'constant-vus',
      vus: 100,  // More than pool size
      duration: '5m',
    },
  },
};

export default function () {
  // Each request acquires a DB connection
  const res = http.get('https://api.example.com/users');

  check(res, {
    'no connection errors': (r) => r.status !== 503,
  });
}

API Performance Testing

Benchmarking Endpoints

// benchmark.js
import http from 'k6/http';
import { Trend } from 'k6/metrics';

const getUserTrend = new Trend('get_user_duration');
const listUsersTrend = new Trend('list_users_duration');
const createUserTrend = new Trend('create_user_duration');

export const options = {
  vus: 10,
  duration: '5m',
};

export default function () {
  // GET /users/:id
  let res = http.get('https://api.example.com/users/1');
  getUserTrend.add(res.timings.duration);

  // GET /users
  res = http.get('https://api.example.com/users');
  listUsersTrend.add(res.timings.duration);

  // POST /users
  res = http.post('https://api.example.com/users', JSON.stringify({
    name: 'Test User',
    email: `test${Date.now()}@example.com`,
  }), {
    headers: { 'Content-Type': 'application/json' },
  });
  createUserTrend.add(res.timings.duration);
}

Frontend Performance Testing

Lighthouse CI

// lighthouserc.js
module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000/', 'http://localhost:3000/dashboard'],
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],
        'categories:accessibility': ['error', { minScore: 0.9 }],
        'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
        'total-blocking-time': ['error', { maxNumericValue: 300 }],
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

Web Vitals Testing

import { test, expect } from '@playwright/test';

test('should meet Web Vitals thresholds', async ({ page }) => {
  await page.goto('/');

  const metrics = await page.evaluate(() => {
    return new Promise((resolve) => {
      const results = {};

      new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          results[entry.name] = entry.value;
        }
      }).observe({ type: 'largest-contentful-paint', buffered: true });

      new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          results.cls = entry.value;
        }
      }).observe({ type: 'layout-shift', buffered: true });

      setTimeout(() => resolve(results), 5000);
    });
  });

  expect(metrics.LCP).toBeLessThan(2500);
  expect(metrics.cls).toBeLessThan(0.1);
});

Performance Testing in CI/CD

GitHub Actions Integration

name: Performance Tests
on:
  push:
    branches: [main]
  schedule:
    - cron: '0 2 * * *'  # Nightly

jobs:
  load-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Start application
        run: docker-compose up -d

      - name: Wait for application
        run: |
          timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done'

      - name: Run k6 load test
        uses: grafana/k6-action@v0.3.0
        with:
          filename: tests/performance/load-test.js

      - name: Run Lighthouse
        uses: treosh/lighthouse-ci-action@v10
        with:
          configPath: ./lighthouserc.js

      - name: Upload results
        uses: actions/upload-artifact@v4
        with:
          name: performance-results
          path: |
            summary.json
            lighthouse-report.html

Performance Baseline and Regression

Establishing Baselines

// baseline-test.js
import http from 'k6/http';
import { check } from 'k6';
import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.1/index.js';

export const options = {
  vus: 50,
  duration: '10m',
  thresholds: {
    'http_req_duration{endpoint:get_users}': ['p(95)<200'],
    'http_req_duration{endpoint:get_user}': ['p(95)<100'],
    'http_req_duration{endpoint:create_user}': ['p(95)<500'],
  },
};

export default function () {
  // Tag requests for separate metrics
  http.get('https://api.example.com/users', {
    tags: { endpoint: 'get_users' },
  });

  http.get('https://api.example.com/users/1', {
    tags: { endpoint: 'get_user' },
  });
}

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

Comparing Against Baseline

// Compare current run against baseline
import { previousRun } from './baseline.json';

export const options = {
  thresholds: {
    // Fail if more than 10% slower than baseline
    http_req_duration: [
      `p(95)<${previousRun.p95 * 1.1}`,
    ],
  },
};

Performance Testing Checklist

Before Testing

  • Define performance requirements (SLAs/SLOs)
  • Identify critical endpoints and workflows
  • Set up monitoring and metrics collection
  • Prepare test data and environment

During Testing

  • Monitor system resources (CPU, memory, network)
  • Watch for error rates and timeouts
  • Capture detailed metrics and logs
  • Test at multiple load levels

After Testing

  • Analyze results against requirements
  • Identify bottlenecks and optimization opportunities
  • Document findings and recommendations
  • Update baselines if acceptable


Compliance

This section fulfills ISO 13485 requirements for process validation (7.5.6) and monitoring and measurement (8.2.4), and ISO 27001 requirements for capacity management (A.8.6) and secure development lifecycle (A.8.25).

View full compliance matrix

How is this guide?

Last updated on

On this page