<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Alek&#x27;s Blog - deno</title>
      <link>https://blog.none.at</link>
      <description>Production notes on Kubernetes, OpenShift, and OVHcloud: observability, log archiving, service mesh, LLM inference, and digital sovereignty.</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://blog.none.at/tags/deno/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Tue, 03 Mar 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>loadgen-rs (Short): Results, Quick Start, and Tool Choice</title>
          <pubDate>Tue, 03 Mar 2026 00:00:00 +0000</pubDate>
          <author>aleks</author>
          <link>https://blog.none.at/blog/2026/2026-03-01-loadgen-rs-short/</link>
          <guid>https://blog.none.at/blog/2026/2026-03-01-loadgen-rs-short/</guid>
          <description xml:base="https://blog.none.at/blog/2026/2026-03-01-loadgen-rs-short/">&lt;p&gt;This is the short version.&lt;br &#x2F;&gt;
If you want profiling details, FFI internals, and full cloud deployment walkthroughs, read the full article:
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;blog.none.at&#x2F;blog&#x2F;2026&#x2F;2026-03-01-loadgen-rs&#x2F;&quot;&gt;loadgen-rs: An HTTP Benchmark Client in Rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;tl-dr&quot;&gt;TL;DR&lt;a class=&quot;zola-anchor&quot; href=&quot;#tl-dr&quot; aria-label=&quot;Anchor link for: tl-dr&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;loadgen-rs&lt;&#x2F;code&gt; is a Rust benchmark client for HTTP&#x2F;1.1, HTTP&#x2F;2, and HTTP&#x2F;3 with:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;one CLI for all protocols&lt;&#x2F;li&gt;
&lt;li&gt;JSONL&#x2F;CSV output for automation&lt;&#x2F;li&gt;
&lt;li&gt;distributed controller&#x2F;worker mode&lt;&#x2F;li&gt;
&lt;li&gt;scripted Deno scenarios via FFI (k6-style checks&#x2F;extractors)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In short: very strong for reproducible automation and multi-node testing.&lt;br &#x2F;&gt;
If your only goal is absolute H3 peak throughput, &lt;code&gt;h2load&lt;&#x2F;code&gt; still has an edge.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;quick-start&quot;&gt;Quick Start&lt;a class=&quot;zola-anchor&quot; href=&quot;#quick-start&quot; aria-label=&quot;Anchor link for: quick-start&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;single-node-cli&quot;&gt;Single-node cli&lt;a class=&quot;zola-anchor&quot; href=&quot;#single-node-cli&quot; aria-label=&quot;Anchor link for: single-node-cli&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; H2 example, 10s run&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list=h2&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-insecure&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;single-node-container&quot;&gt;Single-node container&lt;a class=&quot;zola-anchor&quot; href=&quot;#single-node-container&quot; aria-label=&quot;Anchor link for: single-node-container&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; HTTP&#x2F;3 (QUIC) 10 seconds&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;podman&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; run&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;it&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rm&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-network&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; host&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-name&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; gh-loadgen&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; ghcr.io&#x2F;git001&#x2F;loadgen-rs:v0.3.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list=h3&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-insecure&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;distributed&quot;&gt;Distributed&lt;a class=&quot;zola-anchor&quot; href=&quot;#distributed&quot; aria-label=&quot;Anchor link for: distributed&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 1) start workers&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;bash&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; examples&#x2F;worker-start.sh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 2) run controller&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;deno&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; run&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-allow-ffi&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-allow-net&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-allow-read&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-allow-env&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  examples&#x2F;distributed.ts&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; http:&#x2F;&#x2F;worker1:9091&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; http:&#x2F;&#x2F;worker2:9091&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Why distributed matters: you can scale beyond one machine and still get statistically correct merged percentiles (histogram merge, not naive p99 averaging).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;benchmark-snapshot-vs-h2load&quot;&gt;Benchmark Snapshot vs h2load&lt;a class=&quot;zola-anchor&quot; href=&quot;#benchmark-snapshot-vs-h2load&quot; aria-label=&quot;Anchor link for: benchmark-snapshot-vs-h2load&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;High-concurrency test (&lt;code&gt;-c 512 -t 8 -m 8&lt;&#x2F;code&gt;, 30s):&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Protocol&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;h2load RPS&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;loadgen-rs RPS&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Ratio&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;H1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;18,851&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;19,148&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;101.6%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;H2&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;17,837&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;18,025&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;101.1%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;H3&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;9,006&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;6,702&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;74.4%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Takeaway:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;H1&#x2F;H2 throughput: effectively on par (or slightly better for loadgen-rs in this run).&lt;&#x2F;li&gt;
&lt;li&gt;H3 throughput: h2load remains faster.&lt;&#x2F;li&gt;
&lt;li&gt;Memory footprint: h2load is smaller; loadgen-rs typically used more memory in tests.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;which-tool-should-you-use&quot;&gt;Which Tool Should You Use?&lt;a class=&quot;zola-anchor&quot; href=&quot;#which-tool-should-you-use&quot; aria-label=&quot;Anchor link for: which-tool-should-you-use&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Need&lt;&#x2F;th&gt;&lt;th&gt;Better fit&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Highest H3 throughput only&lt;&#x2F;td&gt;&lt;td&gt;h2load&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;One tool for H1&#x2F;H2&#x2F;H3 + machine-readable reports&lt;&#x2F;td&gt;&lt;td&gt;loadgen-rs&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Distributed multi-machine load generation&lt;&#x2F;td&gt;&lt;td&gt;loadgen-rs&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Scripted scenarios with checks&#x2F;extractors and correlation&lt;&#x2F;td&gt;&lt;td&gt;loadgen-rs&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Lowest memory footprint&lt;&#x2F;td&gt;&lt;td&gt;h2load&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h2 id=&quot;next-step&quot;&gt;Next Step&lt;a class=&quot;zola-anchor&quot; href=&quot;#next-step&quot; aria-label=&quot;Anchor link for: next-step&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;For the complete deep dive (profiling, architecture, FFI API design, Terraform&#x2F;Ansible workflow), continue with the full article:
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;blog.none.at&#x2F;blog&#x2F;2026&#x2F;2026-03-01-loadgen-rs&#x2F;&quot;&gt;loadgen-rs: An HTTP Benchmark Client in Rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>loadgen-rs: An HTTP Benchmark Client in Rust</title>
          <pubDate>Tue, 03 Mar 2026 00:00:00 +0000</pubDate>
          <author>aleks</author>
          <link>https://blog.none.at/blog/2026/2026-03-01-loadgen-rs/</link>
          <guid>https://blog.none.at/blog/2026/2026-03-01-loadgen-rs/</guid>
          <description xml:base="https://blog.none.at/blog/2026/2026-03-01-loadgen-rs/">&lt;h2 id=&quot;tl-dr-executive-summary&quot;&gt;TL;DR &#x2F; Executive Summary&lt;a class=&quot;zola-anchor&quot; href=&quot;#tl-dr-executive-summary&quot; aria-label=&quot;Anchor link for: tl-dr-executive-summary&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Prefer a shorter read first?&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;blog.none.at&#x2F;blog&#x2F;2026&#x2F;2026-03-01-loadgen-rs-short&#x2F;&quot;&gt;loadgen-rs (Short): Results, Quick Start, and Tool Choice&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;loadgen-rs is a Rust HTTP benchmark client that covers HTTP&#x2F;1.1, HTTP&#x2F;2, and HTTP&#x2F;3 in one tool, outputs machine-readable JSONL&#x2F;CSV, and scales from single-node runs to distributed multi-worker tests.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;H1&#x2F;H2 throughput is competitive with h2load, while H3 still favors h2load in peak throughput.&lt;&#x2F;li&gt;
&lt;li&gt;loadgen-rs is strong when you want automation-friendly output and one consistent workflow across protocols.&lt;&#x2F;li&gt;
&lt;li&gt;Distributed mode is a first-class feature, not an afterthought: controller + workers + correct histogram merge.&lt;&#x2F;li&gt;
&lt;li&gt;A Deno&#x2F;FFI layer enables scripted, k6-style scenarios with checks, extractors, and correlation.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;what-is-loadgen-rs&quot;&gt;What Is loadgen-rs?&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-is-loadgen-rs&quot; aria-label=&quot;Anchor link for: what-is-loadgen-rs&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;why-i-built-it&quot;&gt;Why I Built It&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-i-built-it&quot; aria-label=&quot;Anchor link for: why-i-built-it&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;If you benchmark HTTP servers, you probably know &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;nghttp2.org&#x2F;documentation&#x2F;h2load-howto.html&quot;&gt;h2load&lt;&#x2F;a&gt; from the nghttp2 project. It’s a very solid and mature tool, but there are a few pain points:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTTP&#x2F;3 support is not enabled by default&lt;&#x2F;strong&gt; — h2load can do H3&#x2F;QUIC, but only via a separate build variant with ngtcp2.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Requires a C compiler and many native libraries&lt;&#x2F;strong&gt; — this makes source builds and CI setup more cumbersome, especially across different environments.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I wanted a tool that tests all three HTTP protocols and outputs the results directly as JSON and &lt;em&gt;easy&lt;&#x2F;em&gt; to build. So I built &lt;strong&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;git001&#x2F;loadgen-rs&quot;&gt;loadgen-rs&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;core-capabilities-h1-h2-h3-jsonl-distributed-scripting&quot;&gt;Core Capabilities (H1&#x2F;H2&#x2F;H3, JSONL, Distributed, Scripting)&lt;a class=&quot;zola-anchor&quot; href=&quot;#core-capabilities-h1-h2-h3-jsonl-distributed-scripting&quot; aria-label=&quot;Anchor link for: core-capabilities-h1-h2-h3-jsonl-distributed-scripting&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Component&lt;&#x2F;th&gt;&lt;th&gt;Crate&lt;&#x2F;th&gt;&lt;th&gt;Why&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Runtime&lt;&#x2F;td&gt;&lt;td&gt;tokio (multi-thread)&lt;&#x2F;td&gt;&lt;td&gt;De-facto standard for async Rust&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;HTTP&#x2F;1.1&lt;&#x2F;td&gt;&lt;td&gt;Raw TCP + httparse&lt;&#x2F;td&gt;&lt;td&gt;Maximum control, zero framework overhead&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;HTTP&#x2F;2&lt;&#x2F;td&gt;&lt;td&gt;h2 (direct, no hyper)&lt;&#x2F;td&gt;&lt;td&gt;Avoids hyper’s abstraction layer&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;TLS&lt;&#x2F;td&gt;&lt;td&gt;rustls (ring backend)&lt;&#x2F;td&gt;&lt;td&gt;Pure Rust, no OpenSSL dependency hell&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;HTTP&#x2F;3&lt;&#x2F;td&gt;&lt;td&gt;quinn + h3 + h3-quinn&lt;&#x2F;td&gt;&lt;td&gt;QUIC stack in Rust&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;CLI&lt;&#x2F;td&gt;&lt;td&gt;clap (derive)&lt;&#x2F;td&gt;&lt;td&gt;Ergonomic, type-safe&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Metrics&lt;&#x2F;td&gt;&lt;td&gt;hdrhistogram&lt;&#x2F;td&gt;&lt;td&gt;Industry standard for latency measurements&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Allocator&lt;&#x2F;td&gt;&lt;td&gt;mimalloc&lt;&#x2F;td&gt;&lt;td&gt;Better multi-threaded allocation performance&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;An early design choice was to bypass &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hyper.rs&#x2F;&quot;&gt;hyper&lt;&#x2F;a&gt; entirely. For H1, we use raw TCP sockets with &lt;code&gt;httparse&lt;&#x2F;code&gt; for response parsing. For H2, we use the &lt;code&gt;h2&lt;&#x2F;code&gt; crate directly. This eliminates one abstraction layer and gives full control over connection management, flow control tuning, and request templating. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hyper.rs&#x2F;&quot;&gt;Hyper&lt;&#x2F;a&gt; is a great library, but its abstraction model didn’t fit the needs of this benchmarking tool.&lt;&#x2F;p&gt;
&lt;p&gt;In addition to single-node CLI benchmarking, loadgen-rs includes distributed controller&#x2F;worker execution and scripted scenario support through a native FFI layer for Deno.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;runtime-and-execution-model&quot;&gt;Runtime and Execution Model&lt;a class=&quot;zola-anchor&quot; href=&quot;#runtime-and-execution-model&quot; aria-label=&quot;Anchor link for: runtime-and-execution-model&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;figure class=&quot;mt-4&quot;&gt;
  &lt;img
    class=&quot;block image-shortcode mx-auto&quot;
    src=&quot;https:&#x2F;&#x2F;blog.none.at&#x2F;blog&#x2F;2026&#x2F;2026-03-01-loadgen-rs&#x2F;runtime-and-execution-model.png&quot;
    alt=&quot;Runtime and execution model: main, runner, drivers, metrics, output&quot;
    style=&quot;display:block; width:100%; max-width:700px; height:auto; margin:0 auto;&quot;
    loading=&quot;lazy&quot;
    decoding=&quot;async&quot;
  &gt;
&lt;&#x2F;figure&gt;
&lt;h4 id=&quot;connection-strategy&quot;&gt;Connection Strategy&lt;a class=&quot;zola-anchor&quot; href=&quot;#connection-strategy&quot; aria-label=&quot;Anchor link for: connection-strategy&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;The &lt;code&gt;-c&lt;&#x2F;code&gt; and &lt;code&gt;-m&lt;&#x2F;code&gt; flags follow h2load semantics:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-c N&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; spawns N parallel connections distributed across worker threads&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-m N&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; limits concurrent in-flight requests per connection (H2&#x2F;H3: concurrent streams; H1: in-flight cap)&lt;&#x2F;li&gt;
&lt;li&gt;Total parallelism: &lt;code&gt;c * m&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Each worker thread runs its own &lt;code&gt;current_thread&lt;&#x2F;code&gt; Tokio runtime — &lt;strong&gt;no cross-thread synchronization in the hot path&lt;&#x2F;strong&gt;. Workers own local HdrHistograms that are merged only at the end.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;count-vs-duration-mode&quot;&gt;Count vs. Duration Mode&lt;a class=&quot;zola-anchor&quot; href=&quot;#count-vs-duration-mode&quot; aria-label=&quot;Anchor link for: count-vs-duration-mode&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Count mode&lt;&#x2F;strong&gt; (&lt;code&gt;-n 1000&lt;&#x2F;code&gt;): A shared &lt;code&gt;AtomicU64&lt;&#x2F;code&gt; budget that workers decrement via CAS&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Duration mode&lt;&#x2F;strong&gt; (&lt;code&gt;-D 10s&lt;&#x2F;code&gt;): A &lt;code&gt;CancellationToken&lt;&#x2F;code&gt; that fires after the specified duration&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;-D&lt;&#x2F;code&gt; takes priority over &lt;code&gt;-n&lt;&#x2F;code&gt; (matching h2load behavior)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;rps-semantics-h2load-compatible&quot;&gt;RPS Semantics (h2load-compatible)&lt;a class=&quot;zola-anchor&quot; href=&quot;#rps-semantics-h2load-compatible&quot; aria-label=&quot;Anchor link for: rps-semantics-h2load-compatible&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;code&gt;--rps&lt;&#x2F;code&gt; is implemented as a &lt;strong&gt;per-client&lt;&#x2F;strong&gt; request start rate, matching &lt;code&gt;h2load&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Per-client target: &lt;code&gt;--rps&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Aggregate target: &lt;code&gt;--rps * -c&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This matters for comparisons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-c 10 --rps 20&lt;&#x2F;code&gt; targets roughly &lt;code&gt;200&lt;&#x2F;code&gt; started req&#x2F;s overall&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;-c 10 --rps 200&lt;&#x2F;code&gt; targets roughly &lt;code&gt;2000&lt;&#x2F;code&gt; started req&#x2F;s overall&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Also note: in short &lt;strong&gt;count mode&lt;&#x2F;strong&gt; runs (&lt;code&gt;-n&lt;&#x2F;code&gt;), achieved &lt;code&gt;%&lt;&#x2F;code&gt; can appear above or below 100% because startup and shutdown overhead dominate a short elapsed window. For stable pacing comparisons, use &lt;strong&gt;duration mode&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;output-and-tls-notes&quot;&gt;Output and TLS Notes&lt;a class=&quot;zola-anchor&quot; href=&quot;#output-and-tls-notes&quot; aria-label=&quot;Anchor link for: output-and-tls-notes&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The JSONL output contains everything a Python script needs:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;proto&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;h2&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;url&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;target:8443&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;clients&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;threads&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 12543.7&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;latency_p50_us&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 320&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;latency_p90_us&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 890&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;latency_p99_us&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 2100&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;status_counts&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;200&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 125437&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;status_2xx&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 125437&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;err_total&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;elapsed_s&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10.001&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In Python:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;results&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;json&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-generic&quot;&gt;loads&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;line&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; for&lt;&#x2F;span&gt;&lt;span&gt; line&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-support z-function&quot;&gt; open&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;results.jsonl&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;df&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; pd&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-generic&quot;&gt;DataFrame&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;results&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;df&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-generic&quot;&gt;plot&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-variable z-parameter z-function-call&quot;&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;clients&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-variable z-parameter z-function-call&quot;&gt; y&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-variable z-parameter z-function-call&quot;&gt; kind&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;bar&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h4 id=&quot;tls-configuration&quot;&gt;TLS Configuration&lt;a class=&quot;zola-anchor&quot; href=&quot;#tls-configuration&quot; aria-label=&quot;Anchor link for: tls-configuration&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;code&gt;--insecure&lt;&#x2F;code&gt; implements a custom &lt;code&gt;ServerCertVerifier&lt;&#x2F;code&gt; that accepts everything. &lt;code&gt;--tls-ciphers&lt;&#x2F;code&gt; allows selecting specific cipher suites — with validation that H3 only permits TLS 1.3 ciphers. Custom CA certificates can be loaded via &lt;code&gt;--tls-ca&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;quick-start&quot;&gt;Quick Start&lt;a class=&quot;zola-anchor&quot; href=&quot;#quick-start&quot; aria-label=&quot;Anchor link for: quick-start&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;single-node-benchmark-in-30-seconds-cli&quot;&gt;Single-Node Benchmark in 30 Seconds (cli)&lt;a class=&quot;zola-anchor&quot; href=&quot;#single-node-benchmark-in-30-seconds-cli&quot; aria-label=&quot;Anchor link for: single-node-benchmark-in-30-seconds-cli&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; HTTP&#x2F;1.1, 1000 requests&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1000&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-h1&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http:&#x2F;&#x2F;bench.local:8081&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; HTTPS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1000&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-h1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-insecure&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; HTTP&#x2F;2, 10 seconds&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list=h2&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-insecure&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; HTTP&#x2F;3 (QUIC)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list=h3&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-insecure&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;single-node-benchmark-in-30-seconds-container&quot;&gt;Single-Node Benchmark in 30 Seconds (container)&lt;a class=&quot;zola-anchor&quot; href=&quot;#single-node-benchmark-in-30-seconds-container&quot; aria-label=&quot;Anchor link for: single-node-benchmark-in-30-seconds-container&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;If you don’t have &lt;code&gt;podman&lt;&#x2F;code&gt;, you can use &lt;code&gt;docker&lt;&#x2F;code&gt; instead.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; HTTP&#x2F;3 (QUIC) 10 seconds&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;podman&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; run&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;it&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rm&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-network&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; host&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-name&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; gh-loadgen&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; ghcr.io&#x2F;git001&#x2F;loadgen-rs:v0.3.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list=h3&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-insecure&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;distributed-benchmark-in-2-minutes&quot;&gt;Distributed Benchmark in 2 Minutes&lt;a class=&quot;zola-anchor&quot; href=&quot;#distributed-benchmark-in-2-minutes&quot; aria-label=&quot;Anchor link for: distributed-benchmark-in-2-minutes&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Start workers (one per machine, or locally for testing)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;bash&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; examples&#x2F;worker-start.sh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Run controller&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;deno&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; run&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-allow-ffi&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-allow-net&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-allow-read&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-allow-env&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  examples&#x2F;distributed.ts&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; http:&#x2F;&#x2F;worker1:9091&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; http:&#x2F;&#x2F;worker2:9091&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Or programmatically:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block&quot;&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt; Controller&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block&quot;&gt; }&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; from&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;.&#x2F;ts&#x2F;mod.ts&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-storage&quot;&gt;const&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-constant z-variable&quot;&gt; controller&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt; new&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; Controller&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;  workers&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http:&#x2F;&#x2F;worker1:9091&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http:&#x2F;&#x2F;worker2:9091&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;  config&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    url&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;target:8443&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    protocol&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;h2&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    clients&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 200&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;     &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; split: 100 per worker&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    threads&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    duration_s&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 30&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    insecure&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-storage&quot;&gt;const&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-constant z-variable&quot;&gt; result&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt; controller&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;console&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;result&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;merged_report&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;rps&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;minimal-comparison-against-h2load&quot;&gt;Minimal Comparison Against h2load&lt;a class=&quot;zola-anchor&quot; href=&quot;#minimal-comparison-against-h2load&quot; aria-label=&quot;Anchor link for: minimal-comparison-against-h2load&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 1) H1 count mode (same request budget)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;h2load&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-h1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 200&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-h1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 200&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 2) H1 duration mode with pacing (~200 aggregate rps: 10 clients * 20 rps&#x2F;client)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;h2load&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-h1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-h1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-duration&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 3) H2 duration mode with pacing (same effective target)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;h2load&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list=h2&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; h2&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-duration&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 4) H3 duration mode with pacing (same effective target)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;h2load&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list=h3&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; h3&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-duration&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;key-results-vs-h2load&quot;&gt;Key Results vs h2load&lt;a class=&quot;zola-anchor&quot; href=&quot;#key-results-vs-h2load&quot; aria-label=&quot;Anchor link for: key-results-vs-h2load&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;After implementing and tuning the tool, I ran systematic A&#x2F;B comparisons against &lt;code&gt;h2load&lt;&#x2F;code&gt; on the same hardware and target server.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;high-concurrency-benchmark&quot;&gt;High-Concurrency Benchmark&lt;a class=&quot;zola-anchor&quot; href=&quot;#high-concurrency-benchmark&quot; aria-label=&quot;Anchor link for: high-concurrency-benchmark&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Setup:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Duration: 30 seconds per run, 30-second cooldown between runs&lt;&#x2F;li&gt;
&lt;li&gt;Configuration: &lt;code&gt;-c 512 -t 8 -m 8&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Target: &lt;code&gt;https:&#x2F;&#x2F;127.0.0.1:8082&#x2F;?s=256k&lt;&#x2F;code&gt; (256 KB response body)&lt;&#x2F;li&gt;
&lt;li&gt;h2load runs via &lt;code&gt;podman run --rm --network host localhost&#x2F;h2load&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Protocol&lt;&#x2F;th&gt;&lt;th&gt;h2load RPS&lt;&#x2F;th&gt;&lt;th&gt;loadgen-rs RPS&lt;&#x2F;th&gt;&lt;th&gt;Ratio&lt;&#x2F;th&gt;&lt;th&gt;h2load Latency (mean)&lt;&#x2F;th&gt;&lt;th&gt;loadgen-rs Latency (mean)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;H1&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;18,851&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;19,148&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;101.6%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;27.07 ms&lt;&#x2F;td&gt;&lt;td&gt;25.69 ms&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;H2&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;17,837&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;18,025&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;101.1%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;125.59 ms&lt;&#x2F;td&gt;&lt;td&gt;223.91 ms&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;H3&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;9,006&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;6,702&lt;&#x2F;td&gt;&lt;td&gt;74.4%&lt;&#x2F;td&gt;&lt;td&gt;450.15 ms&lt;&#x2F;td&gt;&lt;td&gt;604.17 ms&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Key observations:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;H1&lt;&#x2F;strong&gt;: loadgen-rs is &lt;strong&gt;faster&lt;&#x2F;strong&gt; than h2load on both throughput and latency. The raw TCP + httparse approach with pre-built request templates pays off.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;H2&lt;&#x2F;strong&gt;: Throughput is essentially identical. However, mean latency is ~1.8x higher in loadgen-rs due to an architectural difference (see profiling section below).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;H3&lt;&#x2F;strong&gt;: h2load (using ngtcp2&#x2F;nghttp3 in C) retains a ~25% throughput advantage over quinn&#x2F;h3 in Rust.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;To be fair, h2load was sometimes better than loadgen-rs, and vice versa.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;resource-usage-cpu-and-memory&quot;&gt;Resource Usage (CPU and Memory)&lt;a class=&quot;zola-anchor&quot; href=&quot;#resource-usage-cpu-and-memory&quot; aria-label=&quot;Anchor link for: resource-usage-cpu-and-memory&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;For resource fairness, both tools were run as containers and sampled with &lt;code&gt;podman stats&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;Setup:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;3 repeats per point, &lt;code&gt;duration=5s&lt;&#x2F;code&gt;, &lt;code&gt;c=4&lt;&#x2F;code&gt;, &lt;code&gt;t=2&lt;&#x2F;code&gt;, &lt;code&gt;m in {1,8}&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;&#x2F;th&gt;&lt;th&gt;H1&#x2F;H2&lt;&#x2F;th&gt;&lt;th&gt;H3&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;CPU time&lt;&#x2F;td&gt;&lt;td&gt;mostly &lt;code&gt;+2%&lt;&#x2F;code&gt; to &lt;code&gt;+8%&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;-1%&lt;&#x2F;code&gt; to &lt;code&gt;+10%&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Peak memory&lt;&#x2F;td&gt;&lt;td&gt;about &lt;code&gt;+410%&lt;&#x2F;code&gt; to &lt;code&gt;+914%&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;about &lt;code&gt;+1393%&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;In absolute terms, peak memory was typically:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;h2load&lt;&#x2F;code&gt;: around &lt;code&gt;2.3 MB&lt;&#x2F;code&gt; to &lt;code&gt;3.0 MB&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;loadgen-rs&lt;&#x2F;code&gt;: around &lt;code&gt;15 MB&lt;&#x2F;code&gt; to &lt;code&gt;39 MB&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The higher memory usage comes from Tokio’s runtime overhead, per-worker HdrHistograms, and Rust’s buffered I&#x2F;O layers. In practice, even 39 MB is negligible for a benchmark client.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;which-tool-should-you-choose&quot;&gt;Which Tool Should You Choose?&lt;a class=&quot;zola-anchor&quot; href=&quot;#which-tool-should-you-choose&quot; aria-label=&quot;Anchor link for: which-tool-should-you-choose&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;decision-matrix-by-use-case&quot;&gt;Decision Matrix by Use Case&lt;a class=&quot;zola-anchor&quot; href=&quot;#decision-matrix-by-use-case&quot; aria-label=&quot;Anchor link for: decision-matrix-by-use-case&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Priority&lt;&#x2F;th&gt;&lt;th&gt;Recommendation&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;H1&#x2F;H2 throughput + automation&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;loadgen-rs&lt;&#x2F;strong&gt; — matches or beats h2load, with native JSONL output&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;H3 peak throughput&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;h2load&lt;&#x2F;strong&gt; — ngtcp2 is ~25% faster than quinn&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Lowest memory footprint&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;h2load&lt;&#x2F;strong&gt; — 3 MB vs 15-39 MB&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Cross-protocol consistency&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;loadgen-rs&lt;&#x2F;strong&gt; — same tool, same output format for H1&#x2F;H2&#x2F;H3&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Multi-machine load generation&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;loadgen-rs&lt;&#x2F;strong&gt; — built-in distributed mode with correct histogram merge&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Cloud-provisioned benchmarks&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;loadgen-rs&lt;&#x2F;strong&gt; — Terraform + Ansible pipeline for Hetzner Cloud&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Distributed scripted scenarios&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;loadgen-rs&lt;&#x2F;strong&gt; — workers run multi-step checks&#x2F;extractors via &lt;code&gt;POST &#x2F;run-script&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;practical-recommendation&quot;&gt;Practical Recommendation&lt;a class=&quot;zola-anchor&quot; href=&quot;#practical-recommendation&quot; aria-label=&quot;Anchor link for: practical-recommendation&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;For most benchmarking workflows, the choice comes down to this: do you need the absolute highest H3 throughput, or do you want a single binary with all protocols available out of the box?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;distributed-mode-overview&quot;&gt;Distributed Mode Overview&lt;a class=&quot;zola-anchor&quot; href=&quot;#distributed-mode-overview&quot; aria-label=&quot;Anchor link for: distributed-mode-overview&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;A single loadgen-rs process can saturate most test targets, but sometimes you need
more — testing a load balancer with geographically distributed traffic, generating
more requests than one machine’s NIC can handle, or simply simulating a more
realistic multi-source access pattern.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;architecture&quot;&gt;Architecture&lt;a class=&quot;zola-anchor&quot; href=&quot;#architecture&quot; aria-label=&quot;Anchor link for: architecture&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The distributed mode uses a TypeScript controller (running in Deno) that
orchestrates multiple worker-agents over plain HTTP&#x2F;JSON — no gRPC or custom
protocol needed:&lt;&#x2F;p&gt;
&lt;figure class=&quot;mt-4&quot;&gt;
  &lt;img
    class=&quot;block image-shortcode mx-auto&quot;
    src=&quot;https:&#x2F;&#x2F;blog.none.at&#x2F;blog&#x2F;2026&#x2F;2026-03-01-loadgen-rs&#x2F;architecture.png&quot;
    alt=&quot;Distributed architecture: controller and workers with report merge&quot;
    style=&quot;display:block; width:100%; max-width:700px; height:auto; margin:0 auto;&quot;
    loading=&quot;lazy&quot;
    decoding=&quot;async&quot;
  &gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;Each worker is a lightweight Deno HTTP server (~120 LOC) that wraps the same
&lt;code&gt;LoadgenFFI&lt;&#x2F;code&gt; batch API used for single-node benchmarks. The controller splits
the total client count (&lt;code&gt;-c&lt;&#x2F;code&gt;) evenly across workers, sets a coordinated start
timestamp, and fires all jobs concurrently.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-histogram-merge-problem&quot;&gt;The histogram merge problem&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-histogram-merge-problem&quot; aria-label=&quot;Anchor link for: the-histogram-merge-problem&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Merging latency results from distributed workers is deceptively tricky. The
naive approach — averaging p99 values from each worker — is &lt;strong&gt;statistically
wrong&lt;&#x2F;strong&gt;. If worker A has p99 = 5ms (from 50K requests) and worker B has
p99 = 15ms (from 50K requests), the combined p99 is &lt;em&gt;not&lt;&#x2F;em&gt; 10ms. It depends
on the full distribution shape.&lt;&#x2F;p&gt;
&lt;p&gt;The correct approach is to merge the underlying histograms. Each worker
serializes its HdrHistogram using V2-Deflate format (a compact binary encoding,
typically 1–5 KB per histogram), base64-encodes it, and includes it in the
&lt;code&gt;RunReport&lt;&#x2F;code&gt; JSON:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 136949.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;latency_p99_us&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 210&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;latency_hist_b64&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;HISTFAAAB...&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;ttfb_hist_b64&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;HISTFAAAB...&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;connect_hist_b64&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type z-property-name z-json z-support z-type z-property-name&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;HISTFAAAB...&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The controller passes all worker reports to &lt;code&gt;loadgen_merge_reports()&lt;&#x2F;code&gt; — a new
FFI export that deserializes each histogram, merges them with
&lt;code&gt;Histogram::add()&lt;&#x2F;code&gt;, and recomputes all percentile fields from the combined
distribution. This is the same merge algorithm used internally for per-thread
metrics within a single process.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;coordinated-start&quot;&gt;Coordinated start&lt;a class=&quot;zola-anchor&quot; href=&quot;#coordinated-start&quot; aria-label=&quot;Anchor link for: coordinated-start&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;For benchmark results to be meaningful, all workers should start at the same
time. The controller calculates &lt;code&gt;start_at = now + 500ms&lt;&#x2F;code&gt;, includes it in each
worker’s &lt;code&gt;&#x2F;run&lt;&#x2F;code&gt; request, and each worker sleeps until that timestamp before
starting the benchmark. Clock skew under 10ms is acceptable for benchmark
purposes — and on a local network or the same machine, skew is typically
sub-millisecond.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;minimal-new-code-maximum-reuse&quot;&gt;Minimal new code, maximum reuse&lt;a class=&quot;zola-anchor&quot; href=&quot;#minimal-new-code-maximum-reuse&quot; aria-label=&quot;Anchor link for: minimal-new-code-maximum-reuse&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The implementation reuses the existing FFI infrastructure almost entirely:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Component&lt;&#x2F;th&gt;&lt;th&gt;New code&lt;&#x2F;th&gt;&lt;th&gt;Reused&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Rust: &lt;code&gt;merge_distributed_reports()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;~120 LOC&lt;&#x2F;td&gt;&lt;td&gt;Existing &lt;code&gt;merge_metrics()&lt;&#x2F;code&gt; pattern&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Rust: FFI export&lt;&#x2F;td&gt;&lt;td&gt;~25 LOC&lt;&#x2F;td&gt;&lt;td&gt;Existing &lt;code&gt;catch_unwind&lt;&#x2F;code&gt; + error slot pattern&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;TypeScript: worker-agent&lt;&#x2F;td&gt;&lt;td&gt;~120 LOC&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;LoadgenFFI&lt;&#x2F;code&gt; class&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;TypeScript: Controller&lt;&#x2F;td&gt;&lt;td&gt;~150 LOC&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;mergeReports()&lt;&#x2F;code&gt; FFI wrapper&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Only two new Rust dependencies were needed: &lt;code&gt;base64&lt;&#x2F;code&gt; (for histogram
serialization) was already an indirect dependency via reqwest. The
&lt;code&gt;export_histograms&lt;&#x2F;code&gt; field in &lt;code&gt;BenchConfig&lt;&#x2F;code&gt; defaults to &lt;code&gt;false&lt;&#x2F;code&gt;, so the existing
CLI and single-node FFI path are completely unaffected.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;real-world-validation&quot;&gt;Real-world validation&lt;a class=&quot;zola-anchor&quot; href=&quot;#real-world-validation&quot; aria-label=&quot;Anchor link for: real-world-validation&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Testing 2 local workers against the same target (H2, 8 total clients, 5s):&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Setup&lt;&#x2F;th&gt;&lt;th&gt;RPS&lt;&#x2F;th&gt;&lt;th&gt;p99 Latency&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Single-node (&lt;code&gt;-c 8 -m 4&lt;&#x2F;code&gt;)&lt;&#x2F;td&gt;&lt;td&gt;284,226&lt;&#x2F;td&gt;&lt;td&gt;185 us&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Distributed (2 × 4 clients)&lt;&#x2F;td&gt;&lt;td&gt;269,351&lt;&#x2F;td&gt;&lt;td&gt;207 us&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Ratio&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;94.8%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The ~5% overhead comes from the HTTP&#x2F;JSON coordination round-trip and the
coordinated start delay. In a real multi-machine deployment (where each worker
has its own NIC and CPU), total throughput scales linearly — you’re no longer
bound by a single machine’s resources.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;scripted-scenarios-overview-deno-k6-style&quot;&gt;Scripted Scenarios Overview (Deno &#x2F; k6-style)&lt;a class=&quot;zola-anchor&quot; href=&quot;#scripted-scenarios-overview-deno-k6-style&quot; aria-label=&quot;Anchor link for: scripted-scenarios-overview-deno-k6-style&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;After the initial release, I added a TypeScript&#x2F;Deno layer on top of the native FFI to make scripted testing and k6-style output easier to compare.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-was-added&quot;&gt;What was added&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-was-added&quot; aria-label=&quot;Anchor link for: what-was-added&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;A typed Deno SDK in &lt;code&gt;ts&#x2F;&lt;&#x2F;code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LoadgenFFI&lt;&#x2F;code&gt; for run-level benchmarking&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;LoadgenStepFFI&lt;&#x2F;code&gt; for step&#x2F;session execution&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;A reusable &lt;code&gt;printK6LikeSummary()&lt;&#x2F;code&gt; formatter used by the Deno examples&lt;&#x2F;li&gt;
&lt;li&gt;New comparison examples:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;examples&#x2F;ab-compare.ts&lt;&#x2F;code&gt; and &lt;code&gt;examples&#x2F;k6&#x2F;ab-compare.ts&lt;&#x2F;code&gt; (closed model)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;examples&#x2F;ab-compare-rps.ts&lt;&#x2F;code&gt; and &lt;code&gt;examples&#x2F;k6&#x2F;ab-compare-rps.ts&lt;&#x2F;code&gt; (fixed RPS)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;A one-command comparison helper:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;scripts&#x2F;ab_compare_report.sh&lt;&#x2F;code&gt; (prints req&#x2F;s, avg, p99, fail rate, H2 check)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;important-fairness-fix&quot;&gt;Important fairness fix&lt;a class=&quot;zola-anchor&quot; href=&quot;#important-fairness-fix&quot; aria-label=&quot;Anchor link for: important-fairness-fix&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;One easy mistake is RPS interpretation:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;In loadgen-rs, &lt;code&gt;--rps&lt;&#x2F;code&gt; is &lt;strong&gt;per client&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Aggregate target is &lt;code&gt;rps_per_client * clients&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The Deno comparison script now maps a total target RPS to per-client RPS
automatically (&lt;code&gt;RPS &#x2F; CLIENTS&lt;&#x2F;code&gt;) to match k6 constant-arrival tests.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-latency-can-differ-even-at-similar-req-s&quot;&gt;Why latency can differ even at similar req&#x2F;s&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-latency-can-differ-even-at-similar-req-s&quot; aria-label=&quot;Anchor link for: why-latency-can-differ-even-at-similar-req-s&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Even when request rate is aligned, latency can diverge if concurrency shape is
not aligned:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;number of active clients&#x2F;VUs&lt;&#x2F;li&gt;
&lt;li&gt;streams per connection (&lt;code&gt;-m&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;max_streams&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;thread count and connection reuse behavior&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In short: equal target RPS does not automatically imply equal queueing behavior.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;stage-profile-support-k6-like&quot;&gt;Stage profile support (k6-like)&lt;a class=&quot;zola-anchor&quot; href=&quot;#stage-profile-support-k6-like&quot; aria-label=&quot;Anchor link for: stage-profile-support-k6-like&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I also added &lt;code&gt;examples&#x2F;ab-stages.ts&lt;&#x2F;code&gt;, which emulates stage ramps by splitting
stages into short segments and running sequential FFI runs. This gives a
practical equivalent for basic k6 stage workflows.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;correlation-path&quot;&gt;Correlation path&lt;a class=&quot;zola-anchor&quot; href=&quot;#correlation-path&quot; aria-label=&quot;Anchor link for: correlation-path&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;For dynamic data and sequence scenarios, &lt;code&gt;runScriptScenario()&lt;&#x2F;code&gt; supports:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;execution_mode: &quot;fetch&quot;&lt;&#x2F;code&gt; (pure Deno)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;execution_mode: &quot;ffi-step&quot;&lt;&#x2F;code&gt; (native step transport)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The step API design and contract are documented in:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docs&#x2F;script-mode-ffi.md&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;docs&#x2F;ffi-step-api.md&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;docs&#x2F;ffi-contract.md&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;k6-parity-snapshot-as-of-march-1-2026&quot;&gt;k6 Parity Snapshot (as of March 1, 2026)&lt;a class=&quot;zola-anchor&quot; href=&quot;#k6-parity-snapshot-as-of-march-1-2026&quot; aria-label=&quot;Anchor link for: k6-parity-snapshot-as-of-march-1-2026&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Short answer: Deno + loadgen-rs is now close to k6 for HTTP-focused scripted
workflows, but it is not yet a full 1:1 replacement.&lt;&#x2F;p&gt;
&lt;p&gt;What is now very similar:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Scripted scenarios with step chains, checks, and extractors&lt;&#x2F;li&gt;
&lt;li&gt;Correlation and dynamic data (&lt;code&gt;{{var}}&lt;&#x2F;code&gt; templates, JSON&#x2F;regex&#x2F;header&#x2F;DOM extract)&lt;&#x2F;li&gt;
&lt;li&gt;Cookie handling and redirect policies&lt;&#x2F;li&gt;
&lt;li&gt;Two execution paths: pure &lt;code&gt;fetch&lt;&#x2F;code&gt; and native &lt;code&gt;ffi-step&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Multi-protocol support in native step mode (&lt;code&gt;h1&lt;&#x2F;code&gt;, &lt;code&gt;h2&lt;&#x2F;code&gt;, &lt;code&gt;h3&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;k6-like terminal summary output and side-by-side comparison scripts&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Where k6 is still stronger:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;More mature executor&#x2F;scenario system overall (arrival&#x2F;vu&#x2F;stage&#x2F;ramping breadth)&lt;&#x2F;li&gt;
&lt;li&gt;Larger built-in ecosystem (&lt;code&gt;k6&#x2F;http&lt;&#x2F;code&gt;, ws&#x2F;browser modules, extension landscape)&lt;&#x2F;li&gt;
&lt;li&gt;Richer output&#x2F;integration stack (cloud and common observability backends)&lt;&#x2F;li&gt;
&lt;li&gt;Longer production track record for large shared test suites and CI pipelines&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Practical takeaway:&lt;&#x2F;p&gt;
&lt;p&gt;If your priority is HTTP load + correlation + native H1&#x2F;H2&#x2F;H3 coverage with
comparable CLI summaries, Deno + loadgen-rs is already highly usable. If you
need the full k6 orchestration and ecosystem depth, k6 still has an edge.&lt;&#x2F;p&gt;
&lt;p&gt;The distributed mode isn’t limited to raw throughput benchmarks. Workers also support &lt;code&gt;POST &#x2F;run-script&lt;&#x2F;code&gt; — the same scripted scenario engine used locally, but executed remotely on each worker. This means you can run multi-step request sequences with checks, extractors, cookies, and response validation across a fleet of machines.&lt;&#x2F;p&gt;
&lt;p&gt;The implementation required minimal changes: the worker-agent imports &lt;code&gt;runScriptScenario()&lt;&#x2F;code&gt; from the existing &lt;code&gt;script_mode.ts&lt;&#x2F;code&gt; and exposes it as a new HTTP endpoint. The controller sends a &lt;code&gt;ScriptScenarioConfig&lt;&#x2F;code&gt; (same type used for local script mode) to each worker and collects &lt;code&gt;ScriptRunResult&lt;&#x2F;code&gt; objects with per-check pass&#x2F;fail counters.&lt;&#x2F;p&gt;
&lt;p&gt;A practical example ships as &lt;code&gt;examples&#x2F;html-check.ts&lt;&#x2F;code&gt; — a verification script that tests whether each worker can reach a target URL and parse the response correctly:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-storage&quot;&gt;const&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-constant z-variable&quot;&gt; config&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; ScriptScenarioConfig&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;  vus&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;  duration_s&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 5&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;  execution_mode&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ffi-step&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;  step_session_config&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    protocol&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;h2&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    insecure&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    response_headers&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    response_body_limit&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 2_000_000&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;  steps&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    name&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;html-get&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    method&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    url&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt; targetUrl&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    capture_body&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    checks&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      status_in&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt;200&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 403&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      body_includes&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;lt;title&amp;gt;Example&amp;lt;&#x2F;title&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      header_includes&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;set-cookie&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;session=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; }&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    }&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The controller sends this config to each worker via &lt;code&gt;POST &#x2F;run-script&lt;&#x2F;code&gt;, then aggregates the results into a per-worker table. If the target returns both the HTML element and the cookie on every single response, the iteration count, HTML match count, and header match count should be identical — any mismatch indicates a parsing or check evaluation bug:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;=== per-worker results ===&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  worker                                  iterations  html_match  header_match  ok?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-------------------------------------------------------------------------------------&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  http:&#x2F;&#x2F;worker1:9091                             62          62            62  PASS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  http:&#x2F;&#x2F;worker2:9091                             47          47            47  PASS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-------------------------------------------------------------------------------------&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  TOTAL                                          109         109           109  PASS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;All counts match perfectly across both workers. This validates the full chain: Terraform-provisioned infrastructure, Ansible-deployed workers, &lt;code&gt;ffi-step&lt;&#x2F;code&gt; execution mode with reqwest on the Rust side, response header capture, and the check evaluation engine — all working end-to-end on remote machines.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;header_includes&lt;&#x2F;code&gt; check type was added specifically for this use case. Unlike &lt;code&gt;header_exists&lt;&#x2F;code&gt; (which only checks presence) or &lt;code&gt;header_equals&lt;&#x2F;code&gt; (which requires an exact match), &lt;code&gt;header_includes&lt;&#x2F;code&gt; tests whether a header value &lt;em&gt;contains&lt;&#x2F;em&gt; a substring — essential for &lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; headers where the full value includes expiry, path, and other attributes beyond the cookie name.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;multi-step-login-logout-workflow&quot;&gt;Multi-Step Login&#x2F;Logout Workflow&lt;a class=&quot;zola-anchor&quot; href=&quot;#multi-step-login-logout-workflow&quot; aria-label=&quot;Anchor link for: multi-step-login-logout-workflow&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;The scripted scenario engine really shines when steps depend on each other. A second distributed example — &lt;code&gt;examples&#x2F;roundcube-login.ts&lt;&#x2F;code&gt; — verifies a full Roundcube Webmail login&#x2F;logout cycle across workers. Unlike the single-step HTML check, this scenario chains three HTTP requests with state flowing between them:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;steps&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    name&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;login-page&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    method&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    url&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;mail.example.com&#x2F;mail&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    capture_body&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    checks&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      status_in&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt;200&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      body_includes&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Roundcube Webmail :: Welcome to Roundcube Webmail&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    extract&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      type&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;dom&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      selector&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;input[name=&amp;quot;_token&amp;quot;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      attribute&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;value&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      as&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;token&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    }&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    name&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;login-submit&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    method&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    url&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;mail.example.com&#x2F;mail&#x2F;?_task=login&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    headers&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;content-type&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;application&#x2F;x-www-form-urlencoded&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    body&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;_task=login&amp;amp;_action=login&amp;amp;_timezone=...&amp;amp;_token={{token}}&amp;amp;_user=...&amp;amp;_pass=...&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    capture_body&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    checks&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      status_in&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt;200&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      body_includes&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Roundcube Webmail :: Inbox&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;data-label-msg=&amp;quot;The list is empty.&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    extract&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      type&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;regex&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      pattern&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;request_token&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      group&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      as&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;logout_token&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    }&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    name&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;logout&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    method&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    url&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;mail.example.com&#x2F;mail&#x2F;?_task=logout&amp;amp;_token={{logout_token}}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    capture_body&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    checks&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      status_in&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt;200&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;      body_includes&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Roundcube Webmail :: Welcome to Roundcube Webmail&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-object z-member&quot;&gt;    }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Each step depends on extracted values from the previous one: &lt;code&gt;{{token}}&lt;&#x2F;code&gt; (the CSRF token extracted from the login form’s hidden input via a DOM CSS selector — &lt;code&gt;input[name=&quot;_token&quot;]&lt;&#x2F;code&gt;) flows into the POST body, and &lt;code&gt;{{logout_token}}&lt;&#x2F;code&gt; (the &lt;code&gt;request_token&lt;&#x2F;code&gt; extracted from the Roundcube JavaScript via regex) flows into the logout URL. The &lt;code&gt;dom&lt;&#x2F;code&gt; extractor parses the HTML response with deno-dom’s &lt;code&gt;DOMParser&lt;&#x2F;code&gt; and runs &lt;code&gt;querySelector&lt;&#x2F;code&gt; — far more robust than regex for structured HTML. Session cookies are managed automatically by the native &lt;code&gt;cookie_jar&lt;&#x2F;code&gt; — the &lt;code&gt;roundcube_sessid&lt;&#x2F;code&gt; and &lt;code&gt;roundcube_sessauth&lt;&#x2F;code&gt; cookies persist across all three steps within an iteration, and Roundcube’s logout response invalidates the session so the next iteration starts clean.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;=== per-worker results ===&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  worker                                  iterations    login_ok   logout_ok  ok?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;----------------------------------------------------------------------------------&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  http:&#x2F;&#x2F;worker1:9091                      45          45          45  PASS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  http:&#x2F;&#x2F;worker2:9091                      45          45          45  PASS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;----------------------------------------------------------------------------------&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  TOTAL                                    90          90          90  PASS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;All 90 iterations completed successfully across both workers: every login extracted a valid CSRF token, every POST received the inbox page, and every logout returned to the login screen. This validates the full extractor → template substitution → cookie management pipeline running inside the Rust FFI layer on remote machines.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;deep-dive-performance-analysis&quot;&gt;Deep Dive: Performance Analysis&lt;a class=&quot;zola-anchor&quot; href=&quot;#deep-dive-performance-analysis&quot; aria-label=&quot;Anchor link for: deep-dive-performance-analysis&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;To understand the performance gaps, I profiled loadgen-rs using &lt;code&gt;perf&lt;&#x2F;code&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mstange&#x2F;samply&quot;&gt;samply&lt;&#x2F;a&gt;. The setup:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Build with debug symbols but full optimization&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;cargo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; build&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-profile&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; profiling&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; inherits release, adds debug=2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Record CPU profile&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;perf&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; record&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;g&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-call-graph&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; dwarf&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;o&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &#x2F;tmp&#x2F;perf-h2.data&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  .&#x2F;target&#x2F;profiling&#x2F;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; h2&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 64&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-insecure&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; https:&#x2F;&#x2F;127.0.0.1:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;h2-cpu-profile&quot;&gt;H2 CPU Profile&lt;a class=&quot;zola-anchor&quot; href=&quot;#h2-cpu-profile&quot; aria-label=&quot;Anchor link for: h2-cpu-profile&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Self %&lt;&#x2F;th&gt;&lt;th&gt;Function&lt;&#x2F;th&gt;&lt;th&gt;Category&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;27%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;ring::aes_gcm_dec_update&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;TLS decrypt (AES-256-GCM)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;21%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;__memmove_avx&lt;&#x2F;code&gt; (libc)&lt;&#x2F;td&gt;&lt;td&gt;Memory copies&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;16%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Kernel&lt;&#x2F;td&gt;&lt;td&gt;TCP read&#x2F;write syscalls&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2.0%&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;rustls::read_opaque_message_header&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;TLS framing&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1.4%&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;_mi_page_malloc&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Allocator (mimalloc)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1.3%&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;h2::Connection::poll2&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;H2 protocol state machine&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1.3%&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;h2::DynConnection::recv_frame&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;H2 frame processing&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1.2%&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;BytesMut::split_to&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Buffer management&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;strong&gt;~64% of CPU time is spent outside of our code entirely&lt;&#x2F;strong&gt; — in TLS decryption, memory copies, and kernel syscalls. Only ~2.5% is in the h2 crate’s protocol logic.&lt;&#x2F;p&gt;
&lt;p&gt;The H2 latency gap (1.8x vs h2load) is &lt;em&gt;not&lt;&#x2F;em&gt; a CPU bottleneck — it’s architectural. The h2 crate uses an async mpsc channel between &lt;code&gt;SendRequest&lt;&#x2F;code&gt; (our task) and the connection driver task (h2’s internal I&#x2F;O loop). Each request crosses this channel boundary at least twice (send + receive), adding idle time that doesn’t show up in CPU profiles.&lt;&#x2F;p&gt;
&lt;p&gt;h2load, by contrast, runs everything in a single libev event loop tick — nghttp2 processes the request, writes the frame, and reads the response without any task switching.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;h3-cpu-profile&quot;&gt;H3 CPU Profile&lt;a class=&quot;zola-anchor&quot; href=&quot;#h3-cpu-profile&quot; aria-label=&quot;Anchor link for: h3-cpu-profile&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Self %&lt;&#x2F;th&gt;&lt;th&gt;Function&lt;&#x2F;th&gt;&lt;th&gt;Category&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;15%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;ring::aes_gcm_dec_update&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;TLS decrypt&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;13%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;__memmove_avx&lt;&#x2F;code&gt; (libc)&lt;&#x2F;td&gt;&lt;td&gt;Memory copies&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;12%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Kernel&lt;&#x2F;td&gt;&lt;td&gt;UDP recvmsg syscalls&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;3.5%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;quinn_proto::Connection::process_payload&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;QUIC packet processing&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;3.1%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;quinn_proto::Connection::handle_decode&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;QUIC packet decode&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;2.9%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;quinn::endpoint::RecvState::poll_socket&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;UDP socket I&#x2F;O&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;2.8%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;binary_heap::PeekMut::pop&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Timer&#x2F;scheduling (loss detection, CC)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1.8%&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;ring::aes_gcm::open&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Additional TLS overhead&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1.7%&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;quinn::RecvStream::poll_read_chunk&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Stream receive&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1.7%&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;quinn_proto::Endpoint::handle&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Packet routing&#x2F;demux&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1.6%&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;h3::proto::varint::VarInt::decode&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;H3 framing&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The QUIC protocol stack (quinn-proto) accounts for &lt;strong&gt;~15% of CPU&lt;&#x2F;strong&gt; — compared to ~2.5% for h2. This is the fundamental cost of implementing QUIC in userspace Rust:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per-packet crypto&lt;&#x2F;strong&gt;: QUIC encrypts each UDP datagram individually (vs TLS record batching over TCP)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Congestion control state machine&lt;&#x2F;strong&gt;: Quinn maintains timer heaps (&lt;code&gt;binary_heap::pop&lt;&#x2F;code&gt; at 2.8%) for loss detection and CC that TCP offloads to the kernel&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Packet demux&lt;&#x2F;strong&gt;: &lt;code&gt;Endpoint::handle&lt;&#x2F;code&gt; routes each incoming UDP datagram to the correct connection — work that the kernel’s TCP stack does for free&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;h2load uses ngtcp2 (C) and nghttp3 (C) which have tighter inner loops, less allocation overhead, and direct &lt;code&gt;writev()&lt;&#x2F;code&gt; for packet batching.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-the-profile-means&quot;&gt;What the Profile Means&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-the-profile-means&quot; aria-label=&quot;Anchor link for: what-the-profile-means&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The profiling data tells a clear story: &lt;strong&gt;we’ve hit the performance ceiling of the Rust crate ecosystem&lt;&#x2F;strong&gt;, not a bug in our code.&lt;&#x2F;p&gt;
&lt;p&gt;For H2, the bottleneck is TLS + memcpy + kernel — things we can’t optimize without replacing ring&#x2F;rustls. For H3, quinn’s QUIC protocol processing adds ~15% overhead that ngtcp2 avoids by being a leaner C implementation.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;optimization-attempts-what-worked-and-what-didn-t&quot;&gt;Optimization Attempts (What Worked and What Didn’t)&lt;a class=&quot;zola-anchor&quot; href=&quot;#optimization-attempts-what-worked-and-what-didn-t&quot; aria-label=&quot;Anchor link for: optimization-attempts-what-worked-and-what-didn-t&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;During development, I tried several optimizations to close the gap with h2load:&lt;&#x2F;p&gt;
&lt;h4 id=&quot;what-worked&quot;&gt;What Worked&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-worked&quot; aria-label=&quot;Anchor link for: what-worked&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Optimization&lt;&#x2F;th&gt;&lt;th&gt;Effect&lt;&#x2F;th&gt;&lt;th&gt;Protocol&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Raw h2 crate instead of hyper&lt;&#x2F;td&gt;&lt;td&gt;Eliminated one abstraction layer&lt;&#x2F;td&gt;&lt;td&gt;H2&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Raw TCP + httparse instead of hyper for H1&lt;&#x2F;td&gt;&lt;td&gt;+15-25% RPS vs hyper-based H1&lt;&#x2F;td&gt;&lt;td&gt;H1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;initial_rtt(1ms)&lt;&#x2F;code&gt; for quinn&lt;&#x2F;td&gt;&lt;td&gt;Faster congestion control ramp-up on low-latency links&lt;&#x2F;td&gt;&lt;td&gt;H3&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1 GB stream&#x2F;connection windows&lt;&#x2F;td&gt;&lt;td&gt;Matches h2load’s window sizes&lt;&#x2F;td&gt;&lt;td&gt;H2, H3&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;H2 flow control: 512 KB batch release&lt;&#x2F;td&gt;&lt;td&gt;Reduces WINDOW_UPDATE frame overhead&lt;&#x2F;td&gt;&lt;td&gt;H2&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;UDP socket buffers 4 MB via socket2&lt;&#x2F;td&gt;&lt;td&gt;Fewer kernel drops under load&lt;&#x2F;td&gt;&lt;td&gt;H3&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;mimalloc global allocator&lt;&#x2F;td&gt;&lt;td&gt;Better multi-threaded malloc performance&lt;&#x2F;td&gt;&lt;td&gt;All&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Pre-built request templates&lt;&#x2F;td&gt;&lt;td&gt;Avoid re-building HTTP requests per iteration&lt;&#x2F;td&gt;&lt;td&gt;All&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Per-worker current-thread runtimes&lt;&#x2F;td&gt;&lt;td&gt;No cross-thread lock contention&lt;&#x2F;td&gt;&lt;td&gt;All&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h4 id=&quot;what-didn-t-work&quot;&gt;What Didn’t Work&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-didn-t-work&quot; aria-label=&quot;Anchor link for: what-didn-t-work&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Optimization&lt;&#x2F;th&gt;&lt;th&gt;Expected&lt;&#x2F;th&gt;&lt;th&gt;Actual&lt;&#x2F;th&gt;&lt;th&gt;Why&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Thread-local shared quinn::Endpoint&lt;&#x2F;td&gt;&lt;td&gt;Less per-connection overhead&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;-12% RPS&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;UDP socket contention — all connections on one worker competed for the same socket&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;H2 immediate flow control release (per-chunk)&lt;&#x2F;td&gt;&lt;td&gt;Lower latency&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;-5.6% RPS&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Generated too many WINDOW_UPDATE frames and cross-task messages&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Cubic congestion controller (instead of NewReno)&lt;&#x2F;td&gt;&lt;td&gt;Match h2load’s ngtcp2 CC&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;-7% RPS&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Quinn’s Cubic implementation doesn’t scale well with 512 concurrent connections on localhost&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;http2 crate (0x676e67 fork with parking_lot)&lt;&#x2F;td&gt;&lt;td&gt;Faster mutex in h2 hot path&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;-2.3% RPS&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;The h2 protocol logic is only 2.5% of CPU — faster locks don’t help when locks aren’t the bottleneck&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The failed optimizations were valuable lessons: profiling &lt;em&gt;before&lt;&#x2F;em&gt; optimizing would have saved time. The http2 fork experiment, for instance, was motivated by the assumption that h2’s internal locking was a bottleneck — but perf showed it was only 1.3% of CPU time.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;architectural-comparison-loadgen-rs-vs-h2load&quot;&gt;Architectural Comparison: loadgen-rs vs h2load&lt;a class=&quot;zola-anchor&quot; href=&quot;#architectural-comparison-loadgen-rs-vs-h2load&quot; aria-label=&quot;Anchor link for: architectural-comparison-loadgen-rs-vs-h2load&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Understanding &lt;em&gt;why&lt;&#x2F;em&gt; the gaps exist requires comparing the architectures:&lt;&#x2F;p&gt;
&lt;h4 id=&quot;h2-async-tasks-vs-single-event-loop&quot;&gt;H2: Async Tasks vs Single Event Loop&lt;a class=&quot;zola-anchor&quot; href=&quot;#h2-async-tasks-vs-single-event-loop&quot; aria-label=&quot;Anchor link for: h2-async-tasks-vs-single-event-loop&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loadgen-rs:  Worker Task ──mpsc──▶ h2 Connection Driver Task ──▶ TCP&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;h2load:      libev event loop ──▶ nghttp2 ──▶ writev() ──▶ TCP&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The h2 crate splits request handling across two async tasks connected by an mpsc channel. Each request crosses this boundary twice. h2load runs everything synchronously in one event loop tick — zero context switches per request.&lt;&#x2F;p&gt;
&lt;p&gt;This explains the latency gap (1.8x) without a throughput gap: the pipeline stays full, but each individual request takes longer to complete.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;h3-userspace-quic-vs-lean-c-implementation&quot;&gt;H3: Userspace QUIC vs Lean C Implementation&lt;a class=&quot;zola-anchor&quot; href=&quot;#h3-userspace-quic-vs-lean-c-implementation&quot; aria-label=&quot;Anchor link for: h3-userspace-quic-vs-lean-c-implementation&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loadgen-rs:  quinn (Rust) ──▶ ring (Rust&#x2F;asm) ──▶ UDP socket&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;h2load:      ngtcp2 (C) ──▶ OpenSSL&#x2F;BoringSSL (C&#x2F;asm) ──▶ UDP socket&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Both implement the same QUIC protocol, but ngtcp2 has advantages:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Tighter C inner loops with less allocation&lt;&#x2F;li&gt;
&lt;li&gt;Direct &lt;code&gt;sendmsg&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;recvmsg&lt;&#x2F;code&gt; batching&lt;&#x2F;li&gt;
&lt;li&gt;Fewer abstraction layers between protocol logic and I&#x2F;O&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Quinn adds Tokio task scheduling, channel-based I&#x2F;O between endpoint and connection state, and Rust’s safety overhead (bounds checks, reference counting on &lt;code&gt;Bytes&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;deep-dive-internal-architecture&quot;&gt;Deep Dive: Internal Architecture&lt;a class=&quot;zola-anchor&quot; href=&quot;#deep-dive-internal-architecture&quot; aria-label=&quot;Anchor link for: deep-dive-internal-architecture&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;enum-based-driver-dispatch&quot;&gt;Enum-Based Driver Dispatch&lt;a class=&quot;zola-anchor&quot; href=&quot;#enum-based-driver-dispatch&quot; aria-label=&quot;Anchor link for: enum-based-driver-dispatch&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Instead of &lt;code&gt;dyn Trait&lt;&#x2F;code&gt; with async boxing, I use an enum:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-storage&quot;&gt; enum&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Connection&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;    H1Raw&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;h1_raw&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;H1RawConnection&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;    H2&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;h2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;H2Connection&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;    H3&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;h3&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;H3Connection&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Connection&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; async&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; send_request&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt; self&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt; config&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;RequestConfig&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;        -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;RequestResult&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; RequestError&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        match&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt; self&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;            Connection&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;H1Raw&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;c&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt; c&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;send_request&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;config&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;            Connection&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;H2&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;c&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt; c&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;send_request&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;config&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;            Connection&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;H3&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;c&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt; c&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;send_request&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-variable&quot;&gt;config&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Benefits:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;No heap allocation per dispatch&lt;&#x2F;li&gt;
&lt;li&gt;Compiler can inline&lt;&#x2F;li&gt;
&lt;li&gt;No &lt;code&gt;Pin&amp;lt;Box&amp;lt;dyn Future&amp;gt;&amp;gt;&lt;&#x2F;code&gt; overhead&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;ffi-two-api-design-batch-vs-step&quot;&gt;FFI Two-API Design (Batch vs Step)&lt;a class=&quot;zola-anchor&quot; href=&quot;#ffi-two-api-design-batch-vs-step&quot; aria-label=&quot;Anchor link for: ffi-two-api-design-batch-vs-step&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The Deno integration isn’t just a thin wrapper — it’s built on a Cargo workspace
with a dedicated &lt;code&gt;loadgen-ffi&lt;&#x2F;code&gt; crate that compiles to a 6.4 MB shared library
(&lt;code&gt;libloadgen_ffi.so&lt;&#x2F;code&gt;) exposing 13 C ABI functions via &lt;code&gt;Deno.dlopen()&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;two-distinct-ffi-apis&quot;&gt;Two distinct FFI APIs&lt;a class=&quot;zola-anchor&quot; href=&quot;#two-distinct-ffi-apis&quot; aria-label=&quot;Anchor link for: two-distinct-ffi-apis&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;The shared library provides two API levels, each serving a different use case:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;1. Batch API&lt;&#x2F;strong&gt; — for high-throughput load generation:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loadgen_create(config_json) → handle&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loadgen_run(handle)         → report_json    (nonblocking in Deno)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loadgen_metrics_snapshot(handle) → json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loadgen_destroy(handle)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This wraps the existing &lt;code&gt;run_from_config()&lt;&#x2F;code&gt; engine — the same optimized H1&#x2F;H2&#x2F;H3
drivers, per-worker runtimes, and HdrHistograms that match h2load performance.
A single &lt;code&gt;loadgen_run&lt;&#x2F;code&gt; call spawns the full benchmark and returns a complete
&lt;code&gt;RunReport&lt;&#x2F;code&gt; as JSON.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;2. Step API&lt;&#x2F;strong&gt; — for scripted request sequences with correlation:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loadgen_step_session_create(session_config_json) → session_handle&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loadgen_step_execute(session_handle, request_json) → response_json  (nonblocking)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loadgen_step_session_reset(session_handle)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loadgen_step_session_destroy(session_handle)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The Step API uses &lt;strong&gt;reqwest&lt;&#x2F;strong&gt; as its HTTP client rather than the custom drivers.
This is a deliberate architectural choice: reqwest provides built-in cookie jars,
redirect following, H1&#x2F;H2&#x2F;H3 support, and connection pooling — features that the
raw benchmark drivers intentionally omit for performance. In script mode,
correctness (cookies, redirects, body capture) matters more than raw throughput.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;panic-safe-ffi-boundary&quot;&gt;Panic-safe FFI boundary&lt;a class=&quot;zola-anchor&quot; href=&quot;#panic-safe-ffi-boundary&quot; aria-label=&quot;Anchor link for: panic-safe-ffi-boundary&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;Every exported function is wrapped in &lt;code&gt;std::panic::catch_unwind()&lt;&#x2F;code&gt; to prevent
Rust panics from unwinding across the C ABI boundary (which would be undefined
behavior). Errors are stored in a thread-safe slot (&lt;code&gt;OnceLock&amp;lt;Mutex&amp;lt;Option&amp;lt;String&amp;gt;&amp;gt;&amp;gt;&lt;&#x2F;code&gt;)
and retrieved via &lt;code&gt;loadgen_last_error()&lt;&#x2F;code&gt;. All returned &lt;code&gt;char*&lt;&#x2F;code&gt; strings must be
freed with &lt;code&gt;loadgen_free_string()&lt;&#x2F;code&gt; — the contract is documented in
&lt;code&gt;docs&#x2F;ffi-contract.md&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;typescript-script-engine&quot;&gt;TypeScript script engine&lt;a class=&quot;zola-anchor&quot; href=&quot;#typescript-script-engine&quot; aria-label=&quot;Anchor link for: typescript-script-engine&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;On the Deno side, &lt;code&gt;runScriptScenario()&lt;&#x2F;code&gt; in &lt;code&gt;ts&#x2F;script_mode.ts&lt;&#x2F;code&gt; implements a
k6-inspired scenario runner with:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;VU parallelism&lt;&#x2F;strong&gt;: N virtual users run as concurrent &lt;code&gt;Promise.all&lt;&#x2F;code&gt; tasks&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Duration-based execution&lt;&#x2F;strong&gt;: VUs loop until the target duration expires&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Two execution modes&lt;&#x2F;strong&gt;: &lt;code&gt;fetch&lt;&#x2F;code&gt; (Deno’s native HTTP client) and &lt;code&gt;ffi-step&lt;&#x2F;code&gt;
(Rust reqwest via the Step API)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Template engine&lt;&#x2F;strong&gt;: &lt;code&gt;{{variable}}&lt;&#x2F;code&gt; substitution in URLs, headers, and bodies&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Extractor system&lt;&#x2F;strong&gt;: Capture values from JSON paths, response headers,
regex groups, or DOM CSS selectors (via deno-dom) — stored in per-VU state
for use in subsequent steps&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Check system&lt;&#x2F;strong&gt;: Eight check types including &lt;code&gt;status_in&lt;&#x2F;code&gt;, &lt;code&gt;body_includes&lt;&#x2F;code&gt;,
&lt;code&gt;header_exists&lt;&#x2F;code&gt;, &lt;code&gt;header_equals&lt;&#x2F;code&gt;, &lt;code&gt;header_includes&lt;&#x2F;code&gt;, &lt;code&gt;json_path_exists&lt;&#x2F;code&gt;,
&lt;code&gt;json_path_equals&lt;&#x2F;code&gt;, and &lt;code&gt;regex_match&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Cookie jar&lt;&#x2F;strong&gt;: Full RFC-aware implementation in TypeScript (for &lt;code&gt;fetch&lt;&#x2F;code&gt; mode)
with domain&#x2F;path matching, expiry handling, and Secure attribute support&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The &lt;code&gt;ffi-step&lt;&#x2F;code&gt; mode delegates cookie management and redirects to reqwest on the
Rust side, while the &lt;code&gt;fetch&lt;&#x2F;code&gt; mode handles everything in TypeScript. Both modes
share the same check and extractor evaluation logic.&lt;&#x2F;p&gt;
&lt;p&gt;The full scripting documentation is in &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;git001&#x2F;loadgen-rs&#x2F;blob&#x2F;main&#x2F;docs&#x2F;loadgen-script.md&quot;&gt;loadgen-script&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h4 id=&quot;example-coverage&quot;&gt;Example coverage&lt;a class=&quot;zola-anchor&quot; href=&quot;#example-coverage&quot; aria-label=&quot;Anchor link for: example-coverage&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;The &lt;code&gt;examples&#x2F;&lt;&#x2F;code&gt; directory in &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;git001&#x2F;loadgen-rs&#x2F;&quot;&gt;loadgen-rs repository&lt;&#x2F;a&gt; demonstrates the full feature surface:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Example&lt;&#x2F;th&gt;&lt;th&gt;What it demonstrates&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;simple.ts&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Minimal FFI benchmark (10 requests, single client)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;correlation_mvp.ts&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Login, extract JWT token, use in follow-up request&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;cookie_redirect_local_mvp.ts&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Embedded Deno server with Set-Cookie + 302 redirect chain&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;protocol-matrix.ts&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;H1&#x2F;H2&#x2F;H3 batch + script mode for all three protocols&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ab-stages.ts&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Ramping VUs with stage interpolation (k6-like stages)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;native_step_skeleton.ts&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Direct Step API usage without the scenario runner&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;all-functionality.ts&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Full integration test suite using Chai assertions&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The &lt;code&gt;cookie_redirect_local_mvp.ts&lt;&#x2F;code&gt; example is particularly interesting: it spins
up a local Deno HTTP server that sets cookies and issues redirects, then runs the
script scenario against it — a self-contained integration test that validates the
entire cookie + redirect + check pipeline.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;appendix-commands-and-reproducibility&quot;&gt;Appendix: Commands and Reproducibility&lt;a class=&quot;zola-anchor&quot; href=&quot;#appendix-commands-and-reproducibility&quot; aria-label=&quot;Anchor link for: appendix-commands-and-reproducibility&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;equivalent-command-pairs-h2load-vs-loadgen-rs&quot;&gt;Equivalent Command Pairs (h2load vs loadgen-rs)&lt;a class=&quot;zola-anchor&quot; href=&quot;#equivalent-command-pairs-h2load-vs-loadgen-rs&quot; aria-label=&quot;Anchor link for: equivalent-command-pairs-h2load-vs-loadgen-rs&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;To compare both tools fairly, use equivalent flags and remember:
&lt;code&gt;--rps&lt;&#x2F;code&gt; is &lt;strong&gt;per client&lt;&#x2F;strong&gt; in both tools.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 1) H1 count mode (same request budget)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;h2load&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-h1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 200&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-h1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 200&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 2) H1 duration mode with pacing (~200 aggregate rps: 10 clients * 20 rps&#x2F;client)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;h2load&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-h1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-h1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-duration&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 3) H2 duration mode with pacing (same effective target)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;h2load&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list=h2&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; h2&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-duration&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 4) H3 duration mode with pacing (same effective target)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;h2load&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list=h3&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; h3&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-duration&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;m&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rps&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.local:8082&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;cli-compatibility-map&quot;&gt;CLI Compatibility Map&lt;a class=&quot;zola-anchor&quot; href=&quot;#cli-compatibility-map&quot; aria-label=&quot;Anchor link for: cli-compatibility-map&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The table below maps &lt;code&gt;loadgen-rs&lt;&#x2F;code&gt; options to their closest &lt;code&gt;h2load&lt;&#x2F;code&gt; equivalent.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;code&gt;loadgen-rs&lt;&#x2F;code&gt;&lt;&#x2F;th&gt;&lt;th&gt;&lt;code&gt;h2load&lt;&#x2F;code&gt;&lt;&#x2F;th&gt;&lt;th&gt;Mapping&lt;&#x2F;th&gt;&lt;th&gt;Notes&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;URL&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;&amp;lt;URI&amp;gt;...&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;partial&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;h2load&lt;&#x2F;code&gt; accepts multiple URIs&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;-n &amp;lt;REQUESTS&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;-n, --requests=&amp;lt;N&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;same&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;-D, --duration&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;-D, --duration&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;same&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--warm-up-time&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;--warm-up-time&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;same&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--ramp-up-time&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;none&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;no direct equivalent&lt;&#x2F;td&gt;&lt;td&gt;Closest knobs are &lt;code&gt;-r&#x2F;--rate&lt;&#x2F;code&gt; + &lt;code&gt;--rate-period&lt;&#x2F;code&gt;, but semantics differ&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;-c &amp;lt;CLIENTS&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;-c, --clients=&amp;lt;N&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;same&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;-t &amp;lt;THREADS&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;-t, --threads=&amp;lt;N&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;same&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;-m &amp;lt;MAX_STREAMS&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;-m, --max-concurrent-streams=&amp;lt;N&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;same&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--h1&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;--h1&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;same&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--alpn-list h2|h3&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;--alpn-list=&amp;lt;LIST&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;partial&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;h2load&lt;&#x2F;code&gt; allows arbitrary ALPN lists&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--connect-timeout&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;none&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;no direct equivalent&lt;&#x2F;td&gt;&lt;td&gt;Only related connection-level timeout controls exist (&lt;code&gt;-T&lt;&#x2F;code&gt;, &lt;code&gt;-N&lt;&#x2F;code&gt;)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--request-timeout&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;none&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;no direct equivalent&lt;&#x2F;td&gt;&lt;td&gt;No per-request timeout flag in &lt;code&gt;h2load&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--tcp-quickack&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;none&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;none&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--method &amp;lt;METHOD&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;none&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;no direct equivalent&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;h2load&lt;&#x2F;code&gt; is primarily GET; &lt;code&gt;POST&lt;&#x2F;code&gt; is enabled via &lt;code&gt;-d&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;-H, --header&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;-H, --header&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;same&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;-d, --data &amp;lt;DATA&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;-d, --data=&amp;lt;PATH&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;partial&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;loadgen-rs&lt;&#x2F;code&gt;: inline body; &lt;code&gt;h2load&lt;&#x2F;code&gt;: file path&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--data-file &amp;lt;PATH&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;-d, --data=&amp;lt;PATH&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;close equivalent&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--rps &amp;lt;RPS&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;--rps=&amp;lt;N&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;same&lt;&#x2F;td&gt;&lt;td&gt;per-client target in both tools (aggregate &lt;code&gt;RPS * clients&lt;&#x2F;code&gt;)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;-k, --insecure&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;none&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;no CLI equivalent&lt;&#x2F;td&gt;&lt;td&gt;No direct insecure toggle in &lt;code&gt;h2load&lt;&#x2F;code&gt; CLI&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--tls-ciphers&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;--ciphers&lt;&#x2F;code&gt;, &lt;code&gt;--tls13-ciphers&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;partial&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;h2load&lt;&#x2F;code&gt; splits TLS &amp;lt;=1.2 and TLS 1.3 ciphers&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--tls-ca &amp;lt;PATH&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;none&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;none&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;-o, --output &amp;lt;PATH&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;none&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;none&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;h2load&lt;&#x2F;code&gt; offers &lt;code&gt;--log-file&lt;&#x2F;code&gt; (per-request TSV), not run-level JSON&#x2F;CSV&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--format jsonl|csv&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;none&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;none&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--no-human&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;none&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;none&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--tail-friendly&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;none&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;none&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--metrics-sample&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;none&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;none&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;container-baseline&quot;&gt;Container Baseline&lt;a class=&quot;zola-anchor&quot; href=&quot;#container-baseline&quot; aria-label=&quot;Anchor link for: container-baseline&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The multi-stage Containerfile builds the binary in a Rust image and copies it into a minimal Debian Slim image with &lt;code&gt;bash&lt;&#x2F;code&gt; and &lt;code&gt;curl&lt;&#x2F;code&gt;, with HTTP&#x2F;3 support built in:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;podman&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; build&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;f&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; Containerfile&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; .&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;podman&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; run&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-rm&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-network&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; host&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-cap-add=SYS_ADMIN&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-security-opt&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; seccomp=unconfined&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  loadgen-rs&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 10s&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-alpn-list=h2&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; https:&#x2F;&#x2F;target:8443&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;--network host&lt;&#x2F;code&gt; avoids NAT overhead. &lt;code&gt;--cap-add=SYS_ADMIN&lt;&#x2F;code&gt; enables io_uring inside the container.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;test-server-setup&quot;&gt;Test Server Setup&lt;a class=&quot;zola-anchor&quot; href=&quot;#test-server-setup&quot; aria-label=&quot;Anchor link for: test-server-setup&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The test server used in this post is &lt;code&gt;haterm&lt;&#x2F;code&gt; from &lt;code&gt;HAProxy&lt;&#x2F;code&gt;, created and started with the following commands:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# create combined.pem in TLS-PATH&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cat server.crt ca.crt server.key &amp;gt; combined.pem&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# build container image&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;buildah bud -f Containerfile-hap-git -t hap-own&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# run haterm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;podman run -d --rm --name hap-own \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --network host \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --entrypoint=&#x2F;usr&#x2F;local&#x2F;sbin&#x2F;haterm \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -v &amp;lt;TLS-PATH&amp;gt;&#x2F;tls:&#x2F;mnt:ro \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  localhost&#x2F;haterm:latest \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -L 127.0.0.1:8081 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -F &amp;#39;bind quic4@127.0.0.1:8082 ssl crt &#x2F;mnt&#x2F;combined.pem alpn h3&amp;#39; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -F &amp;#39;bind 127.0.0.1:8082 ssl crt &#x2F;mnt&#x2F;combined.pem alpn h2&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The used &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;git001&#x2F;loadgen-rs&#x2F;blob&#x2F;main&#x2F;Containerfile-hap-git&quot;&gt;Containerfile-hap-git&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;cloud-deployment-terraform-ansible&quot;&gt;Cloud Deployment (Terraform + Ansible)&lt;a class=&quot;zola-anchor&quot; href=&quot;#cloud-deployment-terraform-ansible&quot; aria-label=&quot;Anchor link for: cloud-deployment-terraform-ansible&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Running distributed benchmarks on your local machines is useful for development, but real load testing demands dedicated cloud infrastructure — consistent hardware, dedicated NICs, and geographic proximity to the target. loadgen-rs ships with a complete Terraform + Ansible pipeline for Hetzner Cloud that takes you from zero to running workers in under five minutes.&lt;&#x2F;p&gt;
&lt;p&gt;The Terraform configuration provisions dedicated-vCPU instances (CCX series), places them in a private network, and configures a firewall that allows SSH and the worker-agent port. A single &lt;code&gt;generate-inventory.sh&lt;&#x2F;code&gt; script bridges the gap between Terraform and Ansible — it reads the Terraform outputs and writes a ready-to-use &lt;code&gt;ansible&#x2F;inventory.ini&lt;&#x2F;code&gt; with the public IPs:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;terraform apply&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── hcloud_server.worker[0]  →  49.13.x.x  (loadgen-worker-0)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── hcloud_server.worker[1]  →  49.13.y.y  (loadgen-worker-1)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── hcloud_network           →  10.0.1.0&#x2F;24 (private)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└── hcloud_firewall          →  SSH + port 9091&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;▼&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.&#x2F;generate-inventory.sh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;▼&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ansible&#x2F;inventory.ini&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── loadgen-worker-0  ansible_host=49.13.x.x  worker_port=9091&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└── loadgen-worker-1  ansible_host=49.13.y.y  worker_port=9091&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;▼&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ansible-playbook deploy-workers.yml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;▼&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Workers ready — POST &#x2F;run from controller&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The full workflow from provisioning to results:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 1. Provision Hetzner Cloud machines&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-support z-function&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; terraform&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;cp&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; terraform.tfvars.example&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; terraform.tfvars&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Set: hcloud_token, ssh_key_name, worker_count=4, worker_type=ccx33&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;terraform&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; init&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; terraform&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apply&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 2. Generate Ansible inventory from Terraform state&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&#x2F;generate-inventory.sh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; → ansible&#x2F;inventory.ini with 4 workers&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 3. Deploy + benchmark + cleanup in one command&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-support z-function&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; ..&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;scripts&#x2F;distributed-remote.sh&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;i&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; ansible&#x2F;inventory.ini&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-target-url&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;https:&#x2F;&#x2F;bench.target:8443&#x2F;?s=256k&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;  -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 1000&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;D&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-numeric&quot;&gt; 60&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-constant z-other&quot;&gt;-stop-after&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 4. Tear down when done&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-support z-function&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; terraform&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; terraform&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; destroy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The Ansible playbook (&lt;code&gt;deploy-workers.yml&lt;&#x2F;code&gt;) handles everything on the remote machines: installing Deno if not present, copying the FFI shared library and TypeScript files, deploying a systemd service unit (with a tmux fallback for systems without systemd), and running health checks to confirm each worker is ready. The matching &lt;code&gt;stop-workers.yml&lt;&#x2F;code&gt; playbook cleanly shuts everything down.&lt;&#x2F;p&gt;
&lt;p&gt;This separation of concerns — Terraform owns infrastructure, Ansible owns configuration, the shell script orchestrates the workflow — means you can mix and match. Already have machines? Skip Terraform, write &lt;code&gt;inventory.ini&lt;&#x2F;code&gt; by hand. Prefer a different cloud? Replace the &lt;code&gt;.tf&lt;&#x2F;code&gt; files, keep the same Ansible playbooks and scripts. The worker-agent doesn’t care how it got deployed.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Component&lt;&#x2F;th&gt;&lt;th&gt;Responsibility&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;terraform&#x2F;*.tf&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Provision Hetzner VMs, network, firewall&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;terraform&#x2F;generate-inventory.sh&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Bridge: Terraform outputs → Ansible inventory&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ansible&#x2F;deploy-workers.yml&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Install Deno, deploy files, start workers&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ansible&#x2F;stop-workers.yml&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Stop workers (systemd or tmux)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;scripts&#x2F;distributed-remote.sh&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;End-to-end: deploy → benchmark → stop&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;loadgen-rs is a lean, focused tool: it performs exactly one run, outputs the results as JSONL, and leaves the orchestration (warmup, parameter sweeps, plotting) to the calling script. The combination of raw h2, quinn, and rustls covers all three HTTP protocols — and thanks to per-worker runtimes and a lock-free design, the client itself is not the bottleneck.&lt;&#x2F;p&gt;
&lt;p&gt;CPU profiling confirmed this: over 60% of execution time is spent in TLS decryption, memory copies, and kernel syscalls — code paths shared by any HTTP benchmark client regardless of language. The remaining gaps to h2load are architectural properties of the Rust crate ecosystem (h2’s async channel design, quinn’s userspace QUIC overhead) rather than optimization opportunities in loadgen-rs itself.&lt;&#x2F;p&gt;
&lt;p&gt;With the Deno FFI layer, distributed mode, and Terraform&#x2F;Ansible pipeline, loadgen-rs now serves five usage patterns: shell scripts that need a fast, h2load-compatible CLI tool; TypeScript users who want k6-style scripted scenarios with native H1&#x2F;H2&#x2F;H3 performance; multi-machine deployments where a single process can’t generate enough load; cloud-provisioned benchmarks where you spin up dedicated Hetzner instances, run the test, and tear everything down in minutes; and distributed scripted scenarios where workers execute multi-step request sequences with checks and response validation on remote machines. The two-API design (batch for throughput, step for correlation) plus the controller&#x2F;worker&#x2F;infrastructure stack bridges all five without compromising any of them.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;code-and-repository-link&quot;&gt;Code and Repository Link&lt;a class=&quot;zola-anchor&quot; href=&quot;#code-and-repository-link&quot; aria-label=&quot;Anchor link for: code-and-repository-link&quot;&gt;🔗&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The code is on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;git001&#x2F;loadgen-rs&quot;&gt;loadgen-rs - GitHub&lt;&#x2F;a&gt; — PRs welcome.&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>
