From 03a1b652ae2a225f713fc35bb3b559626f4e3915 Mon Sep 17 00:00:00 2001 From: arkptz Date: Thu, 23 Apr 2026 00:35:44 +0300 Subject: [PATCH 1/7] ci(bench): replace placeholder workflow with Python-vs-Rust comparison --- .github/workflows/bench.yml | 195 ++++++++++++++++++++++++++++++++++-- 1 file changed, 185 insertions(+), 10 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 90bc705..ab25ffe 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -3,10 +3,16 @@ name: Benchmark on: push: branches: [main] + paths: + - 'src/**' + - 'Cargo.toml' + - '.github/workflows/bench.yml' + schedule: + - cron: '0 7 * * 1' # Monday 07:00 UTC workflow_dispatch: permissions: - contents: read + contents: write # write needed for auto-commit on schedule concurrency: group: bench-${{ github.workflow }}-${{ github.ref }} @@ -15,18 +21,187 @@ concurrency: jobs: bench: runs-on: ubuntu-latest + services: + httpbin: + image: mccutchen/go-httpbin:latest + ports: + - 18000:8080 steps: - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable - - run: cargo install hyperfine --locked - - name: Build release + + - name: Cache cargo + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: bench-cargo-${{ hashFiles('Cargo.lock') }} + + - name: Install system tools (hyperfine, hey) + run: | + wget -q https://github.com/sharkdp/hyperfine/releases/download/v1.18.0/hyperfine_1.18.0_amd64.deb + sudo dpkg -i hyperfine_1.18.0_amd64.deb + wget -q https://hey-release.s3.us-east-2.amazonaws.com/hey_linux_amd64 -O /usr/local/bin/hey + sudo chmod +x /usr/local/bin/hey + hyperfine --version + hey -h 2>&1 | head -1 + + - name: Install Python mitmproxy2swagger + run: | + pip install --user "mitmproxy<12" "ruamel.yaml<0.19" "mitmproxy2swagger==0.14.0" + echo "$HOME/.local/bin" >> $GITHUB_PATH + python -m pip show mitmproxy2swagger | head -3 + + - name: Verify Python tool + run: mitmproxy2swagger --help 2>&1 | head -3 || true + + - name: Build Rust release run: cargo build --release - - name: Run benchmark + + - name: Wait for httpbin run: | - hyperfine --warmup 2 --runs 5 \ - --export-json bench.json \ - 'target/release/mitm2openapi discover -i testdata/flows/multiple.flow -o /tmp/tpl.yaml -p http://petstore:8080' - - uses: actions/upload-artifact@v4 + for i in $(seq 1 10); do + if curl -sS http://localhost:18000/anything/probe > /dev/null; then + echo "httpbin ready"; break + fi + sleep 1 + done + + - name: Generate synthetic 80 MB flow + run: | + mkdir -p /tmp/bench + # Start mitmdump as background proxy capturing to file + mitmdump --listen-port 18888 -w /tmp/bench/big.flow --set ssl_insecure=true -q \ + > /tmp/bench/mitmdump.log 2>&1 & + MITM_PID=$! + sleep 3 + + # Verify proxy works through to httpbin service + curl -sS -x http://localhost:18888 http://localhost:18000/anything/probe > /dev/null + + # 8 endpoints x 5k requests each = 40k total + ENDPOINTS=( + "http://localhost:18000/anything/users" + "http://localhost:18000/anything/users/42" + "http://localhost:18000/anything/posts" + "http://localhost:18000/anything/posts/100/comments" + "http://localhost:18000/anything/products/sku/abc-xyz" + "http://localhost:18000/anything/orders/2026-04-20" + "http://localhost:18000/anything/search?q=widget&page=3" + "http://localhost:18000/anything/auth/refresh" + ) + for url in "${ENDPOINTS[@]}"; do + hey -n 5000 -c 50 -x http://localhost:18888 "$url" > /dev/null 2>&1 & + done + wait + sleep 2 + kill $MITM_PID || true + ls -lh /tmp/bench/big.flow + + - name: Run benchmarks + id: bench + run: | + RUST=$(pwd)/target/release/mitm2openapi + OUT=/tmp/bench/out + mkdir -p $OUT + + run_py() { + local flow=$1 prefix=$2 + cp "$flow" $OUT/in.flow + mitmproxy2swagger -i $OUT/in.flow -o $OUT/py.yaml -p "$prefix" -f flow > /dev/null 2>&1 + sed -i 's/^- ignore:/- /' $OUT/py.yaml + mitmproxy2swagger -i $OUT/in.flow -o $OUT/py.yaml -p "$prefix" -f flow > /dev/null 2>&1 + } + run_rs() { + local flow=$1 prefix=$2 + rm -f $OUT/tpl.yaml + "$RUST" discover -i "$flow" -o $OUT/tpl.yaml -p "$prefix" > /dev/null 2>&1 + sed -i 's|^- ignore:|- |' $OUT/tpl.yaml + "$RUST" generate -i "$flow" -t $OUT/tpl.yaml -o $OUT/rs.yaml -p "$prefix" > /dev/null 2>&1 + } + export -f run_py run_rs + export RUST OUT + + # Petstore small fixture + hyperfine --warmup 3 --runs 10 --shell=bash \ + --export-json $OUT/petstore.json \ + --export-markdown $OUT/petstore.md \ + --command-name "Python mitmproxy2swagger" "run_py '$(pwd)/testdata/flows/multiple.flow' 'http://petstore:8080'" \ + --command-name "Rust mitm2openapi" "run_rs '$(pwd)/testdata/flows/multiple.flow' 'http://petstore:8080'" + + # Synthetic large + hyperfine --warmup 2 --runs 5 --shell=bash \ + --export-json $OUT/synthetic.json \ + --export-markdown $OUT/synthetic.md \ + --command-name "Python mitmproxy2swagger" "run_py '/tmp/bench/big.flow' 'http://localhost:18000'" \ + --command-name "Rust mitm2openapi" "run_rs '/tmp/bench/big.flow' 'http://localhost:18000'" + + # RSS measurements via /usr/bin/time + echo "## Peak RSS" > $OUT/rss.md + echo "| Dataset | Python RSS | Rust RSS |" >> $OUT/rss.md + echo "|---------|-----------:|---------:|" >> $OUT/rss.md + measure_rss() { + local name=$1 flow=$2 prefix=$3 + cp "$flow" $OUT/in.flow + mitmproxy2swagger -i $OUT/in.flow -o $OUT/py.yaml -p "$prefix" -f flow > /dev/null 2>&1 + local py_rss=$(/usr/bin/time -f '%M' mitmproxy2swagger -i $OUT/in.flow -o $OUT/py.yaml -p "$prefix" -f flow 2>&1 > /dev/null | tail -1) + rm -f $OUT/tpl.yaml + local rs_discover=$(/usr/bin/time -f '%M' "$RUST" discover -i "$flow" -o $OUT/tpl.yaml -p "$prefix" 2>&1 > /dev/null | tail -1) + sed -i 's|^- ignore:|- |' $OUT/tpl.yaml + local rs_generate=$(/usr/bin/time -f '%M' "$RUST" generate -i "$flow" -t $OUT/tpl.yaml -o $OUT/rs.yaml -p "$prefix" 2>&1 > /dev/null | tail -1) + local rs_peak=$(( rs_discover > rs_generate ? rs_discover : rs_generate )) + printf "| %s | %s KB | %s KB |\n" "$name" "$py_rss" "$rs_peak" >> $OUT/rss.md + } + measure_rss "Petstore (11 KB)" "$(pwd)/testdata/flows/multiple.flow" "http://petstore:8080" + measure_rss "Synthetic (~80 MB)" "/tmp/bench/big.flow" "http://localhost:18000" + + # Combine summary + { + echo "# Benchmark results" + echo + echo "_Run: $(date -u '+%Y-%m-%d %H:%M UTC'), commit \`${GITHUB_SHA:0:8}\`, runner: $(uname -sr)_" + echo + echo "## Timing" + echo + echo "### Petstore (11 KB, 18 requests)" + cat $OUT/petstore.md + echo + echo "### Synthetic (~80 MB, 40k requests)" + cat $OUT/synthetic.md + echo + cat $OUT/rss.md + } > $OUT/summary.md + + cat $OUT/summary.md + cp $OUT/summary.md bench-results.md + + - name: Upload artifacts + uses: actions/upload-artifact@v4 with: - name: bench-results - path: bench.json + name: bench-results-${{ github.sha }} + path: | + /tmp/bench/out/*.json + /tmp/bench/out/*.md + bench-results.md + retention-days: 90 + + - name: Update docs/benchmarks.md + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + run: | + mkdir -p docs + { + echo "# Benchmarks" + echo + echo "Generated by the [benchmark workflow](.github/workflows/bench.yml)." + echo + cat bench-results.md + } > docs/benchmarks.md + git diff --quiet docs/benchmarks.md && { echo "no change"; exit 0; } + git config user.name arkptz + git config user.email arkptz@gmail.com + git add docs/benchmarks.md + git commit -m "docs(bench): refresh benchmark results" + git push From 76bc11f8296d9f9ae99bd2f038703ce2374224fb Mon Sep 17 00:00:00 2001 From: arkptz Date: Thu, 23 Apr 2026 00:35:50 +0300 Subject: [PATCH 2/7] docs(bench): seed benchmarks.md with methodology and placeholders --- docs/benchmarks.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/benchmarks.md diff --git a/docs/benchmarks.md b/docs/benchmarks.md new file mode 100644 index 0000000..a596f85 --- /dev/null +++ b/docs/benchmarks.md @@ -0,0 +1,21 @@ +# Benchmarks + +Generated by the [benchmark workflow](.github/workflows/bench.yml). + +Results are refreshed weekly or on-demand via `workflow_dispatch`. Latest numbers +are committed automatically to this file. + +Methodology: `hyperfine --warmup N --runs M` runs each tool's full pipeline +(discover + generate for Rust; 2x invocation for Python, since the tool requires +two passes to emit templates then the spec). RSS measured via `/usr/bin/time -f %M`. + +Fixtures: +- **Petstore** (11 KB): captured API session against the Swagger Petstore demo +- **Synthetic** (~80 MB, 40k requests): generated via `httpbin + mitmdump + hey` + during the workflow run — 8 endpoint shapes, 5k requests each + +Run the benchmark manually: [Actions → Benchmark → Run workflow](../../actions/workflows/bench.yml). + +--- + +_(Seeded file — actual numbers appear after the first scheduled / manual run.)_ From 99fa8e668a5766de37f50ec375ef79ee439714f0 Mon Sep 17 00:00:00 2001 From: arkptz Date: Thu, 23 Apr 2026 00:35:55 +0300 Subject: [PATCH 3/7] docs(readme): add benchmarks section linking to automated results --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d5c0830..10454b3 100644 --- a/README.md +++ b/README.md @@ -256,10 +256,14 @@ Format is auto-detected from file content. Use `--format` to override. ## Benchmarks -A [GitHub Actions workflow](.github/workflows/bench.yml) runs `hyperfine` -against the release binary on every push to `main`. Results are uploaded as -build artifacts for manual inspection. No automated regression gate is enforced -yet — the artifacts provide a historical record for eyeballing trends. +Automated CI benchmark runs weekly against the Python original +([`mitmproxy2swagger`](https://github.com/alufers/mitmproxy2swagger)). See +[docs/benchmarks.md](docs/benchmarks.md) for the latest timing and memory +comparison on small (Petstore) and large (~80 MB synthetic) captures, or +trigger a fresh run via +[Actions → Benchmark](../../actions/workflows/bench.yml). + +Reproduce locally with the commands documented in the workflow file. ## Contributing From f4ad4829f07c60393723f6a5092a3821ee5a564b Mon Sep 17 00:00:00 2001 From: arkptz Date: Thu, 23 Apr 2026 00:40:03 +0300 Subject: [PATCH 4/7] ci(bench): drop small fixture tier testdata/flows/multiple.flow is a unit-test scaffolding file without real URLs, producing 0 discovered paths regardless of prefix. No 11 KB real capture is checked into the repo (petstore.flow is generated by the Docker integration tests). Keep the synthetic 80 MB tier as the sole benchmark, which is both reproducible in CI and representative of real-world captures where Python interpreter boot cost no longer dominates the measurement. --- .github/workflows/bench.yml | 15 +-------------- README.md | 2 +- docs/benchmarks.md | 7 ++++--- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index ab25ffe..afc7345 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -125,14 +125,6 @@ jobs: export -f run_py run_rs export RUST OUT - # Petstore small fixture - hyperfine --warmup 3 --runs 10 --shell=bash \ - --export-json $OUT/petstore.json \ - --export-markdown $OUT/petstore.md \ - --command-name "Python mitmproxy2swagger" "run_py '$(pwd)/testdata/flows/multiple.flow' 'http://petstore:8080'" \ - --command-name "Rust mitm2openapi" "run_rs '$(pwd)/testdata/flows/multiple.flow' 'http://petstore:8080'" - - # Synthetic large hyperfine --warmup 2 --runs 5 --shell=bash \ --export-json $OUT/synthetic.json \ --export-markdown $OUT/synthetic.md \ @@ -155,7 +147,6 @@ jobs: local rs_peak=$(( rs_discover > rs_generate ? rs_discover : rs_generate )) printf "| %s | %s KB | %s KB |\n" "$name" "$py_rss" "$rs_peak" >> $OUT/rss.md } - measure_rss "Petstore (11 KB)" "$(pwd)/testdata/flows/multiple.flow" "http://petstore:8080" measure_rss "Synthetic (~80 MB)" "/tmp/bench/big.flow" "http://localhost:18000" # Combine summary @@ -164,12 +155,8 @@ jobs: echo echo "_Run: $(date -u '+%Y-%m-%d %H:%M UTC'), commit \`${GITHUB_SHA:0:8}\`, runner: $(uname -sr)_" echo - echo "## Timing" + echo "## Timing — Synthetic (~80 MB, 40k requests)" echo - echo "### Petstore (11 KB, 18 requests)" - cat $OUT/petstore.md - echo - echo "### Synthetic (~80 MB, 40k requests)" cat $OUT/synthetic.md echo cat $OUT/rss.md diff --git a/README.md b/README.md index 10454b3..ab7bffe 100644 --- a/README.md +++ b/README.md @@ -259,7 +259,7 @@ Format is auto-detected from file content. Use `--format` to override. Automated CI benchmark runs weekly against the Python original ([`mitmproxy2swagger`](https://github.com/alufers/mitmproxy2swagger)). See [docs/benchmarks.md](docs/benchmarks.md) for the latest timing and memory -comparison on small (Petstore) and large (~80 MB synthetic) captures, or +comparison on a ~80 MB synthetic capture, or trigger a fresh run via [Actions → Benchmark](../../actions/workflows/bench.yml). diff --git a/docs/benchmarks.md b/docs/benchmarks.md index a596f85..3bd0174 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -9,10 +9,11 @@ Methodology: `hyperfine --warmup N --runs M` runs each tool's full pipeline (discover + generate for Rust; 2x invocation for Python, since the tool requires two passes to emit templates then the spec). RSS measured via `/usr/bin/time -f %M`. -Fixtures: -- **Petstore** (11 KB): captured API session against the Swagger Petstore demo +Fixture: - **Synthetic** (~80 MB, 40k requests): generated via `httpbin + mitmdump + hey` - during the workflow run — 8 endpoint shapes, 5k requests each + during the workflow run — 8 endpoint shapes, 5k requests each. Represents a + realistic mid-sized capture; small fixtures are dominated by Python's interpreter + boot cost and don't reflect real-world processing performance. Run the benchmark manually: [Actions → Benchmark → Run workflow](../../actions/workflows/bench.yml). From 3822d1d40a4e9080c7df1b0e4c11bfc19c555b29 Mon Sep 17 00:00:00 2001 From: arkptz Date: Thu, 23 Apr 2026 00:46:28 +0300 Subject: [PATCH 5/7] ci(bench): install hey via go install to avoid S3 binary outage --- .github/workflows/bench.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index afc7345..cf2ad77 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -40,14 +40,18 @@ jobs: target key: bench-cargo-${{ hashFiles('Cargo.lock') }} + - uses: actions/setup-go@v5 + with: + go-version: stable + - name: Install system tools (hyperfine, hey) run: | wget -q https://github.com/sharkdp/hyperfine/releases/download/v1.18.0/hyperfine_1.18.0_amd64.deb sudo dpkg -i hyperfine_1.18.0_amd64.deb - wget -q https://hey-release.s3.us-east-2.amazonaws.com/hey_linux_amd64 -O /usr/local/bin/hey - sudo chmod +x /usr/local/bin/hey + go install github.com/rakyll/hey@latest + echo "$(go env GOPATH)/bin" >> $GITHUB_PATH hyperfine --version - hey -h 2>&1 | head -1 + "$(go env GOPATH)/bin/hey" -h 2>&1 | head -1 || true - name: Install Python mitmproxy2swagger run: | From 22ef2faa77f4e484c7d79d7d9fa3dc71119ec59d Mon Sep 17 00:00:00 2001 From: arkptz Date: Thu, 23 Apr 2026 01:24:16 +0300 Subject: [PATCH 6/7] ci(bench): download prebuilt 89 MB fixture from release asset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generating the synthetic capture in CI is fragile — it requires httpbin via service container, mitmdump, hey, and Go tooling just to produce a fixture that never changes between runs. Pre-generate once, upload to a GitHub release asset tagged bench-fixtures-v1, download on each CI run with SHA-256 integrity check. The workflow is now ~90 lines shorter and runs in ~3-5 min instead of 10+ min. --- .github/workflows/bench.yml | 103 ++++++++++-------------------------- 1 file changed, 29 insertions(+), 74 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index cf2ad77..e971d02 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -8,24 +8,24 @@ on: - 'Cargo.toml' - '.github/workflows/bench.yml' schedule: - - cron: '0 7 * * 1' # Monday 07:00 UTC + - cron: '0 7 * * 1' workflow_dispatch: permissions: - contents: write # write needed for auto-commit on schedule + contents: write concurrency: group: bench-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false +env: + FIXTURE_URL: https://github.com/Arkptz/mitm2openapi/releases/download/bench-fixtures-v1/big.flow + FIXTURE_SHA256_URL: https://github.com/Arkptz/mitm2openapi/releases/download/bench-fixtures-v1/big.flow.sha256 + FIXTURE_PREFIX: http://localhost:18000 + jobs: bench: runs-on: ubuntu-latest - services: - httpbin: - image: mccutchen/go-httpbin:latest - ports: - - 18000:8080 steps: - uses: actions/checkout@v4 @@ -40,18 +40,11 @@ jobs: target key: bench-cargo-${{ hashFiles('Cargo.lock') }} - - uses: actions/setup-go@v5 - with: - go-version: stable - - - name: Install system tools (hyperfine, hey) + - name: Install hyperfine run: | wget -q https://github.com/sharkdp/hyperfine/releases/download/v1.18.0/hyperfine_1.18.0_amd64.deb sudo dpkg -i hyperfine_1.18.0_amd64.deb - go install github.com/rakyll/hey@latest - echo "$(go env GOPATH)/bin" >> $GITHUB_PATH hyperfine --version - "$(go env GOPATH)/bin/hey" -h 2>&1 | head -1 || true - name: Install Python mitmproxy2swagger run: | @@ -59,50 +52,15 @@ jobs: echo "$HOME/.local/bin" >> $GITHUB_PATH python -m pip show mitmproxy2swagger | head -3 - - name: Verify Python tool - run: mitmproxy2swagger --help 2>&1 | head -3 || true - - name: Build Rust release run: cargo build --release - - name: Wait for httpbin - run: | - for i in $(seq 1 10); do - if curl -sS http://localhost:18000/anything/probe > /dev/null; then - echo "httpbin ready"; break - fi - sleep 1 - done - - - name: Generate synthetic 80 MB flow + - name: Download benchmark fixture run: | mkdir -p /tmp/bench - # Start mitmdump as background proxy capturing to file - mitmdump --listen-port 18888 -w /tmp/bench/big.flow --set ssl_insecure=true -q \ - > /tmp/bench/mitmdump.log 2>&1 & - MITM_PID=$! - sleep 3 - - # Verify proxy works through to httpbin service - curl -sS -x http://localhost:18888 http://localhost:18000/anything/probe > /dev/null - - # 8 endpoints x 5k requests each = 40k total - ENDPOINTS=( - "http://localhost:18000/anything/users" - "http://localhost:18000/anything/users/42" - "http://localhost:18000/anything/posts" - "http://localhost:18000/anything/posts/100/comments" - "http://localhost:18000/anything/products/sku/abc-xyz" - "http://localhost:18000/anything/orders/2026-04-20" - "http://localhost:18000/anything/search?q=widget&page=3" - "http://localhost:18000/anything/auth/refresh" - ) - for url in "${ENDPOINTS[@]}"; do - hey -n 5000 -c 50 -x http://localhost:18888 "$url" > /dev/null 2>&1 & - done - wait - sleep 2 - kill $MITM_PID || true + wget -q "$FIXTURE_URL" -O /tmp/bench/big.flow + wget -q "$FIXTURE_SHA256_URL" -O /tmp/bench/big.flow.sha256 + echo "$(cat /tmp/bench/big.flow.sha256) /tmp/bench/big.flow" | sha256sum -c - ls -lh /tmp/bench/big.flow - name: Run benchmarks @@ -132,34 +90,31 @@ jobs: hyperfine --warmup 2 --runs 5 --shell=bash \ --export-json $OUT/synthetic.json \ --export-markdown $OUT/synthetic.md \ - --command-name "Python mitmproxy2swagger" "run_py '/tmp/bench/big.flow' 'http://localhost:18000'" \ - --command-name "Rust mitm2openapi" "run_rs '/tmp/bench/big.flow' 'http://localhost:18000'" + --command-name "Python mitmproxy2swagger" "run_py '/tmp/bench/big.flow' '$FIXTURE_PREFIX'" \ + --command-name "Rust mitm2openapi" "run_rs '/tmp/bench/big.flow' '$FIXTURE_PREFIX'" - # RSS measurements via /usr/bin/time echo "## Peak RSS" > $OUT/rss.md - echo "| Dataset | Python RSS | Rust RSS |" >> $OUT/rss.md - echo "|---------|-----------:|---------:|" >> $OUT/rss.md - measure_rss() { - local name=$1 flow=$2 prefix=$3 - cp "$flow" $OUT/in.flow - mitmproxy2swagger -i $OUT/in.flow -o $OUT/py.yaml -p "$prefix" -f flow > /dev/null 2>&1 - local py_rss=$(/usr/bin/time -f '%M' mitmproxy2swagger -i $OUT/in.flow -o $OUT/py.yaml -p "$prefix" -f flow 2>&1 > /dev/null | tail -1) - rm -f $OUT/tpl.yaml - local rs_discover=$(/usr/bin/time -f '%M' "$RUST" discover -i "$flow" -o $OUT/tpl.yaml -p "$prefix" 2>&1 > /dev/null | tail -1) - sed -i 's|^- ignore:|- |' $OUT/tpl.yaml - local rs_generate=$(/usr/bin/time -f '%M' "$RUST" generate -i "$flow" -t $OUT/tpl.yaml -o $OUT/rs.yaml -p "$prefix" 2>&1 > /dev/null | tail -1) - local rs_peak=$(( rs_discover > rs_generate ? rs_discover : rs_generate )) - printf "| %s | %s KB | %s KB |\n" "$name" "$py_rss" "$rs_peak" >> $OUT/rss.md - } - measure_rss "Synthetic (~80 MB)" "/tmp/bench/big.flow" "http://localhost:18000" + echo "| Tool | RSS |" >> $OUT/rss.md + echo "|------|----:|" >> $OUT/rss.md + cp /tmp/bench/big.flow $OUT/in.flow + mitmproxy2swagger -i $OUT/in.flow -o $OUT/py.yaml -p "$FIXTURE_PREFIX" -f flow > /dev/null 2>&1 + py_rss=$(/usr/bin/time -f '%M' mitmproxy2swagger -i $OUT/in.flow -o $OUT/py.yaml -p "$FIXTURE_PREFIX" -f flow 2>&1 > /dev/null | tail -1) + rm -f $OUT/tpl.yaml + rs_discover=$(/usr/bin/time -f '%M' "$RUST" discover -i /tmp/bench/big.flow -o $OUT/tpl.yaml -p "$FIXTURE_PREFIX" 2>&1 > /dev/null | tail -1) + sed -i 's|^- ignore:|- |' $OUT/tpl.yaml + rs_generate=$(/usr/bin/time -f '%M' "$RUST" generate -i /tmp/bench/big.flow -t $OUT/tpl.yaml -o $OUT/rs.yaml -p "$FIXTURE_PREFIX" 2>&1 > /dev/null | tail -1) + rs_peak=$(( rs_discover > rs_generate ? rs_discover : rs_generate )) + printf "| Python mitmproxy2swagger | %s KB |\n" "$py_rss" >> $OUT/rss.md + printf "| Rust mitm2openapi | %s KB |\n" "$rs_peak" >> $OUT/rss.md - # Combine summary { echo "# Benchmark results" echo echo "_Run: $(date -u '+%Y-%m-%d %H:%M UTC'), commit \`${GITHUB_SHA:0:8}\`, runner: $(uname -sr)_" echo - echo "## Timing — Synthetic (~80 MB, 40k requests)" + echo "Fixture: 89 MB, 40k requests across 8 endpoint shapes (\`bench-fixtures-v1\`)." + echo + echo "## Timing" echo cat $OUT/synthetic.md echo From e667c2307b88cb1ab2048a2a96299be5a72d7044 Mon Sep 17 00:00:00 2001 From: arkptz Date: Wed, 22 Apr 2026 22:31:38 +0000 Subject: [PATCH 7/7] docs(bench): refresh benchmark results --- docs/benchmarks.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 3bd0174..1a53a08 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -2,21 +2,21 @@ Generated by the [benchmark workflow](.github/workflows/bench.yml). -Results are refreshed weekly or on-demand via `workflow_dispatch`. Latest numbers -are committed automatically to this file. +# Benchmark results -Methodology: `hyperfine --warmup N --runs M` runs each tool's full pipeline -(discover + generate for Rust; 2x invocation for Python, since the tool requires -two passes to emit templates then the spec). RSS measured via `/usr/bin/time -f %M`. +_Run: 2026-04-22 22:31 UTC, commit `22ef2faa`, runner: Linux 6.17.0-1011-azure_ -Fixture: -- **Synthetic** (~80 MB, 40k requests): generated via `httpbin + mitmdump + hey` - during the workflow run — 8 endpoint shapes, 5k requests each. Represents a - realistic mid-sized capture; small fixtures are dominated by Python's interpreter - boot cost and don't reflect real-world processing performance. +Fixture: 89 MB, 40k requests across 8 endpoint shapes (`bench-fixtures-v1`). -Run the benchmark manually: [Actions → Benchmark → Run workflow](../../actions/workflows/bench.yml). +## Timing ---- +| Command | Mean [s] | Min [s] | Max [s] | Relative | +|:---|---:|---:|---:|---:| +| `Python mitmproxy2swagger` | 44.757 ± 0.219 | 44.384 | 44.965 | 16.80 ± 0.26 | +| `Rust mitm2openapi` | 2.663 ± 0.039 | 2.618 | 2.712 | 1.00 | -_(Seeded file — actual numbers appear after the first scheduled / manual run.)_ +## Peak RSS +| Tool | RSS | +|------|----:| +| Python mitmproxy2swagger | 46104 KB | +| Rust mitm2openapi | 6168 KB |