cryload

sdogruyolMIT活跃维护

用 Crystal 写的单二进制 HTTP 压测工具,面向 CI/CD 流水线,提供真实延迟统计、阈值校验和 JSON 输出,方便在流水线里卡性能回归。

HTTP load testing for CI/CD — honest latency, CI thresholds, single binary, JSON output

v6.1.0发布于 2026-10-11 14:56 UTC217star18fork同步于 2026-11-10 12:04 UTC

怎么上手

由 README 提炼,执行前请确认
curl -sSfL https://raw.githubusercontent.com/sdogruyol/cryload/master/scripts/install.sh | sh -s
cryload https://example.com -n 1000 -c 50
硬件要求按 README 说明

README 未说明 GPU 或显存要求;这是纯 CPU 的 HTTP 压测工具,Linux、macOS、Windows 均可运行,官方提供 amd64/arm64 的 Docker 镜像。

有 Docker 镜像
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

面向 CI/CD 的单二进制 HTTP 压测工具,用修正延迟和阈值门禁判断部署是否拖慢性能

用 Crystal 写的单二进制 HTTP 压测工具,面向 CI/CD 流水线,提供真实延迟统计、阈值校验和 JSON 输出,方便在流水线里卡性能回归。

核心能力

  • 用协调遗漏修正的 corrected_latency 计算 p99 等分位
  • 提供 --max-p50 到 --max-p999 等阈值矩阵,超限退出码 1
  • 按 DNS、连接、TLS、TTFB 拆分阶段耗时定位瓶颈
  • 支持 JSON/CSV/quiet 输出,schema 带版本号便于流水线解析
  • 多核 --workers 并发压测,单文件约 1.4 MB 无运行时依赖

容易踩的坑

从源码构建需要 Crystal >= 1.21.0;Docker 里访问宿主机 localhost 服务需加 --network host;--workers 并非越大越好,超过 8 后实测吞吐反而下降。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v6.1.0
  1. v6.1.0最新正式版2026-10-11 · 2026-10-11 14:56 UTC
    • Documentation — Public descriptions match the README: honest latency and CI thresholds, no stale throughput number. shard.yml, the Docker image, --help, and the installers now share that positioning.
    • Reporting — A refused connection is reported as connect_refused again when cryload is built with Crystal 1.21.1+. Crystal 1.21.1 (crystal-lang/crystal#17436) swapped the real connect error for a stale errno (connect: Operation now in progress), so refusals showed up as connect_failed. cryload patches the Unix event loops' connect until upstream ships the fix
    • Reporting — A connect that exceeds --timeout is reported as connect_timeout. The stdlib raises a bare IO::TimeoutError for it, so it used to land in read_timeout and connect_timeout was never emitted
    • CLI — Numeric flags reject nan, inf and out-of-range values with a config error (exit 2). --rate inf, -d inf or --rate 1e-10 used to crash with OverflowError, and --max-p99 nan crashed with JSON::Error after the whole run, all exiting 1 — the threshold-breach code. --rate now needs at least 1e-9 requests/sec, and huge finite limits (--max-p99 1e300) render instead of overflowing
    • Proxy — HTTPS targets through --proxy now honour --timeout on the tunnel (CONNECT reply, TLS handshake and every request; a stalled proxy used to block the worker forever), reconnect after the target closes a keep-alive connection instead of failing every later request as other, close the socket when CONNECT or the handshake fails, and support https:// proxies and IPv6 proxy literals. Tunnel phases: dns/connect describe the proxy hop (connect includes the CONNECT exchange), tls the handshake with the target
    • Proxy — Plain-HTTP targets through --proxy http://user:pass@... now send Proxy-Authorization; only the CONNECT tunnel used to carry the credentials
    • Accuracy — Transfer metrics count response bodies as received. cryload still sends Accept-Encoding: gzip, deflate by default, but no longer inflates the body, so transfer.* and "Total data" report compressed bytes for a compressing target, and the generator spends no CPU on zlib. An explicit -H Accept-Encoding still wins
    • Accuracy — The process exits with the exact code the report printed. The gates used to be evaluated again after the report was written, and in request-count mode the measurement window stayed open meanwhile, so a slow stdout reader could turn a printed verdict.exit_code: 0 into exit 1 under --min-rps. The window now closes when the last request is merged
    • Accuracy — Request-count workers flush their stats at least once a second, like duration workers. A worker whose -n share was below 250 requests stayed invisible until it finished, so progress and a Ctrl-C report of a slow run showed 0 requests
    • Toolchain — Release binaries, the Docker image and CI are built with Crystal 1.21.1 (was 1.21.0). It carries crystal-lang/crystal#17436, which fixes non-blocking connect on macOS 26.7: a refused connection was treated as established, so a localhost that resolves to ::1 first never fell back to 127.0.0.1. The minimum Crystal for building from source stays 1.21.0
  2. v6.0.02026-08-29 · 2026-08-29 18:21 UTC
    查看更新说明
    • Breaking (Concurrency) — Multi-core load generation is now the default. v5 ran every load fiber on a single thread; v6 sizes the execution context to min(cpu_count, connections) OS threads and exposes it as --workers N. --workers 1 reproduces the v5 single-core path; the cpu default respects CRYSTAL_WORKERS
    • Breaking (Accuracy) — Threshold gates now evaluate corrected latency by default (--latency-correction on). A run that passed --max-p99 in v5 can fail in v6 when the target could not keep up with --rate; corrected and uncorrected latency are identical when --rate is not set. Pass --latency-correction off for v5 gate semantics
    • Breaking (Exit codes) — Exit codes are now a taxonomy instead of a single failure code: usage/config errors moved from 1 to 2, an unreachable target (zero HTTP responses plus at least one transport error) moved from 1 to 3, and signals now exit 130 (SIGINT) / 143 (SIGTERM) instead of 0. 1 is reserved for breached thresholds, and a breached threshold takes precedence over a signal code
    CodeMeaning
    0Run finished, no threshold breached
    1A threshold was breached
    2Usage/config error
    3Target unreachable
    130Interrupted (SIGINT)
    143Terminated (SIGTERM)
    • Breaking (Reporting) — Transport error categories are a fixed, normalized vocabulary instead of Crystal exception class names: dns_failure, connect_refused, connect_timeout, connect_failed, read_timeout, write_timeout, request_timeout, connection_reset, connection_closed, tls_error, proxy_error, protocol_error, other. Consumers matching on IO::TimeoutError or Socket::ConnectError must switch to the new names
    • Breaking (JSON) — JSON output gained a top-level schema_version, currently 1. Every documented key is always present and is null when not applicable, so consumers can stop probing for key existence
    • Breaking (CLI) — --progress now defaults to on only when stderr is a TTY, so CI logs are clean without passing --no-progress. Explicit --progress still forces it on
    • Breaking (Toolchain) — Minimum Crystal is now 1.21.0 (was 1.19.0); multi-core load generation uses Fiber::ExecutionContext.default.resize
    • Performance — --workers 8 -c 128 reaches a median 329,440 req/s against a SO_REUSEPORT target on loopback, versus 109,161 req/s on the single-thread --workers 1 path — a 3.0x gain (AMD Ryzen AI 9 465, 20 threads, 2-byte body, median of 5 three-second runs). --workers 16 measures slower (280,079 req/s) than --workers 8 on the same box, which is why the default is min(cpu cores, connections) rather than every core
    • Performance — Response bodies are streamed through one fixed buffer per worker instead of being materialised as a String, so peak RSS no longer scales with response size: 22.9 MB at --workers 8 -c 128 with 2-byte bodies, 12.9 MB at --workers 8 -c 32 with 100 KB bodies
    • Accuracy — Added coordinated-omission correction. v5 measured latency only from the moment a request was actually sent, so a stalled target made the numbers look better: in a regression test that froze the target for 1 second during -q 500 -d 5, v5 reported p99 = 0.37 ms while silently dropping 490 of 2500 scheduled requests. v6 reports the outage in corrected_latency_ms.p99 and surfaces the loss as rate.skipped_requests
    • Accuracy — Added a rate block reporting requested vs attained req/s, attainment percent, scheduled and skipped requests, and schedule drift, so a rate-limited run can no longer claim a rate it never achieved
    • Accuracy — Added send_delay_ms percentiles, the gap between a request's scheduled send time and its actual send time
    • Accuracy — --timeout now sets the write timeout as well as connect and read; v5 left writes unbounded
    • Accuracy — -q / --rate accepts fractional rates (-q 0.5)
    • CI/CD — Adde
  3. v5.2.12026-08-11 · 2026-08-11 08:19 UTC
    查看更新说明
    • GitHub Action — Shortened the action.yml description to 117 characters; GitHub Marketplace rejects listings whose description is 125 characters or longer, which blocked publishing v5.2.0
  4. v5.2.02026-08-11 · 2026-08-11 08:06 UTC
    查看更新说明
    • Distribution — Added a Docker image at ghcr.io/sdogruyol/cryload: multi-arch (amd64/arm64), ~9 MB Alpine runtime around the same fully static binary as the Linux release
    docker run --rm ghcr.io/sdogruyol/cryload https://example.com -n 1000 -c 50
    
    • Distribution — Added a composite GitHub Action so cryload runs in a workflow step with thresholds and JSON results as step outputs; every CLI flag is exposed as an input, and uses: sdogruyol/cryload@v5 tracks the latest v5 release
    - uses: sdogruyol/cryload@v5
      with:
        url: http://127.0.0.1:3000/api
        requests: 500
        max_p99: "250"
    
    • Releases — Windows release binaries are now linked statically; earlier builds dynamically linked the Crystal toolchain's DLLs and failed to start on machines without Crystal installed
    • Releases — Release smoke tests now cover Windows, so a broken Windows binary fails the release instead of shipping
    • Installer — scripts/install.sh uses GITHUB_TOKEN for the latest-release API lookup when set, avoiding the anonymous per-IP rate limit shared across hosted CI runners
    • Installer — GITHUB_URL now also redirects the release API lookup, so enterprise mirrors query their own /api/v3 endpoint and a mirror token is never sent to public GitHub
    • Documentation — Added docs/github-action.md with the full input/output reference and version-pinning rules
  5. v5.1.02026-08-01 · 2026-08-01 13:18 UTC
    查看更新说明
    • Releases — Linux release binaries are now fully static (musl/Alpine), so they run on any distro without matching glibc/OpenSSL versions
    • Releases — macOS release binary now links OpenSSL statically; release smoke tests cover HTTPS
    • Resilience — Static OpenSSL builds ignore the host openssl.cnf by default (OPENSSL_CONF → null device) so foreign crypto-policy configs cannot abort TLS
    • Resilience — On macOS, TLS falls back to the system CA bundle (/etc/ssl/cert.pem) when the OpenSSL default cert path is missing
    • Documentation — README rewritten for CI/CD positioning; added FAQ and moved the full comparison table to docs/comparison.md
    • Packaging — Updated shard.yml description for shard registries

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    cryload - HTTP load testing for CI/CD

    Cross-platform, single-binary HTTP load testing CLI. Honest latency, CI thresholds, JSON/CSV output. A modern alternative to ab / wrk / hey, written in Crystal.

    Stars CI Release Downloads Crystal License


    Quick start

    curl -sSfL https://raw.githubusercontent.com/sdogruyol/cryload/master/scripts/install.sh | sh
    cryload https://example.com -n 1000 -c 50
    

    1000 requests, 50 concurrent connections, JSON/CSV/plain output. That's it.


    Why cryload?

    Existing tools work fine on your laptop. cryload is built for the one place that matters most: CI/CD pipelines.

    ProblemSolution
    "Did my deploy break performance?"Set latency thresholds that fail your build
    "Where do I put the results?"JSON and CSV output, ready to parse
    "Different OS in CI vs local?"Single binary for Linux, macOS, Windows
    "Which tool works in all three?"cryload does

    If you need a graph on your laptop, use wrk. If you need to fail a pipeline when p99 goes over 200ms, use cryload.


    Features

    • ⚡ Multi-core load generation (--workers), concurrent connections (-c)
    • ⏱️ Duration or request count mode
    • 📊 Latency percentiles: p50, p75, p90, p95, p99, p999 + histogram
    • 🎯 Honest latency: coordinated-omission-corrected percentiles and rate attainment
    • 🔍 Phase breakdown: DNS, connect, TLS, TTFB — see where the time actually went
    • 🗂️ Per-URL and per-status breakdowns, not just one global number
    • 🎯 CI threshold matrix: --max-p50 … --max-p999, --max-avg, --max-latency, --min-rps, --max-fail-rate, per-endpoint --url-threshold
    • 🚦 Exit code taxonomy: slow service, unreachable target and bad config are different codes
    • 📦 JSON / CSV / quiet output for pipelines, with a versioned schema
    • 🔒 Rate limiting, warmup, keep-alive control, request deadlines, TLS skip
    • 🌐 Multi-URL, redirects, proxy, cookies, custom success codes
    • 🖥️ Cross-platform: Linux, macOS, Windows - single binary

    Performance

    cryload is fast. Written in Crystal, compiled to native code, and multi-core by default since 6.0.0.

    All figures below are the median of 5 runs, with the observed spread, because a single run of a load generator on a shared machine is noise, not a number.

    TestThroughput (median of 5)Spreadp99
    --workers 8 -c 128 -d 3329,440 req/sec296k – 342k1.95 ms
    --workers 16 -c 128 -d 3280,079 req/sec278k – 301k2.34 ms
    --workers 4 -c 128 -d 3210,668 req/sec190k – 240k2.38 ms
    --workers 1 -c 128 -d 3 (v5-equivalent single-thread path)109,161 req/sec108k – 114k2.94 ms
    --workers 1 -c 64 -d 3138,106 req/sec110k – 143k1.32 ms
    Peak RSS (--workers 8 -c 128, 2-byte bodies)22.9 MB
    Peak RSS (--workers 1 -c 64, 2-byte bodies)15.7 MB
    Peak RSS (--workers 8 -c 32, 100 KB bodies)12.9 MB
    Binary size~1.4 MB stripped, single file, no runtime dependencies

    Large responses cost less memory than many small ones, because bodies are streamed through one fixed buffer per worker rather than materialised as a String.

    Multi-core buys 3.0x on this hardware. Note that --workers 16 measures slower than --workers 8: once the schedulers outnumber the useful work they fight each other and the target for cores. That is why the default is min(cpu cores, connections) rather than "all of them", and why --workers is a dial worth turning rather than a number to max out.

    Methodology. The target is a local Crystal HTTP::Server returning a 2-byte body, running as 7 processes sharing the port via SO_REUSEPORT so that the target is not the bottleneck. Hardware is an AMD Ryzen AI 9 465 (20 threads), over loopback, release build, 3-second runs, median of 5.

    These are loopback numbers on one machine, not a claim about your network or your application. They exist to show the load generator's own ceiling — the number you need before you can trust that a measurement is about your server. Reproduce them with the commands in the table:

    cryload http://localhost:3000 --workers 8 -c 128 -d 3
    cryload http://localhost:3000 --workers 1 -c 128 -d 3
    

    If throughput rises when you raise --workers, your earlier numbers were measuring the load generator, not the target. And look at that spread column before you set a --min-rps gate from a single local run.

    No JVM, no Node. Just a single binary that starts instantly and uses almost no memory.


    Honest latency

    Most load testers measure latency from the moment a request is sent. That sounds reasonable until the target stalls. While it is stalled, no requests get sent — so no slow measurements get recorded — and the tool reports the handful of fast responses from before and after the freeze. A target that stops responding makes the numbers look better. This is called coordinated omission.

    It is not a rounding error. Here is cryload's own regression test: freeze the target for 1 second during a -q 500 -d 5 run.

    v5v6
    Service p99 (measured from the send)0.37 ms0.56 ms
    p99 the gate actually reads0.37 ms951.96 ms (corrected_latency_ms.p99)
    Scheduled requests25002500
    Requests actually issued20102500
    Dropped without a word4900 (rate.skipped_requests)
    Peak send delay reported—981 ms (rate.schedule_drift_ms)
    --max-p99 200 verdictPASS, exit 0FAIL, exit 1

    Read the second row twice. Same run, same server, same one-second outage: v5 reported a p99 of 0.37 ms and passed the gate, while quietly discarding 490 of the 2500 requests it was supposed to send. v6 reports 951.96 ms and fails the build. Note that v6's service p99 is still sub-millisecond and that is correct — the requests really were served fast once they got out. The outage lives in the waiting, which is exactly what a user would have felt.

    v6 fixes this in three places:

    • corrected_latency_ms charges each request for the time it spent waiting for its scheduled send slot, plus its service time. A request that was supposed to go out at t=1.0s but went out at t=2.0s is a 1-second-plus request, because that is what a real user would have experienced.
    • rate.skipped_requests and rate.attainment_percent report the schedule slots that were never issued, so a run can no longer claim a rate it did not achieve.
    • Threshold gates evaluate corrected latency by default, so --max-p99 250 fails on that frozen target instead of passing. Add --fail-on-rate-miss to also fail when the attained rate falls more than 1% short.

    Corrected and uncorrected latency are identical when --rate is not set: without a request schedule there is no send delay to correct for. Pass --latency-correction off for v5 gate semantics.


    Installation

    Option 1: Install script (recommended)

    Linux / macOS:

    curl -sSfL https://raw.githubusercontent.com/sdogruyol/cryload/master/scripts/install.sh | sh -s
    

    Windows (PowerShell):

    iwr -useb https://raw.githubusercontent.com/sdogruyol/cryload/master/scripts/install.ps1 | iex
    

    Option 2: Prebuilt binary

    Download from Releases:

    chmod +x cryload-linux
    ./cryload-linux --help
    

    Option 3: Docker

    docker run --rm ghcr.io/sdogruyol/cryload https://example.com -n 1000 -c 50
    

    Multi-arch (amd64/arm64) image, ~9 MB. Use --network host on Linux to reach services on the host's localhost. Tags follow the release version without the v prefix: 6.0.0, 6.0, 6, latest.

    Option 4: GitHub Action

    - uses: sdogruyol/cryload@v6
      with:
        url: http://127.0.0.1:3000/api
        requests: 500
        max_p99: "250"
    

    See docs/github-action.md for all inputs and outputs.

    Option 5: Build from source

    Requires Crystal >= 1.21.0.

    git clone https://github.com/sdogruyol/cryload.git && cd cryload
    shards build --release
    

    Usage

    cryload <url> [options]
    
    OptionDescription
    -n, --numbersNumber of requests
    -d, --durationTest duration (30, 30s, 2m, 1h30m, 500ms)
    -c, --connectionsConcurrent connections (default: 10)
    --workersOS threads running the load (default: min(cpus, connections); --workers 1 reproduces v5 behaviour)
    -m, --methodHTTP method (default: GET)
    -b, --bodyRequest body
    --body-fileRead body from file
    --body-stdinRead body from stdin
    -H, --headerRepeatable header (-H "Key: Value")
    -a, --basic-authBasic auth (user:password)
    --user-agentUser-Agent override
    --host-headerExplicit Host header
    --timeoutConnect/read/write timeout, with units
    --request-timeoutTotal deadline for one request, including body download
    -q, --rateRate limit in req/sec, fractional allowed (-q 0.5)
    --latency-correctionon (default) or off — gate on corrected latency
    -L, --follow-redirectsFollow redirects
    --disable-keepaliveNew connection per request; setup cost counts as latency
    --output-formattext, json, csv, quiet
    --jsonShorthand for --output-format json
    --success-statusCustom success codes/ranges
    --insecureSkip TLS verification
    --warmupUntimed warmup before the benchmark, with units
    --proxyHTTP(S) proxy
    --cookieRepeatable cookie (name=value)
    --urls-fileLoad target URLs from file
    --random-pathAppend random path per request
    --progress / --no-progressLive stderr progress (default: on only when stderr is a TTY)
    -V, --versionPrint the installed release

    Threshold flags are in CI/CD below.

    Common examples

    # 10K requests, 100 concurrent
    cryload http://localhost:3000 -n 10000 -c 100
    
    # 30 seconds, 50 connections
    cryload http://localhost:3000 -d 30s -c 50
    
    # POST with JSON body
    cryload http://localhost:3000/api -n 500 -m POST \
      -H "Content-Type: application/json" \
      -b '{"name":"cry"}' --timeout 5
    

    See docs/examples.md for more.


    CI/CD

    cryload is built for pipelines. Use --json or --output-format csv for structured output, --output-format quiet for exit-code-only checks.

    Threshold matrix

    Any breached threshold exits 1.

    FlagEffect
    --max-p50 MSExit 1 if p50 exceeds MS
    --max-p75 MSExit 1 if p75 exceeds MS
    --max-p90 MSExit 1 if p90 exceeds MS
    --max-p95 MSExit 1 if p95 exceeds MS
    --max-p99 MSExit 1 if p99 exceeds MS
    --max-p999 MSExit 1 if p999 exceeds MS
    --max-avg MSExit 1 if average latency exceeds MS
    --max-latency MSExit 1 if the maximum observed latency exceeds MS
    --min-rps NExit 1 if throughput is below N req/s
    --max-fail-rate PCTExit 1 if the failure rate exceeds PCT
    --fail-on-errorExit 1 on any HTTP or transport error
    --fail-on-transport-errorExit 1 on any transport error
    --fail-on-rate-missExit 1 if the attained rate is more than 1% below --rate

    Latency gates evaluate corrected latency by default; --latency-correction off restores v5 semantics. See Honest latency.

    Per-endpoint gates

    --url-threshold "PATTERN METRIC VALUE" is repeatable and scopes a gate to the target URLs whose text contains PATTERN:

    cryload --urls-file urls.txt -d 30s -c 50 \
      --url-threshold "/api/users max-p99 120" \
      --url-threshold "/api/search max-p99 400" \
      --url-threshold "/api/orders max-fail-rate 0.5" \
      --min-rps 2000
    

    METRIC is one of max-p50 max-p75 max-p90 max-p95 max-p99 max-p999 max-avg max-latency max-fail-rate min-rps. A pattern that matches no target URL is a config error (exit 2), so a renamed endpoint fails the build instead of silently un-gating itself.

    Exit codes

    CodeMeaning
    0Run finished, no threshold breached
    1A threshold was breached
    2Usage/config error (bad flag, bad URL, missing file, unresolvable --url-threshold pattern, fd limit too low)
    3Target unreachable: zero HTTP responses and at least one transport error
    130Interrupted (SIGINT)
    143Terminated (SIGTERM)

    A breached threshold takes precedence over a signal code. The same information is in the JSON report as verdict.exit_code and verdict.reason.

    GitHub Actions example

    Use the official action — it installs cryload, runs the benchmark, and exposes the JSON results as step outputs:

    - name: Latency SLA
      id: bench
      uses: sdogruyol/cryload@v6
      with:
        url: http://localhost:3000/api
        requests: 500
        max_p99: "250"
    
    - name: Show p99
      if: always()
      run: echo "p99 was ${{ steps.bench.outputs.p99 }} ms"
    

    See docs/github-action.md for all inputs and outputs. Or install the binary directly:

    - name: Install cryload
      run: curl -sSfL https://raw.githubusercontent.com/sdogruyol/cryload/master/scripts/install.sh | sh -s
    
    - name: Latency SLA
      run: |
        cryload http://localhost:3000/api -d 30s -q 500 \
          --max-p99 250 --fail-on-rate-miss --json > result.json
        jq -e '.schema_version == 1' result.json
        jq -r '.thresholds.breached[] | "FAIL \(.name) \(.metric)=\(.actual) limit=\(.limit)"' result.json
    

    --no-progress is no longer needed: progress is only enabled when stderr is a TTY, so CI logs stay clean by default.


    How cryload compares

    Featurecryloadabheyohawrk
    CI/CD output (JSON/CSV/quiet)✅-JSONJSON-
    CI threshold exit codes✅----
    Per-endpoint threshold gates✅----
    Exit code taxonomy✅----
    Coordinated-omission correction✅----
    Latency phase breakdown✅-partialpartial-
    Rate limiting (--rate)✅-partial✅-
    Multi-core load generation✅-✅✅✅
    HTTP/2--✅✅-
    Scriptable load (Lua)----✅
    Cross-platform binary✅Linux✅✅Linux

    Full matrix: docs/comparison.md.


    Built with Crystal

    cryload is written in Crystal. Ruby-like syntax, compiled speed, single-binary deployment.

    Crystal


    FAQ

    Why not just use ab / hey / wrk?

    Those tools are great for local benchmarks, and wrk still wins on Lua-scripted scenarios. cryload is built for CI/CD: coordinated-omission-corrected latency, a threshold matrix you can scope per endpoint, an exit code taxonomy, JSON/CSV output with a versioned schema, and cross-platform binaries. Since 6.0.0 it also saturates every core by default, so multi-core is no longer a reason to reach for another tool. If you want to fail a pipeline when p99 goes over 200ms — and be sure that p99 is honest — use cryload.

    Can I use cryload for DDoS?

    No. cryload is designed for testing your own servers and CI pipelines. Do not use it against targets you don't own.

    Does cryload support HTTP/2?

    Not yet. HTTP/1.1 only for now. HTTP/2 is on the roadmap.

    What is coordinated omission and why should I care?

    Most load testers start the latency clock when a request is sent. When the target stalls, nothing gets sent, so nothing slow gets measured — and the report shows only the fast responses from either side of the stall. A target that stops responding therefore looks faster. In cryload's own regression test, freezing the target for one second during a -q 500 -d 5 run made v5 report p99 = 0.37 ms while it silently dropped 490 of 2500 scheduled requests. v6 charges each request for the time it waited for its scheduled slot (corrected_latency_ms), reports the slots it never issued (rate.skipped_requests), and gates on corrected latency by default. See Honest latency.

    Why did my exit code change from 1 to 2/3?

    v5 returned 1 for every failure. v6 splits them so a pipeline can tell them apart: 1 is a breached threshold (a real regression to triage), 2 is a usage or config error (a broken command line or workflow file), and 3 is an unreachable target (nothing answered — usually a broken test harness, not a slow service). Signals now exit 130/143 instead of 0, so a killed run no longer looks like a pass. If your script did if cryload ...; then, it still behaves the same; if it matched on == 1, widen it to != 0 or switch on the code — docs/examples.md has a template.

    Is there a Docker image?

    Yes: docker pull ghcr.io/sdogruyol/cryload. The single binary is still the lightest option, but the image is handy for containerized pipelines (GitLab CI, Kubernetes jobs, etc.).

    Why is it written in Crystal?

    Crystal compiles to a single native binary with no runtime. It starts instantly, uses minimal memory, and delivers C-like performance with Ruby-like syntax.

    Sponsors

    If cryload helps your CI pipeline, consider sponsoring. Every dollar helps me keep building open source tools full time.

    Sponsor


    License

    MIT