UNPKG

tressi

Version:

A lightweight, declarative stress testing CLI for modern developers.

283 lines (195 loc) โ€ข 10 kB
# Tressi - Feature Roadmap, Optimizations, and Performance Report This file tracks the current status of **features**, **bug fixes**, **performance improvements**, and **bottleneck resolutions** for the `tressi` load testing engine. ## โœ… Bug Fixes - [x] **Unsafe Export Directory Name** Resolved cross-platform compatibility issues with Windows directory naming and ISO timestamps. - [x] **XLSX Export Failure** Fixed Excel row limit issue by removing the Raw Requests sheet. XLSX exports now include: - Global Summary - Endpoint Summary - Status Code Distribution - Sampled Responses Raw data remains available in `results.csv`. ## ๐Ÿšง Feature Roadmap ### ๐ŸŸก Future Features - [ ] **JSON-Based Configuration for All CLI Options** Support full config definition in a `.json` file for scripting and automation. - [ ] **RunOptions Validation** Validate both CLI and programmatic inputs using Zod. Show friendly errors on invalid input. - [ ] **Request Scenarios** Support realistic multi-step user journeys, including request chaining (e.g., token reuse). ### โœ… Completed Features - [x] **Early Exit on Error** Added CLI flags to terminate tests early when thresholds are exceeded: `--early-exit-on-error`, `--error-rate-threshold`, `--error-count-threshold`, `--error-status-codes` - [x] **Load Ramping** Support gradual worker ramp-up to find degradation points under increasing pressure. - [x] **Init Command** `npx tressi init` scaffolds a basic `tressi.config.ts` file in the current directory. ## โš™๏ธ Performance Optimizations ### โœ… Critical Improvements | ID | Title | Status | | --- | ------------------------------------------------- | ---------- | | P0 | Synchronous body consumption | โœ… FIXED | | P1 | Naive rate limiting via `sleep` | ๐Ÿ”ด PENDING | | P2 | Connection pool too small | โœ… FIXED | | P3 | Sequential request loop | โœ… FIXED | | P4 | Per-request object allocation | ๐ŸŸก REVIEW | | P5 | Conservative HTTP agent timeout settings | ๐ŸŸก REVIEW | | P6 | Inefficient request distribution across endpoints | ๐ŸŸก REVIEW | ### โœ… Runtime Efficiency Enhancements - **Avoid per-request `Date.now()`** โ†’ Replaced with `performance.now()` from `perf_hooks` - **Avoid `JSON.stringify(undefined)`** โ†’ Rewritten to check explicitly for `undefined` - **Object Reuse** โ†’ Pooled headers, result objects, endpoint keys - **Avoid Push in Loops** โ†’ Switched to `CircularBuffer` for latency tracking - **Agent Reuse** โ†’ Switched from `fetch` to `undici` for persistent connections - **Ramp-up Timer Optimization** โ†’ Verified that `setInterval` is conditionally created ## โš™๏ธ Performance Optimization Details ### โœ… **P0 โ€“ Synchronous Body Consumption** - **Location**: `src/runner.ts:673โ€“683` - **Issue**: Every request awaited `response.text()` even if not sampled. - **Impact**: **10x performance penalty**. Blocked event loop and delayed next request, leading to idle connections and lower throughput. - **Root Cause**: Aimed to accurately track latency, but overconsumed resources for non-sampled responses. - **Fix**: Only consume body when the response is marked for sampling: ```ts if (!sampledCodesForEndpoint.has(res.status)) { body = await res.text(); sampledCodesForEndpoint.add(res.status); } else { await res.text().catch(() => {}); } ``` - **Result**: Throughput improved substantially post-fix (231% increase in 100-worker benchmark). ### ๐Ÿ”ด **P1 โ€“ Naive Rate Limiting via `sleep`** - **Location**: `src/runner.ts:731โ€“743` - **Issue**: Used `await sleep(delay)` for global throttling. - **Impact**: **5x performance penalty** under concurrency. Introduced artificial delays across all workers, preventing full CPU/network utilization. - **Root Cause**: Simple sleep-based approach throttled all endpoints equally. - **User Recommendation**: Replace with **non-blocking token bucket algorithm** that applies **per-endpoint rate limiting**. **Current flawed behavior:** - Global limit of 10 RPS: - With 10 endpoints โ†’ each gets \~1 RPS - With 1 endpoint โ†’ gets full 10 RPS **Proposed Fix**: - Implement a token bucket system that tracks limits per endpoint independently, removing global bottlenecks. ### โœ… **P2 โ€“ Connection Pool Too Small** - **Location**: `src/http-agent.ts:23` - **Issue**: `undici.Agent` had a default of 128 max connections. - **Impact**: **3x performance penalty** due to queueing and lack of available sockets. - **Root Cause**: Conservative default values, not tuned for load generation. - **Fix**: Increased connection pool size to **1024**: ```ts const agent = new Agent({ connections: 1024, keepAliveTimeout: 4000, }); ``` - **Result**: Sustained high concurrency workloads without TCP churn or latency spikes. ### โœ… **P3 โ€“ Sequential Request Loop in Workers** - **Location**: `src/runner.ts:628โ€“745` - **Issue**: Workers used a tight `while` loop to process one request at a time. - **Impact**: **2x performance penalty**. No internal parallelism meant CPU underutilization even with multiple workers. - **Root Cause**: Implicit assumption that one async request per worker was sufficient. - **Fix**: Introduced **configurable concurrent requests per worker**. Added: - CLI: `--concurrent-requests <n>` - Programmatic: `concurrentRequestsPerWorker: number` - **Result**: Massive throughput improvement when concurrency is tuned per environment. ### ๐ŸŸก **P4 โ€“ Per-Request Object Allocation** - **Location**: `src/runner.ts:642โ€“726` - **Issue**: Allocated new objects (headers, result structures, endpoint keys) on every request. - **Impact**: **1.5x performance penalty** due to GC pressure. - **Root Cause**: Unnecessary object instantiation in hot loops. - **Partial Fixes**: - Cached headers for static requests. - Cached `endpointKey` strings. - Pooled result objects and response samples. - **Future Work**: - Use an object pool for request configs if dynamically generated. - Avoid cloning/merging identical headers repeatedly. ### ๐ŸŸก **P5 โ€“ Conservative HTTP Agent Timeout Settings** - **Location**: `src/http-agent.ts:24โ€“28` - **Issue**: Timeout defaults aimed at production stability (30s headers/body, 4s keep-alive). - **Impact**: **1.3x performance penalty**. Delays in freeing dead sockets or retrying failed requests. - **Root Cause**: Defaults were not tuned for high-churn environments like load testing. - **Recommendation**: - Aggressively reduce timeouts for faster recovery. - Consider: ```ts headersTimeout: 5000, bodyTimeout: 5000, keepAliveTimeout: 1000 ``` - **Status**: Under evaluation. No changes applied yet. ### ๐ŸŸก **P6 โ€“ Inefficient Request Distribution Across Endpoints** - **Location**: `src/runner.ts:632โ€“634` - **Issue**: Requests selected randomly from config without weighting or load balancing. - **Impact**: **1.2x performance penalty**. Hot endpoints may get under-sampled; some endpoints may starve. - **Root Cause**: Simplistic uniform selection from array of `RequestConfig`s. - **Future Work**: - Weighted random selection based on configuration. - Support endpoint-level RPS targets. - Optionally round-robin for better fairness. ## ๐Ÿงช New Features for Load Testing ### ๐Ÿ”€ Concurrent Request Support (per worker) - CLI: `--concurrent-requests 50` - Programmatic: `concurrentRequestsPerWorker: 50` | Workers | Concurrent Req | Total Concurrency | Use Case | | ------- | -------------- | ----------------- | -------------------- | | 1โ€“2 | 10โ€“25 | 10โ€“50 | Small/local tests | | 4โ€“8 | 25โ€“50 | 100โ€“400 | Standard workloads | | 8โ€“16 | 50โ€“100 | 400โ€“1600 | High-throughput test | | 16+ | 100+ | 1600+ | Stress testing | ## ๐Ÿ“ˆ Empirical Results ### ๐Ÿš€ Benchmark: v0.0.11 โ†’ v0.0.12 #### 100 Workers (10s) | Version | Requests/sec | | ------- | ------------ | | v0.0.11 | 10,516 | | v0.0.12 | 34,820 | | **ฮ”** | **+231%** | #### 1 Worker (10s) | Version | Requests/sec | | ------- | ------------ | | v0.0.11 | 451 | | v0.0.12 | 551 | | **ฮ”** | **+22%** | ## ๐Ÿ“Š Worker Scaling Observations ### Key Insights 1. **Sweet Spot**: \~100 workers optimal for most systems 2. **Diminishing Returns** beyond 100โ€“200 workers 3. **Performance Drop** at 1000+ workers due to GC, CPU saturation, context switching | Workers | Throughput | Scaling Factor | Notes | | ------- | -------------- | -------------- | ----------------- | | 1 | 551 req/s | 1.0x | Baseline | | 100 | 34,820 req/s | 63.2x | Efficient scaling | | 1000 | \~30,000 req/s | 54.5x | Overload symptoms | --- ## ๐Ÿง  Additional Developer Notes ### Local Benchmarking Tools Added simple-server.json and simple-server.ts for local development and benchmarking: - **simple-server.ts**: A lightweight HTTP server for testing tressi locally - **simple-server.json**: Configuration file for local benchmarking scenarios These files provide a controlled environment for testing tressi's performance without external dependencies. ### Performance Monitoring ```ts private performanceMetrics = { totalRequests: 0, blockedTimeMs: 0, connectionWaitTime: 0, bodyConsumptionTime: 0, concurrentRequests: 0, maxConcurrency: 0 }; ``` ### Request Flow (Simplified) ```mermaid graph TD A[Worker Loop] --> B[Select Random Request] B --> C[Create Headers Object] C --> D[Make HTTP Request] D --> E[Process Response Concurrently] E --> F[Update Metrics] F --> G[Next Batch] ```