From d5d0d642ab6618520d009b2610a55208d237d8cc Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Thu, 6 Nov 2025 17:00:22 -0800 Subject: [PATCH 1/2] add benchmark suite --- Cargo.lock | 148 +++++++++++++++++++++------------------- Cargo.toml | 3 +- benchmark/Cargo.toml | 14 ++++ benchmark/README.md | 59 ++++++++++++++++ benchmark/package.json | 14 ++++ benchmark/run.js | 128 ++++++++++++++++++++++++++++++++++ benchmark/src/lib.rs | 91 ++++++++++++++++++++++++ benchmark/wrangler.toml | 6 ++ package.json | 1 + 9 files changed, 391 insertions(+), 73 deletions(-) create mode 100644 benchmark/Cargo.toml create mode 100644 benchmark/README.md create mode 100644 benchmark/package.json create mode 100644 benchmark/run.js create mode 100644 benchmark/src/lib.rs create mode 100644 benchmark/wrangler.toml diff --git a/Cargo.lock b/Cargo.lock index 879a2f234..b3874792c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,9 +33,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -345,9 +345,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.43" +version = "1.2.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ "find-msvc-tools", "jobserver", @@ -396,9 +396,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.50" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", "clap_derive", @@ -406,9 +406,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.50" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstream", "anstyle", @@ -1235,9 +1235,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -1248,9 +1248,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -1261,11 +1261,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -1276,42 +1275,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -1388,9 +1383,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" dependencies = [ "memchr", "serde", @@ -1493,9 +1488,9 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "litrs" @@ -1921,9 +1916,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -1991,9 +1986,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -2226,9 +2221,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.34" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "log", "once_cell", @@ -2259,9 +2254,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.7" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring", "rustls-pki-types", @@ -2581,9 +2576,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.108" +version = "2.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" dependencies = [ "proc-macro2", "quote", @@ -2738,9 +2733,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -2869,9 +2864,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -3168,24 +3163,24 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" @@ -3245,7 +3240,7 @@ dependencies = [ "serde_json", "ureq-proto", "utf-8", - "webpki-roots 1.0.3", + "webpki-roots 1.0.4", ] [[package]] @@ -3332,9 +3327,9 @@ dependencies = [ [[package]] name = "walrus" -version = "0.24.2" +version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b5fbda74aece555fd16909d66141a934c9db314980a98800cf138a00c3e23a8" +checksum = "ff282e73d21b86a9d397f42570d158eb9e5c521d0ead4d13cd049fd7cb45c467" dependencies = [ "anyhow", "gimli", @@ -3548,14 +3543,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.3", + "webpki-roots 1.0.4", ] [[package]] name = "webpki-roots" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" dependencies = [ "rustls-pki-types", ] @@ -4129,11 +4124,21 @@ dependencies = [ "web-sys", ] +[[package]] +name = "workers-rs-benchmark" +version = "0.1.0" +dependencies = [ + "futures-util", + "serde", + "serde_json", + "worker", +] + [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "xattr" @@ -4156,11 +4161,10 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -4168,9 +4172,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", @@ -4241,9 +4245,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -4252,9 +4256,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -4263,9 +4267,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", @@ -4304,9 +4308,9 @@ dependencies = [ [[package]] name = "zopfli" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" dependencies = [ "bumpalo", "crc32fast", diff --git a/Cargo.toml b/Cargo.toml index e4ea71863..31705dba4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,8 @@ members = [ "worker-sys", "worker-kv", "examples/*", - "test/container-echo" + "test/container-echo", + "benchmark" ] exclude = ["examples/coredump", "examples/axum", "templates/*", "wasm-bindgen", "generated"] resolver = "2" diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml new file mode 100644 index 000000000..0742ca42b --- /dev/null +++ b/benchmark/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "workers-rs-benchmark" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +worker.workspace = true +serde.workspace = true +serde_json.workspace = true +futures-util.workspace = true diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 000000000..28d955a3b --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,59 @@ +# workers-rs Benchmark Suite + +Performance benchmark for workers-rs that measures streaming and parallel sub-request performance. + +## How to run + +First, make sure to clone workers-rs with all submodules. + +Then from the root of workers-rs: + +```bash +npm run build +``` + +to build the local `worker-build`. + +Then run the benchmark: + +```bash +cd benchmark +npm install +npm run bench +``` + +## What it does + +- Streams 1MB of data from `/stream` endpoint in 8KB chunks +- Makes 10 parallel sub-requests to `/stream` from `/benchmark` endpoint +- All requests are internal (no network I/O) to isolate workers-rs performance +- Runs 20 iterations with 3 warmup requests + +## Output + +The benchmark provides: + +- Per-iteration timing for Node.js end-to-end and Worker internal execution +- Summary statistics: average, min, and max times +- Data transfer statistics (10MB per iteration = 10 parallel 1MB streams) +- Average throughput in Mbps + +## Configuration + +Adjust parameters in `run.mjs`: +- `iterations` - Number of benchmark runs (default: 20) +- Warmup count (default: 3) + +Adjust workload in `src/lib.rs`: +- Number of parallel requests (default: 10) +- Data size per request (default: 1MB) +- Chunk size for streaming (default: 8KB) + +## Rust Toolchain + +`rust-toolchain.toml` in the root of workers-rs sets the Rust toolchain. Changing this can be used to +benchmark against different toolchain versions. + +## Compatibility Date + +The current compaitibility date is set to `2025-11-01` in the `wrangler.toml`. Finalization registry was enabled as of `2025-05-05`, so is included. \ No newline at end of file diff --git a/benchmark/package.json b/benchmark/package.json new file mode 100644 index 000000000..7c25e4048 --- /dev/null +++ b/benchmark/package.json @@ -0,0 +1,14 @@ +{ + "name": "workers-rs-benchmark", + "version": "0.1.0", + "type": "module", + "description": "Performance benchmark suite for workers-rs", + "private": true, + "scripts": { + "build": "WASM_BINDGEN_BIN=../wasm-bindgen/target/debug/wasm-bindgen ../target/debug/worker-build --release", + "bench": "npm run build && node run.js" + }, + "dependencies": { + "miniflare": "^4.20250923.0" + } +} diff --git a/benchmark/run.js b/benchmark/run.js new file mode 100644 index 000000000..7ce60a4d4 --- /dev/null +++ b/benchmark/run.js @@ -0,0 +1,128 @@ +#!/usr/bin/env node + +/** + * Benchmark runner for workers-rs + * + * This script runs performance benchmarks against the worker server. + * It measures the time taken to complete a benchmark that makes 10 parallel + * sub-requests, each streaming 1MB of data internally. + */ + +import { Miniflare } from 'miniflare'; + +async function runBenchmark() { + console.log('šŸš€ Starting workers-rs benchmark suite\n'); + + // Initialize Miniflare instance with the compiled worker + console.log('šŸ“¦ Initializing Miniflare...'); + const mf = new Miniflare({ + workers: [ + { + name: 'benchmark', + scriptPath: './build/index.js', + compatibilityDate: '2025-01-06', + modules: true, + modulesRules: [ + { type: 'CompiledWasm', include: ['**/*.wasm'], fallthrough: true } + ], + outboundService: 'benchmark', + } + ] + }); + + const mfUrl = await mf.ready; + console.log(`āœ… Miniflare ready at ${mfUrl}\n`); + + // Run warmup requests + console.log('šŸ”„ Running warmup requests...'); + for (let i = 0; i < 3; i++) { + await mf.dispatchFetch(`${mfUrl}benchmark`); + } + console.log('āœ… Warmup complete\n'); + + // Run benchmark iterations + const iterations = 20; + const results = []; + + console.log(`šŸ“Š Running ${iterations} benchmark iterations...\n`); + + for (let i = 0; i < iterations; i++) { + const iterStart = Date.now(); + const response = await mf.dispatchFetch(`${mfUrl}benchmark`); + const iterEnd = Date.now(); + + const nodeJsDuration = iterEnd - iterStart; + const result = await response.json(); + + if (!result.success) { + console.error(`āŒ Iteration ${i + 1} failed:`, result.errors); + await mf.dispose(); + process.exit(1); + } + + results.push({ + iteration: i + 1, + nodeJsDuration, + workerDuration: result.duration_ms, + totalBytes: result.total_bytes, + numRequests: result.num_requests, + }); + + console.log(` Iteration ${i + 1}:`); + console.log(` Node.js end-to-end time: ${nodeJsDuration}ms`); + console.log(` Worker internal time: ${result.duration_ms}ms`); + console.log(` Data transferred: ${(result.total_bytes / (1024 * 1024)).toFixed(2)}MB`); + console.log(` Sub-requests: ${result.num_requests}`); + console.log(); + } + + // Calculate statistics + const nodeJsDurations = results.map(r => r.nodeJsDuration); + const workerDurations = results.map(r => r.workerDuration); + + const avgNodeJs = nodeJsDurations.reduce((a, b) => a + b, 0) / iterations; + const avgWorker = workerDurations.reduce((a, b) => a + b, 0) / iterations; + + const minNodeJs = Math.min(...nodeJsDurations); + const maxNodeJs = Math.max(...nodeJsDurations); + const minWorker = Math.min(...workerDurations); + const maxWorker = Math.max(...workerDurations); + + // Print summary + console.log('━'.repeat(60)); + console.log('šŸ“ˆ BENCHMARK SUMMARY'); + console.log('━'.repeat(60)); + console.log(); + console.log('Node.js End-to-End Time:'); + console.log(` Average: ${avgNodeJs.toFixed(2)}ms`); + console.log(` Min: ${minNodeJs.toFixed(2)}ms`); + console.log(` Max: ${maxNodeJs.toFixed(2)}ms`); + console.log(); + console.log('Worker Internal Time:'); + console.log(` Average: ${avgWorker.toFixed(2)}ms`); + console.log(` Min: ${minWorker.toFixed(2)}ms`); + console.log(` Max: ${maxWorker.toFixed(2)}ms`); + console.log(); + console.log('Benchmark Configuration:'); + console.log(` Parallel sub-requests: 10`); + console.log(` Data per sub-request: 1MB`); + console.log(` Total data per iteration: 10MB`); + console.log(` Iterations: ${iterations}`); + console.log(); + console.log('━'.repeat(60)); + + // Calculate throughput + const totalBytes = results[0].totalBytes; + const throughputMbps = (totalBytes * 8 / (avgWorker / 1000)) / (1024 * 1024); + console.log(`šŸš€ Average throughput: ${throughputMbps.toFixed(2)} Mbps`); + console.log('━'.repeat(60)); + + // Cleanup + await mf.dispose(); + console.log('\nāœ… Benchmark complete!'); +} + +runBenchmark().catch((error) => { + console.error('āŒ Benchmark failed:', error); + process.exit(1); +}); diff --git a/benchmark/src/lib.rs b/benchmark/src/lib.rs new file mode 100644 index 000000000..f583f4831 --- /dev/null +++ b/benchmark/src/lib.rs @@ -0,0 +1,91 @@ +use worker::*; + +#[event(fetch)] +async fn main(req: Request, _env: Env, _ctx: Context) -> Result { + let url = req.url()?; + let path = url.path(); + + match path { + "/stream" => handle_stream().await, + "/benchmark" => handle_benchmark(&url).await, + _ => Response::error("Not Found", 404), + } +} + +/// Streams 1MB of data in chunks +async fn handle_stream() -> Result { + use futures_util::stream; + + // Create 1MB of data (1024 * 1024 bytes) + let chunk_size = 8192; // 8KB chunks + let num_chunks = (1024 * 1024) / chunk_size; // 128 chunks + let chunk = vec![b'x'; chunk_size]; + + // Create a stream that yields the data + let data_stream = stream::iter((0..num_chunks).map(move |_| { + Ok::, worker::Error>(chunk.clone()) + })); + + Response::from_stream(data_stream) +} + +/// Main benchmark handler that makes 10 parallel sub-requests +async fn handle_benchmark(url: &Url) -> Result { + // Get the base URL from the request + let base_url = format!("{}://{}", url.scheme(), url.host_str().unwrap_or("localhost")); + let stream_url = format!("{}/stream", base_url); + + // Create 10 parallel sub-requests + let mut tasks = Vec::new(); + + for i in 0..10 { + let stream_url = stream_url.clone(); + + // Create a task for each sub-request + let task = async move { + // Make the sub-request to the streaming endpoint + let mut response = Fetch::Url(stream_url.parse().unwrap()) + .send() + .await + .map_err(|e| format!("Fetch error on request {}: {:?}", i, e))?; + + // Consume the stream to ensure all data is read + let body = response.bytes().await + .map_err(|e| format!("Body read error on request {}: {:?}", i, e))?; + + let total_bytes = body.len() as u64; + + Ok::(total_bytes) + }; + + tasks.push(task); + } + + // Execute all tasks in parallel + let start = Date::now().as_millis(); + let results = futures_util::future::join_all(tasks).await; + let end = Date::now().as_millis(); + let duration_ms = end - start; + + // Check for errors and sum up total bytes + let mut total_bytes = 0u64; + let mut errors = Vec::new(); + + for (i, result) in results.iter().enumerate() { + match result { + Ok(bytes) => total_bytes += bytes, + Err(e) => errors.push(format!("Request {}: {}", i, e)), + } + } + + // Return summary as JSON + let summary = serde_json::json!({ + "success": errors.is_empty(), + "duration_ms": duration_ms, + "total_bytes": total_bytes, + "num_requests": 10, + "errors": errors, + }); + + Response::from_json(&summary) +} diff --git a/benchmark/wrangler.toml b/benchmark/wrangler.toml new file mode 100644 index 000000000..1e07bbab2 --- /dev/null +++ b/benchmark/wrangler.toml @@ -0,0 +1,6 @@ +name = "workers-rs-benchmark" +main = "build/worker/shim.mjs" +compatibility_date = "2025-09-09" + +[build] +command = "cargo install -q worker-build && worker-build --release" diff --git a/package.json b/package.json index 27fad2a05..46e2cb215 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "scripts": { "build": "cd wasm-bindgen && cargo build -p wasm-bindgen-cli --bin wasm-bindgen && cd .. && cargo build -p worker-build", + "bench": "cd benchmark && npm run build && npm run bench", "test": "cd test && NO_MINIFY=1 WASM_BINDGEN_BIN=../wasm-bindgen/target/debug/wasm-bindgen ../target/debug/worker-build --dev && NODE_OPTIONS='--experimental-vm-modules' npx vitest run", "test-http": "cd test && NO_MINIFY=1 WASM_BINDGEN_BIN=../wasm-bindgen/target/debug/wasm-bindgen ../target/debug/worker-build --release --features http && NODE_OPTIONS='--experimental-vm-modules' npx vitest run", "test-mem": "cd test && npx wrangler dev --enable-containers=false", From 05b5abef68a651ed7d7a86d0fb5273657161ef58 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Thu, 6 Nov 2025 17:03:03 -0800 Subject: [PATCH 2/2] fmt --- benchmark/src/lib.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/benchmark/src/lib.rs b/benchmark/src/lib.rs index f583f4831..6959b48fb 100644 --- a/benchmark/src/lib.rs +++ b/benchmark/src/lib.rs @@ -22,9 +22,8 @@ async fn handle_stream() -> Result { let chunk = vec![b'x'; chunk_size]; // Create a stream that yields the data - let data_stream = stream::iter((0..num_chunks).map(move |_| { - Ok::, worker::Error>(chunk.clone()) - })); + let data_stream = + stream::iter((0..num_chunks).map(move |_| Ok::, worker::Error>(chunk.clone()))); Response::from_stream(data_stream) } @@ -32,7 +31,11 @@ async fn handle_stream() -> Result { /// Main benchmark handler that makes 10 parallel sub-requests async fn handle_benchmark(url: &Url) -> Result { // Get the base URL from the request - let base_url = format!("{}://{}", url.scheme(), url.host_str().unwrap_or("localhost")); + let base_url = format!( + "{}://{}", + url.scheme(), + url.host_str().unwrap_or("localhost") + ); let stream_url = format!("{}/stream", base_url); // Create 10 parallel sub-requests @@ -50,7 +53,9 @@ async fn handle_benchmark(url: &Url) -> Result { .map_err(|e| format!("Fetch error on request {}: {:?}", i, e))?; // Consume the stream to ensure all data is read - let body = response.bytes().await + let body = response + .bytes() + .await .map_err(|e| format!("Body read error on request {}: {:?}", i, e))?; let total_bytes = body.len() as u64;