openclaw/load-tests/run.ts
ronitchidara 1546fb5c04 enterprise: add Phase 5 observability and enterprise features
Observability:
- HTTP health endpoints (/health, /ready, /health/deep)
- Prometheus metrics endpoint (/metrics) with prom-client
- W3C traceparent request tracing with AsyncLocalStorage
- Trace ID propagation to logs and diagnostic events

Enterprise:
- JSONL audit logging with daily rotation and 7-day retention
- RBAC permission engine with flat roles (admin, operator, user, viewer)
- Tool and agent access restrictions per role
- Audit integration with pairing, auth, and exec approval flows

Documentation:
- Enterprise deployment guide (single/multi-tenant, K8s, Docker)
- Security hardening guide (TLS, RBAC, rate limiting)
- Observability guide (Prometheus, Grafana, alerting)
- Self-healing behaviors documentation

Load Testing:
- Connection stress test (WebSocket saturation)
- Chat throughput test (sustained message load)
- Auth stress test (rate limit verification)
- Configurable scenarios with p50/p95/p99 latency metrics

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 17:01:41 +05:30

103 lines
3.5 KiB
TypeScript
Executable File

#!/usr/bin/env bun
/**
* Gateway Load Test Runner
*
* Entry point for running gateway load tests.
*
* Usage:
* bun load-tests/run.ts --scenario connections --concurrency 100 --duration 30
* bun load-tests/run.ts --scenario chat --concurrency 20 --rps 5 --duration 60
* bun load-tests/run.ts --scenario auth-stress --concurrency 10 --duration 30
*/
import { DEFAULT_CONFIG, formatMetricsConsole, parseArgs } from "./config.js";
import type { LoadTestConfig, LoadTestMetrics } from "./config.js";
import { runConnectionsTest } from "./gateway-ws.js";
import { runChatTest } from "./gateway-chat.js";
import { runAuthStressTest } from "./gateway-auth.js";
async function main(): Promise<void> {
const args = process.argv.slice(2);
const parsedArgs = parseArgs(args);
const config: LoadTestConfig = {
...DEFAULT_CONFIG,
...parsedArgs,
};
// Validate config
if (!config.gatewayUrl) {
console.error("Error: Gateway URL is required (--url or -u)");
process.exit(1);
}
if (!["connections", "chat", "auth-stress"].includes(config.scenario)) {
console.error(`Error: Invalid scenario "${config.scenario}". Use: connections, chat, auth-stress`);
process.exit(1);
}
console.log("");
console.log("╔══════════════════════════════════════════════════════════════════╗");
console.log("║ Clawdbot Gateway Load Test ║");
console.log("╚══════════════════════════════════════════════════════════════════╝");
console.log("");
console.log(` Scenario: ${config.scenario}`);
console.log(` Gateway: ${config.gatewayUrl}`);
console.log(` Concurrency: ${config.concurrency}`);
console.log(` Duration: ${config.durationSeconds}s`);
console.log(` Ramp-up: ${config.rampUpSeconds}s`);
if (config.scenario === "chat") {
console.log(` RPS/user: ${config.rpsPerUser}`);
}
console.log(` Auth: ${config.token ? "token provided" : "none"}`);
console.log("");
let metrics: LoadTestMetrics;
try {
switch (config.scenario) {
case "connections":
metrics = await runConnectionsTest(config);
break;
case "chat":
metrics = await runChatTest(config);
break;
case "auth-stress":
metrics = await runAuthStressTest(config);
break;
}
} catch (err) {
console.error(`\nLoad test failed: ${err instanceof Error ? err.message : String(err)}`);
process.exit(1);
}
// Output results
if (config.outputFormat === "json") {
console.log(JSON.stringify(metrics, null, 2));
} else {
console.log(formatMetricsConsole(metrics));
}
// Exit with error code if significant failures
const failureRate = metrics.requestsTotal > 0
? metrics.requestsFailed / metrics.requestsTotal
: 0;
if (failureRate > 0.1) {
console.log("⚠️ Warning: Failure rate > 10%");
process.exit(1);
}
if (metrics.connectionsFailed > 0 && metrics.connectionsSucceeded === 0) {
console.log("⚠️ Warning: All connections failed");
process.exit(1);
}
console.log("✓ Load test completed successfully");
}
main().catch((err) => {
console.error(`Fatal error: ${err instanceof Error ? err.message : String(err)}`);
process.exit(1);
});