🚧 Prototype Notice

This project (sufast) is currently a dummy prototype.
Only static routes are working at the moment.
Dynamic routing and full features are under development.
Thank you for understanding! πŸ™

Sufast/Documentation
Load Testing

Load Testing Guide

Learn how to properly load test your Sufast applications to ensure they can handle production traffic.

Setting Up Load Tests
Tools and setup for effective load testing

Installing k6

k6 Installationbash
# Install k6 on macOS
brew install k6

# Install k6 on Ubuntu/Debian
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb 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

# Install k6 on Windows (using Chocolatey)
choco install k6

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

Basic Load Test Script

Basic Load Testjavascript
// basic-load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

// Test configuration
export let options = {
  stages: [
    { duration: '2m', target: 100 },   // Ramp up to 100 users over 2 minutes
    { duration: '5m', target: 100 },   // Stay at 100 users for 5 minutes
    { duration: '2m', target: 200 },   // Ramp up to 200 users over 2 minutes
    { duration: '5m', target: 200 },   // Stay at 200 users for 5 minutes
    { duration: '2m', target: 0 },     // Ramp down to 0 users over 2 minutes
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],   // 95% of requests must complete below 500ms
    http_req_failed: ['rate<0.1'],      // Error rate must be below 10%
  },
};

export default function () {
  // Test your Sufast API
  let response = http.get('http://localhost:8080/api/users');
  
  check(response, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
    'response has users': (r) => r.json().users !== undefined,
  });
  
  // Simulate user think time
  sleep(1);
}

Running the Test

Running Testsbash
# Run the load test
k6 run basic-load-test.js

# Run with custom options
k6 run --vus 50 --duration 30s basic-load-test.js

# Generate detailed HTML report
k6 run --out html=report.html basic-load-test.js

# Run with environment variables
k6 run -e BASE_URL=http://localhost:8080 basic-load-test.js
Advanced Load Testing Scenarios
Complex scenarios for comprehensive testing

API Workflow Testing

API Workflow Testjavascript
// api-workflow-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

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

export default function () {
  const baseUrl = 'http://localhost:8080';
  
  // 1. Login to get authentication token
  let loginResponse = http.post(`${baseUrl}/auth/login`, JSON.stringify({
    username: 'testuser',
    password: 'testpass'
  }), {
    headers: { 'Content-Type': 'application/json' },
  });
  
  check(loginResponse, {
    'login successful': (r) => r.status === 200,
    'token received': (r) => r.json().token !== undefined,
  });
  
  if (loginResponse.status !== 200) {
    return; // Skip rest of test if login fails
  }
  
  const token = loginResponse.json().token;
  const authHeaders = {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json',
  };
  
  // 2. Create a new resource
  let createResponse = http.post(`${baseUrl}/api/items`, JSON.stringify({
    name: `Test Item ${Math.random()}`,
    description: 'Load test item'
  }), { headers: authHeaders });
  
  check(createResponse, {
    'item created': (r) => r.status === 201,
    'item has id': (r) => r.json().id !== undefined,
  });
  
  if (createResponse.status === 201) {
    const itemId = createResponse.json().id;
    
    // 3. Retrieve the created resource
    let getResponse = http.get(`${baseUrl}/api/items/${itemId}`, {
      headers: authHeaders
    });
    
    check(getResponse, {
      'item retrieved': (r) => r.status === 200,
      'correct item': (r) => r.json().id === itemId,
    });
    
    // 4. Update the resource
    let updateResponse = http.put(`${baseUrl}/api/items/${itemId}`, JSON.stringify({
      name: 'Updated Test Item',
      description: 'Updated description'
    }), { headers: authHeaders });
    
    check(updateResponse, {
      'item updated': (r) => r.status === 200,
    });
    
    // 5. Delete the resource
    let deleteResponse = http.del(`${baseUrl}/api/items/${itemId}`, {
      headers: authHeaders
    });
    
    check(deleteResponse, {
      'item deleted': (r) => r.status === 204,
    });
  }
  
  sleep(1);
}

Stress Testing

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

export let options = {
  stages: [
    { duration: '5m', target: 100 },   // Normal load
    { duration: '5m', target: 200 },   // Above normal
    { duration: '5m', target: 300 },   // High load
    { duration: '5m', target: 400 },   // Stress level
    { duration: '5m', target: 500 },   // Breaking point
    { duration: '10m', target: 0 },    // Recovery
  ],
  thresholds: {
    http_req_duration: ['p(95)<2000'],  // More lenient for stress test
    http_req_failed: ['rate<0.2'],      // Allow higher error rate
  },
};

export default function () {
  let response = http.get('http://localhost:8080/api/heavy-computation');
  
  check(response, {
    'status is not 5xx': (r) => r.status < 500,
    'response time acceptable': (r) => r.timings.duration < 5000,
  });
}

Spike Testing

Spike Testjavascript
// spike-test.js
import http from 'k6/http';
import { check } from 'k6';

export let options = {
  stages: [
    { duration: '2m', target: 100 },   // Normal load
    { duration: '1m', target: 1000 },  // Sudden spike
    { duration: '2m', target: 100 },   // Back to normal
    { duration: '1m', target: 1000 },  // Another spike
    { duration: '2m', target: 100 },   // Back to normal
    { duration: '2m', target: 0 },     // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<1000'],
    http_req_failed: ['rate<0.15'],
  },
};

export default function () {
  let response = http.get('http://localhost:8080/api/users');
  
  check(response, {
    'status is 200': (r) => r.status === 200,
    'response time under 1s': (r) => r.timings.duration < 1000,
  });
}
Interpreting Results
Understanding load test metrics and identifying bottlenecks

Key Metrics to Monitor

MetricDescriptionGood Target
http_req_durationResponse time percentilesp95 < 500ms
http_req_failedError rate percentage< 1%
http_reqsTotal requests per secondDepends on requirements
vusVirtual users (concurrent)Based on expected load
data_receivedBandwidth usageMonitor for capacity

Sample Test Output

k6 Test Resultstext
     βœ“ status is 200
     βœ“ response time < 500ms
     βœ“ response has users

     checks.........................: 100.00% βœ“ 15000      βœ— 0     
     data_received..................: 45 MB   1.5 MB/s
     data_sent......................: 1.2 MB  40 kB/s
     http_req_blocked...............: avg=1.2ms    min=0s       med=1ms      max=15ms     p(90)=2ms      p(95)=3ms     
     http_req_connecting............: avg=0.8ms    min=0s       med=0s       max=12ms     p(90)=1ms      p(95)=2ms     
     http_req_duration..............: avg=45ms     min=12ms     med=42ms     max=180ms    p(90)=65ms     p(95)=85ms    
       { expected_response:true }...: avg=45ms     min=12ms     med=42ms     max=180ms    p(90)=65ms     p(95)=85ms    
     http_req_failed................: 0.00%   βœ“ 0          βœ— 5000  
     http_req_receiving.............: avg=0.5ms    min=0.1ms    med=0.4ms    max=5ms      p(90)=0.8ms    p(95)=1.2ms   
     http_req_sending...............: avg=0.2ms    min=0.05ms   med=0.15ms   max=2ms      p(90)=0.3ms    p(95)=0.5ms   
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=44.3ms   min=11.8ms   med=41.5ms   max=178ms    p(90)=64ms     p(95)=84ms    
     http_reqs......................: 5000    166.666667/s
     iteration_duration.............: avg=1.04s    min=1.01s    med=1.04s    max=1.18s    p(90)=1.06s    p(95)=1.08s   
     iterations.....................: 5000    166.666667/s
     vus............................: 100     min=100      max=100 
     vus_max........................: 100     min=100      max=100

Monitoring Your Sufast App

Performance Monitoringpython
from sufast import App, Request
import time
import psutil
import asyncio

app = App()

# Add performance monitoring middleware
@app.middleware("http")
async def performance_monitor(request: Request, call_next):
    start_time = time.time()
    start_memory = psutil.Process().memory_info().rss / 1024 / 1024  # MB
    
    response = await call_next(request)
    
    end_time = time.time()
    end_memory = psutil.Process().memory_info().rss / 1024 / 1024  # MB
    
    # Log performance metrics
    duration = (end_time - start_time) * 1000  # ms
    memory_diff = end_memory - start_memory
    
    print(f"Request: {request.method} {request.url.path}")
    print(f"Duration: {duration:.2f}ms")
    print(f"Memory change: {memory_diff:.2f}MB")
    print(f"Status: {response.status_code}")
    print("---")
    
    # Add metrics to response headers
    response.headers["X-Response-Time"] = f"{duration:.2f}ms"
    response.headers["X-Memory-Usage"] = f"{end_memory:.2f}MB"
    
    return response

@app.get("/metrics")
def get_metrics():
    """Endpoint for monitoring tools to scrape metrics"""
    process = psutil.Process()
    
    return {
        "cpu_percent": psutil.cpu_percent(),
        "memory_mb": process.memory_info().rss / 1024 / 1024,
        "memory_percent": process.memory_percent(),
        "open_files": len(process.open_files()),
        "connections": len(process.connections()),
        "threads": process.num_threads(),
    }