A vendor-agnostic SIEM ingestion performance-testing CLI. Generates realistic security telemetry and pushes it to a SIEM collector (HEC/LPHC/syslog) at controlled rates to measure throughput, latency, and error rates.
Prerequisites: Go 1.23+.
go build -o ingestbench ./cmd/ingestbench# 1. Secrets come from environment variables, never config files.
export IB_SMOKE_TOKEN=<your-api-token>
# 2. Validate the config.
ingestbench validate --config examples/steady-smoke.yaml
# 3. Preview events without sending anything.
ingestbench gen --source aws.cloudtrail -n 5 --format pretty
# 4. Run the load test.
ingestbench run --config examples/steady-smoke.yamlingestbench serve starts a local control panel (embedded SPA, no Node) with 5 tabs:
Run (start/stop, live metrics), Configs (build/validate/edit/delete),
Source packs (browse catalog), Reports (browse run reports), and
Scenarios (author multi-stage attack chains). Requires --configs-dir, --scenarios-dir,
etc. to enable write operations.
| Command | Description |
|---|---|
run --config <file> |
Run the load test against the configured target |
validate --config <file> |
Validate a config file |
probe |
Probe an endpoint: connectivity, auth, batch framing, rate ceiling |
gen --source <id> |
Generate events to stdout (no network) |
scenario --file <file> |
Play a multi-stage correlated scenario to stdout |
serve |
Start the web control panel |
init |
Interactive config builder (or --non-interactive for CI) |
sources list|show|scaffold|validate |
Manage source packs |
size --eps <n> --event-bytes <n> |
Compute sizing recommendations |
secret set|list|rm |
Manage token files (0600, never echoed) |
version |
Print version info |
| Flag | Default | Description |
|---|---|---|
--config |
(required) | YAML config file |
--scenario |
— | Scenario file for correlated multi-stage runs |
--duration |
config | Override workload.stop.after |
--total |
config | Override workload.stop.total_events |
--workers |
config or 4 | Concurrent send workers |
--output |
text |
text or json |
--seed |
config | Override RNG seed |
Exit codes: 0 OK, 2 config invalid, 3 SLO breach, 4 auth failure, 6 run aborted.
YAML-based. Run ingestbench validate to check, or ingestbench init to generate interactively.
schema_version: "1.0"
name: my-run
seed: 42
mode: realtime # or "deterministic"
target:
adapter: lphc # lphc (default), generic-hec, or syslog
url: https://collector.example.com/lphc/events/json
auth:
scheme: x-api-key # x-api-key, basic, bearer, splunk, header
token_env: MY_TOKEN_ENV_VAR # env var holding the token (never in config)
tls:
verify: true
batch:
max_events: 500
max_bytes: 512KB
compression: none # none or gzip (generic-hec only)
limits:
max_requests_per_sec: 190
source:
id: aws.cloudtrail
version: ">=1.0"
entities: { host: 500, user: 100 }
workload:
profile: steady # steady, burst, or ramp
eps: 5000
stop:
after: 5m # or total_events: 1000000
observability:
dashboard: true
report: /tmp/report.json
prometheus: "127.0.0.1:9090" # optional
slo: # optional; breach -> exit code 3
min_success_rate: 0.99
max_p95_ms: 500Timestamp shaping (source.timestamps) supports historical backfills with anchors,
jitter, and business-hours weighting. See examples/timestamps-backfill.yaml.
| File | Purpose |
|---|---|
steady-smoke.yaml |
Quick first run: 200 events, low rate |
lphc.yaml |
Guardsix HTTP collector |
lphc-syslog.yaml |
Guardsix over syslog (TCP) |
generic-hec.yaml |
Splunk-compatible HEC |
steady-100k.yaml |
1M-event sustained run |
burst.yaml |
Square-wave burst profile |
ramp.yaml |
Linear ramp 500 -> 10k EPS |
timestamps-backfill.yaml |
14-day historical backfill |
| Adapter | target.adapter |
Auth | Use for |
|---|---|---|---|
| LPHC (default) | lphc or omit |
x-api-key, basic |
Guardsix / Logpoint HTTP Collector (JSON packs) |
| Generic HEC | generic-hec |
bearer, splunk, header |
Splunk-compatible HEC (Splunk, Cribl). framing: raw for non-JSON |
| Syslog | syslog |
none (socket) | TCP/UDP/TLS syslog. Native carrier for syslog/CEF packs |
Adapter-specific knobs: target.hec.* (generic-hec) and target.syslog.* (syslog).
See the example configs for annotated usage.
| ID | Category | Description |
|---|---|---|
aws.cloudtrail |
cloud | KMS Decrypt, Console Login, S3 GetObject |
aws.guardduty |
cloud | SSH brute force, port probe, DNS exfiltration |
m365.audit |
cloud | Entra sign-in, SharePoint file ops, Exchange mail |
windows.security |
endpoint | Logon 4624/4625, process creation 4688 |
linux.auditd |
endpoint | execve, PAM auth, user login (JSON) |
linux.syslog |
endpoint | sshd logins, sudo commands (RFC 5424) |
firewall.traffic |
network | Allowed/denied/dropped connections (JSON) |
firewall.cef |
network | Same as above in ArcSight CEF format |
firewall.cef.syslog |
network | CEF wrapped in RFC 5424 syslog |
paloalto.traffic |
network | PAN-OS session end, deny, drop |
identity.okta |
identity | Sign-in, MFA, app SSO |
crowdstrike.falcon |
edr | Process exec, network connect, detection alert |
ingestbench sources list
ingestbench sources show aws.cloudtrail
ingestbench gen --source aws.cloudtrail -n 3 --format prettyMulti-stage, entity-correlated attack chains with MITRE ATT&CK tagging:
schema_version: "2.0"
seed: 12345
anchor: "2024-06-03T13:00:00Z"
correlation:
shared: [user, host, source_ip]
stages:
- { tactic: TA0006, technique: T1110, source: windows.security, count: 40, start_offset: 0s, duration: 4m }
- { tactic: TA0001, technique: T1078, source: identity.okta, count: 5, start_offset: 4m, duration: 1m }
- { tactic: TA0002, technique: T1059, source: windows.security, count: 25, start_offset: 5m, duration: 6m }Preview with ingestbench scenario --file <f>, or push to a collector with
ingestbench run --config <target> --scenario <file>.
Measured on Apple M5 (arm64), Go 1.25.12.
| Metric | Value |
|---|---|
| Engine ceiling | ~65 000 events/s |
| Allocs per event | ~234 |
| Mock LPHC sustained EPS | ~65 500 |
| Batch latency p95 | ~33 ms |
| Event loss | 0 |
Measured 2026-07-21, aws.cloudtrail, batch size 50, find_ceiling: true.
| Workers | EPS | RPS | p50 | p99 | Events | Failures |
|---|---|---|---|---|---|---|
| 4 | 380 | 7.6 | 370 ms | 2 410 ms | 70k | 0 |
| 16 | 876 | 17.5 | 601 ms | 4 223 ms | 160k | 0 |
| 114 | 4 758 | 95.2 | 342 ms | 1 654 ms | 860k | 0 |
Throughput scales linearly with workers (Little's Law). At 114 workers the bottleneck flipped from endpoint-bound to sender-bound (rate limiter ceiling). Zero 429s across all runs.
- Success is per-batch HTTP. A
200means the collector accepted the batch, not that every event was indexed. - Single-node only. No distributed load generation.
- Laptop NIC-bound. Real throughput depends on your network and the target.
See CONTRIBUTING.md.
MIT — see LICENSE.