Zentinel includes a comprehensive testing framework validating performance, stability, and resilience. All results below are from actual test runs.
Test Coverage Overview
| Category | Tests | Status |
|---|---|---|
| Unit Tests | 322 tests across all crates | Passing |
| Integration Tests | Full proxy stack with agents | Passing |
| Chaos Tests | 10 failure scenarios | Passing |
| Soak Tests | 24-72 hour stability tests | Passing |
Proxy Comparison Benchmarks
Five reverse proxies benchmarked head-to-head on the same hardware, same backend, same load. Native binaries only — no Docker overhead.
Test Environment
Methodology: Each proxy is started as a native binary, warmed up, then benchmarked for 60 seconds with oha. Resource usage (CPU, memory) is sampled every second via ps. Proxies are tested sequentially with 10-second cooldowns between runs to avoid contention.
Throughput
Latency Distribution
Resource Efficiency
Summary
| Proxy | RPS | p50 | p99 | Peak Mem | Avg CPU | Success |
|---|---|---|---|---|---|---|
| Loading... | ||||||
Component Latency
| Operation | Latency |
|---|---|
| Core rate limiting | < 100μs |
| Geo filtering (MaxMind/IP2Location) | < 100μs |
| Token bucket algorithm | ~50μs |
| Agent IPC (Unix socket round-trip) | 100-500μs |
| Distributed rate limit (Redis) | 1-5ms |
Core operations like rate limiting and geo filtering are sub-100μs, meaning they add negligible overhead to request processing. The agent IPC overhead (100-500μs) is the primary cost of extensibility, which is why latency-critical features like rate limiting were moved into the core.
WAF Engine Benchmarks (zentinel-modsec)
Pure Rust ModSecurity implementation with full OWASP CRS compatibility. Benchmarks run on macOS ARM64 using Criterion.
Throughput
| Traffic Type | Time/Request | Throughput |
|---|---|---|
| Clean traffic | 1.10 µs | 912K req/s |
| Attack traffic | 1.15 µs | 869K req/s |
| Mixed (80/20) | 1.07 µs | 935K req/s |
Detection Operators
| Operator | Match | No Match | Description |
|---|---|---|---|
@pm | 25 ns | 18 ns | Multi-pattern (Aho-Corasick) |
@contains | 59 ns | 48 ns | String contains |
@detectXSS | 57 ns | 171 ns | XSS detection |
@rx | 80 ns | 28 ns | Regex matching |
@detectSQLi | 123 ns | 291 ns | SQL injection detection |
Rule Parsing
Optimized with lazy regex compilation for fast rule loading.
| Rule Type | Parse Time |
|---|---|
| Simple rule | 1.21 µs |
| SQLi detection rule | 1.73 µs |
| XSS detection rule | 1.46 µs |
| Chain rule | 2.45 µs |
| Complex rule (transforms) | 2.75 µs |
Rule parsing is now 3.6x faster than libmodsecurity for complex rules.
Transformations
| Transformation | Latency |
|---|---|
t:lowercase | 17 ns |
t:base64Decode | 39 ns |
t:urlDecode | 61 ns |
t:cmdLine | 84 ns |
t:normalizePath | 173 ns |
t:htmlEntityDecode | 225 ns |
Body Processing Throughput
| Body Size | Processing Time | Throughput |
|---|---|---|
| 0 bytes | 1.04 µs | - |
| 100 bytes | 3.67 µs | 27 MB/s |
| 1 KB | 27.2 µs | 37 MB/s |
| 10 KB | 263 µs | 38 MB/s |
| 100 KB | 2.43 ms | 41 MB/s |
Key Findings:
- Sub-microsecond transaction processing enables >900K requests/second
- XSS detection is faster than SQLi (57ns vs 123ns) due to optimized Aho-Corasick + RegexSet
- Pattern matching (@pm) uses Aho-Corasick for O(n) multi-pattern search
- Body processing scales linearly at ~40 MB/s throughput
- Pure Rust - zero C/C++ dependencies, full OWASP CRS compatibility
Rust vs C++: zentinel-modsec vs libmodsecurity
| Benchmark | zentinel-modsec | libmodsecurity | Speedup |
|---|---|---|---|
| Clean request | 161 ns | 4,831 ns | 30x faster |
| SQLi attack request | 295 ns | 5,545 ns | 18.8x faster |
| POST body with SQLi | 1.24 µs | 12.93 µs | 10.4x faster |
| Clean traffic throughput | 203 ns | 4,937 ns | 24.3x faster |
| Attack traffic throughput | 316 ns | 5,678 ns | 18x faster |
| Metric | zentinel-modsec | libmodsecurity |
|---|---|---|
| Clean request throughput | 6.2M req/s | 207K req/s |
| Attack detection throughput | 3.2M req/s | 176K req/s |
Rule Parsing Comparison
| Rule Type | zentinel-modsec | libmodsecurity | Speedup |
|---|---|---|---|
| Simple rule | 1.21 µs | 3.28 µs | 2.7x faster |
| Complex rule | 2.75 µs | 10.07 µs | 3.6x faster |
- Zero-copy parsing -
Cow<str>avoids allocations when data is unchanged - PHF (Perfect Hash Functions) - O(1) operator/variable name lookup vs linear search
- Lazy regex compilation - defer compilation to first use, not parse time
- Aho-Corasick - O(n) multi-pattern matching for
@pmoperator - RegexSet - single-pass evaluation of multiple regex patterns
- No FFI overhead - no memory allocation crossing language boundaries
- No std::string copies - Rust’s ownership model eliminates defensive copying
Soak Test Results
Extended duration tests validate stability and detect memory leaks or resource exhaustion.
1-Hour Soak Test (2026-01-01)
| Metric | Value |
|---|---|
| Duration | 1 hour |
| Total Requests | 1,000,000 |
| Throughput | 775 RPS |
| Average Latency | 13.9ms |
| p50 Latency | 1.1ms |
| p95 Latency | 32.3ms |
| p99 Latency | 61.5ms |
| Success Rate | 99.95% |
Memory Analysis
| Metric | Value |
|---|---|
| Initial Memory | 12 MB |
| Final Memory | 1 MB |
| Memory Growth | -91% |
| Verdict | No memory leak detected |
Key Findings:
- Memory decreased over time, demonstrating efficient Rust memory management
- Throughput remained stable throughout the test
- 99% of requests completed in under 62ms
- Connection errors (0.05%) occurred only during startup/shutdown phases
Leak Detection Methodology
Our analysis uses linear regression to detect memory growth patterns:
| Threshold | Verdict | Action |
|---|---|---|
| Growth < 10% | NO LEAK | Pass |
| Growth 10-20% | WARNING | Investigate |
| Growth > 20% | LEAK DETECTED | Fail CI |
Analysis Method:
- Linear regression on memory samples over time
- R-squared correlation to detect consistent growth
- Monotonic increase ratio (>80% samples increasing = suspicious)
Test Configuration
From tests/soak/soak-config.kdl:
system {
worker-threads 2
max-connections 10000
graceful-shutdown-timeout-secs 30
}
| Parameter | Value |
|---|---|
| Duration | 24 hours (standard), 72 hours (extended) |
| Sustained RPS | 100 requests/second |
| Concurrent Connections | 10 |
| Sampling Interval | 60 seconds |
| Backend | Simple HTTP echo service |
Chaos Testing
Fault injection validates resilience under failure conditions. All scenarios are in tests/chaos/.
Test Scenarios (10 Total)
Agent Failure Tests
| Scenario | What We Test | Expected Behavior |
|---|---|---|
| Agent Crash | Kill agent process | Fail-open: traffic continues; Fail-closed: 503 |
| Agent Timeout | Freeze agent (1s timeout) | Request fails after timeout, no hang |
| Circuit Breaker | 5+ consecutive failures | CB opens, fast-fails requests, recovers |
Upstream Failure Tests
| Scenario | What We Test | Expected Behavior |
|---|---|---|
| Backend Crash | Kill primary backend | Health check detects in ~15s, returns 502/503 |
| Backend Failover | Primary down, secondary up | Traffic routes to secondary |
| All Backends Down | Kill all backends | Graceful 503, no crash |
| Backend 5xx | Backend returns errors | Retry policy triggers, metrics recorded |
Resilience Tests
| Scenario | What We Test | Expected Behavior |
|---|---|---|
| Fail-Open Mode | Agent failure on /failopen/* | Traffic continues to backend |
| Fail-Closed Mode | Agent failure on /protected/* | Traffic blocked with 503 |
| Health Recovery | Backend restart after failure | Auto-recovery in ~15s |
| Memory Stability | 20 chaos cycles | No memory leaks |
Circuit Breaker Behavior
The circuit breaker protects the system from cascading failures when agents become unhealthy. It operates as a state machine with three states:
┌─────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────┐ 5 failures ┌──────────┐ 10s timeout │
│ │ CLOSED │ ─────────────> │ OPEN │ ─────────────┐ │
│ │ (normal) │ │(fast-fail│ │ │
│ └────▲─────┘ └──────────┘ │ │
│ │ ▼ │
│ │ 2 successes ┌───────────┐ │
│ └───────────────────── │ HALF-OPEN │ ◄───────────┘ │
│ │ (probe) │ │
│ └───────────┘ │
│ │ │
│ │ failure │
│ └──────────> back to OPEN │
│ │
└─────────────────────────────────────────────────────────────────┘
State Descriptions:
- CLOSED (green): Normal operation. All requests pass through to the agent. Failures are counted.
- OPEN (red): Circuit is tripped. Requests are immediately failed without contacting the agent. This prevents hammering a broken service.
- HALF-OPEN (yellow): After a timeout, the circuit allows a limited number of probe requests. If they succeed, the circuit closes. If they fail, it reopens.
Configuration from chaos-config.kdl:
circuit-breaker {
failure-threshold 5 // Opens after 5 consecutive failures
success-threshold 2 // Closes after 2 successes in half-open
timeout-seconds 10 // Time before transitioning to half-open
half-open-max-requests 2 // Probe requests allowed in half-open
}
Recovery Timeline
Recovery Times
| Failure Type | Detection Time | Recovery Time |
|---|---|---|
| Agent crash (fail-open) | Immediate | N/A (bypassed) |
| Agent crash (fail-closed) | Immediate | On agent restart |
| Backend crash | ~15s (3 health checks) | ~10s after restart |
| Circuit breaker trip | Immediate | 10s + 2 successes |
Security Validation
WAF Testing (OWASP CRS)
Validated against OWASP attack patterns with the WAF agent running OWASP Core Rule Set:
| Attack Type | Payload Example | Result |
|---|---|---|
| SQL Injection | ' OR 1=1-- | Blocked |
| XSS | <script>alert(1)</script> | Blocked |
| Path Traversal | ../../etc/passwd | Blocked |
| Command Injection | ; cat /etc/passwd | Blocked |
| Scanner Detection | SQLMap User-Agent | Blocked |
All OWASP Top 10 attack patterns are blocked with the CRS rule set.
Integration Test Environment
Full integration tests run in Docker Compose with:
| Service | Port | Purpose |
|---|---|---|
| Zentinel Proxy | 8080 | Main reverse proxy |
| Metrics Endpoint | 9090 | Prometheus metrics |
| Backend (httpbin) | 8081 | Test backend |
| Echo Agent | UDS | Header manipulation |
| Rate Limit Agent | 9092 | Token bucket limiting |
| WAF Agent | 9094 | OWASP CRS protection |
| Prometheus | 9091 | Metrics collection |
| Grafana | 3000 | Dashboards |
| Jaeger | 16686 | Distributed tracing |
Running the Tests
Unit Tests
cargo test --workspace
Integration Tests
cd tests
./integration_test.sh # Full suite
./integration_test.sh --quick # Smoke tests only
Chaos Tests
cd tests/chaos
make quick # 4 core scenarios (~5 min)
make test # All 10 scenarios (~20 min)
make test-circuit-breaker # Single scenario
Soak Tests
cd tests/soak
./run-soak-test.sh --duration 1 # 1 hour quick test
./run-soak-test.sh --duration 24 # Standard 24h test
./run-soak-test.sh --duration 72 # Extended 72h test
# Analyze results
python3 analyze-results.py results/<timestamp>/
Prometheus Metrics
| Metric | Description |
|---|---|
zentinel_requests_total | Total requests by route/status |
zentinel_request_duration_seconds | Latency histogram |
zentinel_upstream_healthy_backends | Backend health status |
zentinel_upstream_retries_total | Retry attempts |
zentinel_agent_circuit_breaker_state | CB state (0=closed, 1=open, 2=half-open) |
zentinel_agent_failures_total | Agent communication failures |
zentinel_agent_timeouts_total | Agent timeout events |
zentinel_agent_bypasses_total | Fail-open bypass count |
Milestone Achievements
| Milestone | Status | Key Metrics |
|---|---|---|
| M2: Cacheable | Complete | 23K RPS load tested |
| M4: Scalable | Complete | Redis/Memcached distributed rate limiting |
| M5: Observable | Complete | OpenTelemetry, Grafana dashboards |
| M6: Optimized | Complete | Core rate limiting < 100μs, geo filtering < 100μs |
How to Reproduce
All benchmark scripts live in the main repository. To reproduce any result on your own hardware:
Proxy Comparison
git clone https://github.com/zentinelproxy/zentinel
cd zentinel
# Install proxies you want to compare (Zentinel, nginx, HAProxy, Caddy, Envoy)
# Then run the benchmark suite:
cd tests/bench
./proxy-bench.sh # All proxies, default settings
./proxy-bench.sh --proxy zentinel # Single proxy
./proxy-bench.sh --duration 120 # Custom duration (seconds)
./proxy-bench.sh --connections 200 # Custom concurrency
The script uses oha as the load generator. Each proxy is started as a native binary (no Docker), warmed up for 5 seconds, then benchmarked for 60 seconds at the configured concurrency. CPU and memory are sampled every second via ps. Proxies are tested sequentially with 10-second cooldowns between runs.
Configuration: All proxies use equivalent minimal configs — single upstream, no TLS, no caching, keepalive enabled. Configs are in tests/bench/configs/.
WAF Benchmarks
cd tests/bench
./waf-bench.sh # zentinel-modsec vs libmodsecurity
Soak & Chaos Tests
cd tests/soak
./run-soak-test.sh --duration 1 # 1-hour quick test
cd tests/chaos
make quick # 4 core scenarios
make test # All 10 scenarios
Results are written to results/<timestamp>/ as JSON. The raw data behind the charts on this page is available at benchmark-results.json.
Known Gaps
We’re actively working on:
| Gap | Status |
|---|---|
| Criterion microbenchmarks | Complete |
| High-concurrency (10k+ conn) tests | Planned |
| Property-based fuzzing | Planned |
| HTTP request smuggling tests | Planned |
| Comparison benchmarks vs nginx/HAProxy/Caddy | Complete |
Last updated: January 2026 | Zentinel v0.4.2