From 6832fa1bce227e94a0f8f4fb85d1d0e059eb0f47 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 24 Feb 2026 17:47:20 +0100 Subject: [PATCH] keynote-2: use rust client (#4421) --- Cargo.lock | 81 +- Cargo.toml | 4 +- pnpm-lock.yaml | 47 +- templates/keynote-2/README.md | 9 + templates/keynote-2/package-lock.json | 74 +- templates/keynote-2/package.json | 2 +- .../spacetimedb-rust-client/Cargo.lock | 2197 +++++++++++++++++ .../spacetimedb-rust-client/Cargo.toml | 21 + .../spacetimedb-rust-client/src/main.rs | 332 +++ .../spacetimedb-rust-client/src/websocket.rs | 422 ++++ templates/keynote-2/src/core/runner.ts | 62 +- templates/keynote-2/src/demo.ts | 92 +- 12 files changed, 3267 insertions(+), 76 deletions(-) create mode 100644 templates/keynote-2/spacetimedb-rust-client/Cargo.lock create mode 100644 templates/keynote-2/spacetimedb-rust-client/Cargo.toml create mode 100644 templates/keynote-2/spacetimedb-rust-client/src/main.rs create mode 100644 templates/keynote-2/spacetimedb-rust-client/src/websocket.rs diff --git a/Cargo.lock b/Cargo.lock index 8af1de0f1..14f9a0682 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4318,6 +4318,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -5943,6 +5944,16 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand 0.9.2", +] + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -7436,9 +7447,9 @@ dependencies = [ [[package]] name = "spacetimedb" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8679cf54a7a653e6bc612bef28c219f98b9274308d8aae1e86046ed685db48b1" +checksum = "db18cb19c7499ba4a65b1504442179a7e4aba487dc35978d90966c5ca02ee16b" dependencies = [ "bytemuck", "derive_more 0.99.20", @@ -7446,10 +7457,11 @@ dependencies = [ "log", "rand 0.8.5", "scoped-tls", - "spacetimedb-bindings-macro 1.6.0", - "spacetimedb-bindings-sys 1.6.0", - "spacetimedb-lib 1.6.0", - "spacetimedb-primitives 1.6.0", + "serde_json", + "spacetimedb-bindings-macro 1.9.0", + "spacetimedb-bindings-sys 1.9.0", + "spacetimedb-lib 1.9.0", + "spacetimedb-primitives 1.9.0", ] [[package]] @@ -7541,15 +7553,15 @@ dependencies = [ [[package]] name = "spacetimedb-bindings-macro" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a930242493f5c875ab96903eb40fb6a90c4d3ae99597fd51da569ff22769a03" +checksum = "47725515a53cf3344aa6bbb3f2063c7fbb5496c743f7a7c2150413acd1213c1d" dependencies = [ "heck 0.4.1", "humantime", "proc-macro2", "quote", - "spacetimedb-primitives 1.6.0", + "spacetimedb-primitives 1.9.0", "syn 2.0.107", ] @@ -7567,11 +7579,11 @@ dependencies = [ [[package]] name = "spacetimedb-bindings-sys" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e565dfcdd2dc3f58e0178052ec1c8ce710b013482d4fa50b511ba786a2c3bc68" +checksum = "08201dac3ce095645dfbf407e71aba7c784a6061dace21bb4a49dd0b80d3f007" dependencies = [ - "spacetimedb-primitives 1.6.0", + "spacetimedb-primitives 1.9.0", ] [[package]] @@ -8074,9 +8086,9 @@ dependencies = [ [[package]] name = "spacetimedb-lib" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba57c8f1983bb144ee7e1ff28aa5882ca437f67c14a14daad4bf61d31f7040d" +checksum = "702c08bfcd0426c45786e30f016e0a03d85f34dac3555e5b370291441297e266" dependencies = [ "anyhow", "bitflags 2.10.0", @@ -8086,9 +8098,9 @@ dependencies = [ "enum-as-inner", "hex", "itertools 0.12.1", - "spacetimedb-bindings-macro 1.6.0", - "spacetimedb-primitives 1.6.0", - "spacetimedb-sats 1.6.0", + "spacetimedb-bindings-macro 1.9.0", + "spacetimedb-primitives 1.9.0", + "spacetimedb-sats 1.9.0", "thiserror 1.0.69", ] @@ -8194,12 +8206,13 @@ dependencies = [ [[package]] name = "spacetimedb-primitives" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dcd64c6970ca59e7b71e51952cf1a71bae2652b1eb736c19a7e528f3874894a" +checksum = "55af71f2ccb753957ad47b19648481bd67ae458885f18df867a4d5b0a55c8c67" dependencies = [ "bitflags 2.10.0", "either", + "enum-as-inner", "itertools 0.12.1", "nohash-hasher", ] @@ -8242,11 +8255,31 @@ dependencies = [ "spacetimedb-lib 2.0.1", ] +[[package]] +name = "spacetimedb-rust-transfer-sim" +version = "0.1.0" +dependencies = [ + "bytes", + "clap 4.5.50", + "futures", + "http 1.3.1", + "humantime", + "itertools 0.12.1", + "log", + "rand 0.9.2", + "rand_distr", + "spacetimedb-client-api-messages", + "spacetimedb-lib 2.0.1", + "thiserror 1.0.69", + "tokio", + "tokio-tungstenite", +] + [[package]] name = "spacetimedb-sats" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae61d8f88bda21f56c143bc9b4ddc20100ff08ae4fed0b9444509f7c3ca4339" +checksum = "c4a89afd9f4eded852e7355102f66f8ff346d25fe903d38ef0b6a171d32d696a" dependencies = [ "anyhow", "arrayvec", @@ -8263,8 +8296,8 @@ dependencies = [ "second-stack", "sha3", "smallvec", - "spacetimedb-bindings-macro 1.6.0", - "spacetimedb-primitives 1.6.0", + "spacetimedb-bindings-macro 1.9.0", + "spacetimedb-primitives 1.9.0", "thiserror 1.0.69", ] @@ -9813,7 +9846,7 @@ version = "0.1.0" dependencies = [ "anyhow", "log", - "spacetimedb 1.6.0", + "spacetimedb 1.9.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9e97cd464..0aae96ae4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ members = [ "modules/module-test", "templates/basic-rs/spacetimedb", "templates/chat-console-rs/spacetimedb", + "templates/keynote-2/spacetimedb-rust-client", "modules/sdk-test", "modules/sdk-test-connect-disconnect", "modules/sdk-test-procedure", @@ -210,7 +211,7 @@ home = "0.5" hostname = "^0.3" http = "1.0" http-body-util= "0.1.3" -humantime = "2.1.0" +humantime = "2.3" hyper = "1.0" hyper-util = { version = "0.1", features = ["tokio"] } ignore = "0.4" @@ -254,6 +255,7 @@ quick-xml = "0.31" quote = "1.0.8" rand08 = { package = "rand", version = "0.8" } rand = "0.9" +rand_distr = "0.5.1" rayon = "1.8" rayon-core = "1.11.0" regex = "1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aae618d64..6fc62ac21 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -468,8 +468,8 @@ importers: specifier: ^3.0.1 version: 3.0.1 spacetimedb: - specifier: workspace:* - version: link:../../crates/bindings-typescript + specifier: ^2.0 + version: 2.0.1(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(@tanstack/react-query@5.90.19(react@19.2.4))(react@19.2.4)(svelte@5.46.4)(undici@6.21.3)(vue@3.5.26(typescript@5.9.3)) sql.js: specifier: ^1.13.0 version: 1.14.0 @@ -12872,6 +12872,29 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spacetimedb@2.0.1: + resolution: {integrity: sha512-19YkLz1P+JQDlYvlegDsn3YF4OsE2Pih31e5Xx/kobTExKwx/AtFWRUSA/EiRWvMV9jaIWzMhnsVfG+1fJ39Aw==} + peerDependencies: + '@angular/core': '>=17.0.0' + '@tanstack/react-query': ^5.0.0 + react: ^18.0.0 || ^19.0.0-0 || ^19.0.0 + svelte: ^4.0.0 || ^5.0.0 + undici: ^6.19.2 + vue: ^3.3.0 + peerDependenciesMeta: + '@angular/core': + optional: true + '@tanstack/react-query': + optional: true + react: + optional: true + svelte: + optional: true + undici: + optional: true + vue: + optional: true + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -25925,7 +25948,7 @@ snapshots: h3@2.0.1-rc.14: dependencies: rou3: 0.7.12 - srvx: 0.11.4 + srvx: 0.11.7 handle-thing@2.0.1: {} @@ -30788,6 +30811,24 @@ snapshots: space-separated-tokens@2.0.2: {} + spacetimedb@2.0.1(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(@tanstack/react-query@5.90.19(react@19.2.4))(react@19.2.4)(svelte@5.46.4)(undici@6.21.3)(vue@3.5.26(typescript@5.9.3)): + dependencies: + base64-js: 1.5.1 + headers-polyfill: 4.0.3 + object-inspect: 1.13.4 + prettier: 3.6.2 + pure-rand: 7.0.1 + safe-stable-stringify: 2.5.0 + statuses: 2.0.2 + url-polyfill: 1.1.14 + optionalDependencies: + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2) + '@tanstack/react-query': 5.90.19(react@19.2.4) + react: 19.2.4 + svelte: 5.46.4 + undici: 6.21.3 + vue: 3.5.26(typescript@5.9.3) + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 diff --git a/templates/keynote-2/README.md b/templates/keynote-2/README.md index b7b636385..dac18031e 100644 --- a/templates/keynote-2/README.md +++ b/templates/keynote-2/README.md @@ -15,6 +15,8 @@ The demo compares SpacetimeDB and Convex by default, since both are easy for any **Options:** `--systems a,b,c` | `--seconds N` | `--skip-prep` | `--no-animation` +**Note:** You will need to [install Rust](https://rust-lang.org/tools/install/) to run the spacetimedb benchmark, because we run a [Rust Client](#rust-client). + ## Results Summary All tests use 50 concurrent connections with a transfer workload (read-modify-write transaction between two accounts). @@ -151,6 +153,13 @@ SpacetimeDB supports `withConfirmedReads` mode which ensures transactions are du PlanetScale results (~477 TPS) demonstrate the **significant impact of cloud database latency**. When the database is accessed over the network (even within the same cloud region), round-trip latency dominates performance. This is why SpacetimeDB's colocated architecture provides such dramatic improvements. +### Rust client + +When running the benchmark for SpacetimeDB on higher-end hardware we found out that we were actually bottlnecked +on our test TypeScript client. To get the absolute most out of the performance of SpacetimeDB we wrote a custom +Rust client that allows us to send a much larger number of requests then we could otherwise. We didn't do this +for the other backends/databases as they maxed out before the client. + ## Systems Tested | System | Architecture | diff --git a/templates/keynote-2/package-lock.json b/templates/keynote-2/package-lock.json index 82d7f2bd8..96fb50cda 100644 --- a/templates/keynote-2/package-lock.json +++ b/templates/keynote-2/package-lock.json @@ -15,7 +15,7 @@ "drizzle-orm": "^0.44.7", "express": "^5.1.0", "hdr-histogram-js": "^3.0.1", - "spacetimedb": "1.11.4", + "spacetimedb": "^2.0", "sql.js": "^1.13.0", "undici": "^6.19.2" }, @@ -744,7 +744,6 @@ "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -841,7 +840,6 @@ "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -907,7 +905,6 @@ "integrity": "sha512-ep8b36RKHlgWPqjNG9ToUrPiwkhwh0AEzy883mO5Xnd+cL6VBH1EvSjBAAuxLUFF2Vn/moE3Me6v9E1Lo+48GQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/emscripten": "*", "@types/node": "*" @@ -981,7 +978,6 @@ "integrity": "sha512-gaYt9yqTbQ1iOxLpJA8FPR5PiaHP+jlg8I5EX0Rs2KFwNzhBsF40KzMZS5FwelY7RG0wzaucWdqSAJM3uNCPCg==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" @@ -1097,7 +1093,6 @@ "integrity": "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -1200,7 +1195,6 @@ "resolved": "https://registry.npmjs.org/convex/-/convex-1.29.3.tgz", "integrity": "sha512-tg5TXzMjpNk9m50YRtdp6US+t7ckxE4E+7DNKUCjJ2MupQs2RBSPF/z5SNN4GUmQLSfg0eMILDySzdAvjTrhnw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "esbuild": "0.25.4", "prettier": "^3.0.0" @@ -1667,12 +1661,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/fast-text-encoding": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", - "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", - "license": "Apache-2.0" - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2512,7 +2500,6 @@ "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -2539,7 +2526,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", - "dev": true, "license": "MIT", "optional": true }, @@ -2610,7 +2596,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2749,6 +2734,22 @@ "once": "^1.3.1" } }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -2873,6 +2874,15 @@ ], "license": "MIT" }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -3093,28 +3103,46 @@ } }, "node_modules/spacetimedb": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/spacetimedb/-/spacetimedb-1.11.4.tgz", - "integrity": "sha512-Xcb42UH2dAysThPrCUARTDxeeJWAf6QrJThS89LigegLmjFd1/KvJ9k/yMrXz75JdSlJZhFvttjKZff3CakqRQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/spacetimedb/-/spacetimedb-2.0.1.tgz", + "integrity": "sha512-19YkLz1P+JQDlYvlegDsn3YF4OsE2Pih31e5Xx/kobTExKwx/AtFWRUSA/EiRWvMV9jaIWzMhnsVfG+1fJ39Aw==", "license": "ISC", "dependencies": { "base64-js": "^1.5.1", - "fast-text-encoding": "^1.0.0", "headers-polyfill": "^4.0.3", + "object-inspect": "^1.13.4", "prettier": "^3.3.3", + "pure-rand": "^7.0.1", + "safe-stable-stringify": "^2.5.0", "statuses": "^2.0.2", "url-polyfill": "^1.1.14" }, "peerDependencies": { + "@angular/core": ">=17.0.0", + "@tanstack/react-query": "^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0", - "undici": "^6.19.2" + "svelte": "^4.0.0 || ^5.0.0", + "undici": "^6.19.2", + "vue": "^3.3.0" }, "peerDependenciesMeta": { + "@angular/core": { + "optional": true + }, + "@tanstack/react-query": { + "optional": true + }, "react": { "optional": true }, + "svelte": { + "optional": true + }, "undici": { "optional": true + }, + "vue": { + "optional": true } } }, @@ -3132,8 +3160,7 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.13.0.tgz", "integrity": "sha512-RJbVP1HRDlUUXahJ7VMTcu9Rm1Nzw+EBpoPr94vnbD4LwR715F3CcxE2G2k45PewcaZ57pjetYa+LoSJLAASgA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ssri": { "version": "13.0.0", @@ -3323,7 +3350,6 @@ "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", "integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.17" } diff --git a/templates/keynote-2/package.json b/templates/keynote-2/package.json index 202943e5c..c19cf90fd 100644 --- a/templates/keynote-2/package.json +++ b/templates/keynote-2/package.json @@ -36,7 +36,7 @@ "drizzle-orm": "^0.44.7", "express": "^5.1.0", "hdr-histogram-js": "^3.0.1", - "spacetimedb": "workspace:*", + "spacetimedb": "^2.0", "sql.js": "^1.13.0", "undici": "^6.19.2" } diff --git a/templates/keynote-2/spacetimedb-rust-client/Cargo.lock b/templates/keynote-2/spacetimedb-rust-client/Cargo.lock new file mode 100644 index 000000000..f1a56aed2 --- /dev/null +++ b/templates/keynote-2/spacetimedb-rust-client/Cargo.lock @@ -0,0 +1,2197 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "approx" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", + "serde_core", +] + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "decorum" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "281759d3c8a14f5c3f0c49363be56810fcd7f910422f97f2db850c2920fde5cf" +dependencies = [ + "approx", + "num-traits", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "ethnum" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" +dependencies = [ + "serde", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4eacb0641a310445a4c513f2a5e23e19952e269c6a38887254d5f837a305506" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "protobuf", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "second-stack" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4904c83c6e51f1b9b08bfa5a86f35a51798e8307186e6f5513852210a219c0bb" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spacetimedb-bindings-macro" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47725515a53cf3344aa6bbb3f2063c7fbb5496c743f7a7c2150413acd1213c1d" +dependencies = [ + "heck 0.4.1", + "humantime", + "proc-macro2", + "quote", + "spacetimedb-primitives", + "syn", +] + +[[package]] +name = "spacetimedb-client-api-messages" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4749528096332dfaa0674a252f259610646161e65fed29185a4ea78da38b1c8a" +dependencies = [ + "bytes", + "bytestring", + "chrono", + "derive_more", + "enum-as-inner", + "serde", + "serde_json", + "serde_with", + "smallvec", + "spacetimedb-lib", + "spacetimedb-primitives", + "spacetimedb-sats", + "strum", + "thiserror 1.0.69", +] + +[[package]] +name = "spacetimedb-lib" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702c08bfcd0426c45786e30f016e0a03d85f34dac3555e5b370291441297e266" +dependencies = [ + "anyhow", + "bitflags", + "blake3", + "chrono", + "derive_more", + "enum-as-inner", + "hex", + "itertools 0.12.1", + "serde", + "spacetimedb-bindings-macro", + "spacetimedb-metrics", + "spacetimedb-primitives", + "spacetimedb-sats", + "thiserror 1.0.69", +] + +[[package]] +name = "spacetimedb-metrics" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ededdd9866b3d47ec37d4673a00997c6fa608dc465084583cb8ac72df1d7d0" +dependencies = [ + "arrayvec", + "itertools 0.12.1", + "paste", + "prometheus", +] + +[[package]] +name = "spacetimedb-primitives" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55af71f2ccb753957ad47b19648481bd67ae458885f18df867a4d5b0a55c8c67" +dependencies = [ + "bitflags", + "either", + "enum-as-inner", + "itertools 0.12.1", + "nohash-hasher", +] + +[[package]] +name = "spacetimedb-rust-transfer-sim" +version = "0.1.0" +dependencies = [ + "bytes", + "futures", + "http", + "humantime", + "itertools 0.14.0", + "log", + "rand", + "rand_distr", + "spacetimedb-client-api-messages", + "spacetimedb-lib", + "thiserror 1.0.69", + "tokio", + "tokio-tungstenite", +] + +[[package]] +name = "spacetimedb-sats" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a89afd9f4eded852e7355102f66f8ff346d25fe903d38ef0b6a171d32d696a" +dependencies = [ + "anyhow", + "arrayvec", + "bitflags", + "bytemuck", + "bytes", + "bytestring", + "chrono", + "decorum", + "derive_more", + "enum-as-inner", + "ethnum", + "hex", + "itertools 0.12.1", + "second-stack", + "serde", + "sha3", + "smallvec", + "spacetimedb-bindings-macro", + "spacetimedb-metrics", + "spacetimedb-primitives", + "thiserror 1.0.69", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +dependencies = [ + "fastrand", + "getrandom 0.4.1", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489a59b6730eda1b0171fcfda8b121f4bee2b35cba8645ca35c5f7ba3eb736c1" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite", +] + +[[package]] +name = "tungstenite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand", + "sha1", + "thiserror 2.0.18", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d7d0fce354c88b7982aec4400b3e7fcf723c32737cef571bd165f7613557ee" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55839b71ba921e4f75b674cb16f843f4b1f3b26ddfcb3454de1cf65cc021ec0f" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf2e969c2d60ff52e7e98b7392ff1588bffdd1ccd4769eba27222fd3d621571" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0861f0dcdf46ea819407495634953cdcc8a8c7215ab799a7a7ce366be71c7b30" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.13.0", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/templates/keynote-2/spacetimedb-rust-client/Cargo.toml b/templates/keynote-2/spacetimedb-rust-client/Cargo.toml new file mode 100644 index 000000000..c5cbdd345 --- /dev/null +++ b/templates/keynote-2/spacetimedb-rust-client/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "spacetimedb-rust-transfer-sim" +version = "0.1.0" +edition = "2024" +description = "Client simulating of transfers and benchmarking TPS for SpacetimeDB" + +[dependencies] +spacetimedb-lib.workspace = true +spacetimedb-client-api-messages.workspace = true +http.workspace = true +thiserror.workspace = true +rand.workspace = true +rand_distr.workspace = true +tokio.workspace = true +tokio-tungstenite.workspace = true +log.workspace = true +bytes.workspace = true +futures.workspace = true +itertools.workspace = true +humantime.workspace = true +clap.workspace = true diff --git a/templates/keynote-2/spacetimedb-rust-client/src/main.rs b/templates/keynote-2/spacetimedb-rust-client/src/main.rs new file mode 100644 index 000000000..dfb430608 --- /dev/null +++ b/templates/keynote-2/spacetimedb-rust-client/src/main.rs @@ -0,0 +1,332 @@ +#![allow(clippy::disallowed_macros)] + +mod websocket; + +use crate::websocket::{Recv, Send, ServerMessage, WsParams}; +use clap::{Args, Parser, Subcommand}; +use core::sync::atomic::{AtomicU64, Ordering}; +use humantime::{format_duration, parse_duration}; +use rand::{SeedableRng as _, distr::Distribution, rngs::SmallRng}; +use rand_distr::Zipf; +use spacetimedb_client_api_messages::websocket::v1::{CallReducer, CallReducerFlags, ClientMessage, Compression}; +use spacetimedb_lib::bsatn; +use std::path::Path; +use std::sync::Arc; +use std::time::Instant; +use std::{fs, thread}; +use tokio::runtime::{self, Handle, Runtime}; +use tokio::task::JoinHandle; + +const LOCALHOST: &str = "http://localhost:3000"; +const MODULE: &str = "sim"; + +const DURATION: &str = "5s"; +const WARMUP_DURATION: &str = "5s"; +const ALPHA: f32 = 0.5; +const CONNECTIONS: usize = 10; +const INIT_BALANCE: i64 = 1_000_000; +const AMOUNT: u32 = 1; +const ACCOUNTS: u32 = 100_000; +const CONFIRMED_READS: bool = false; +// Max inflight reducer calls imposed by the server. +const MAX_INFLIGHT_REDUCERS: u64 = 16384; + +// When called from within an async context, return a handle to it (and no +// `Runtime`), otherwise create a fresh `Runtime` and return it along with a +// handle to it. +fn enter_or_create_runtime(connections: usize) -> (Option, runtime::Handle) { + match runtime::Handle::try_current() { + Err(e) if e.is_missing_context() => { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .worker_threads(connections) + .thread_name("spacetimedb-background-connection") + .build() + .unwrap(); + let handle = rt.handle().clone(); + + (Some(rt), handle) + } + Ok(handle) => (None, handle), + Err(_) => unimplemented!(), + } +} + +async fn init_conn(cli: &Common, handle: &Handle) -> (JoinHandle<()>, Recv, Send) { + let server: &str = &cli.server; + let uri = server.try_into().unwrap(); + let params = WsParams { + compression: Compression::None, + light: true, + confirmed: cli.confirmed_reads.into(), + }; + + let conn = websocket::WsConnection::connect(uri, &cli.module, None, None, params) + .await + .unwrap(); + + let (jh, mut rx, tx) = conn.spawn_message_loop(handle); + + let init = rx.recv().await.unwrap(); + assert_eq!(init, ServerMessage::IdentityToken); + + (jh, rx, tx) +} + +fn pick_two_distinct(mut pick: impl FnMut() -> u32, max_spins: usize) -> (u32, u32) { + let a = pick(); + let mut b = pick(); + + let mut spins = 0; + while a == b && spins < max_spins { + b = pick(); + spins += 1; + } + + (a, b) +} + +fn make_transfers(accounts: u32, alpha: f32) -> Vec<(u32, u32)> { + let dist = Zipf::new(accounts as f32, alpha).unwrap(); + let mut rng = SmallRng::seed_from_u64(0x12345678); + (0..10_000_000) + .filter_map(|_| { + let (from, to) = pick_two_distinct(|| dist.sample(&mut rng) as u32, 32); + if from >= accounts || to >= accounts || from == to { + None + } else { + Some((from, to)) + } + }) + .collect() +} + +fn seed(cli: &Common, seed: &Seed) { + let (_runtime, handle) = enter_or_create_runtime(1); + let (jh, mut rx, tx) = handle.block_on(init_conn(cli, &handle)); + + let args = (cli.accounts, seed.initial_balance); + let args = bsatn::to_vec(&args).unwrap().into(); + tx.send(ClientMessage::CallReducer(CallReducer { + reducer: "seed".into(), + args, + request_id: 0, + flags: CallReducerFlags::FullUpdate, + })) + .unwrap(); + + let reply = rx.blocking_recv().unwrap(); + assert_eq!(reply, ServerMessage::TransactionUpdate); + + if !cli.quiet { + println!("done seeding"); + } + + jh.abort(); +} + +fn bench(cli: &Common, bench: &Bench) { + let (_runtime, handle) = enter_or_create_runtime(bench.connections); + + // Dump some config parameters. + let alpha = bench.alpha; + let accounts = cli.accounts; + let amount = bench.amount; + if !cli.quiet { + println!("Benchmark parameters:"); + println!("alpha={alpha}, amount = {amount}, accounts = {accounts}"); + println!("max inflight reducers = {}", bench.max_inflight_reducers); + println!(); + } + + // Parse the durations. + let duration = parse_duration(&bench.duration).expect("invalid duration passed"); + let warmup_duration = parse_duration(&bench.warmup_duration).expect("invalid warmup duration passed"); + + // Initialize connections. + let connections = bench.connections; + let confirmed_reads = cli.confirmed_reads; + if !cli.quiet { + println!("initializing {connections} connections with confirmed-reads={confirmed_reads}"); + } + let (join_handles, conns): (Vec<_>, Vec<_>) = (0..connections) + .map(|_| { + let (jh, tx, rx) = handle.block_on(init_conn(cli, &handle)); + (jh, (tx, rx)) + }) + .unzip(); + + // Pre-compute transfer pairs. + let transfer_pairs = &make_transfers(accounts, alpha); + let transfers_per_worker = transfer_pairs.len() / conns.len(); + + let warmup_start_all = Instant::now(); + let mut start_all = warmup_start_all; + let barrier = &std::sync::Barrier::new(conns.len()); + let completed = Arc::new(AtomicU64::default()); + + thread::scope(|scope| { + if !cli.quiet { + eprintln!("warming up for {}...", format_duration(warmup_duration)); + } + let mut start_all = Some(&mut start_all); + for (worker_idx, (mut rx, tx)) in conns.into_iter().enumerate() { + let completed = completed.clone(); + let start_all = start_all.take(); + scope.spawn(move || { + let mut run = || { + let mut transfers = 0; + let mut transfer_idx = worker_idx * transfers_per_worker; + + while transfers < bench.max_inflight_reducers { + let (from, to) = match transfer_pairs.get(transfer_idx) { + Some(x) => *x, + None => { + transfer_idx = 0; + transfer_pairs[transfer_idx] + } + }; + transfer_idx += 1; + + let args = (from, to, amount); + let args = bsatn::to_vec(&args).unwrap().into(); + tx.send(ClientMessage::CallReducer(CallReducer { + reducer: "transfer".into(), + args, + request_id: 0, + flags: CallReducerFlags::FullUpdate, + })) + .unwrap(); + transfers += 1; + } + + // Block until all confirmations arrived. + let mut recorded_transfers = 0; + while recorded_transfers < transfers { + match rx.blocking_recv() { + None => unreachable!(), + Some(ServerMessage::TransactionUpdate) => {} + Some(_) => continue, + } + recorded_transfers += 1; + } + + transfers + }; + + while warmup_start_all.elapsed() < warmup_duration { + run(); + } + + if barrier.wait().is_leader() && !cli.quiet { + eprintln!("finished warmup..."); + eprintln!("benchmarking for {}...", format_duration(duration)); + } + let start = Instant::now(); + if let Some(start_all) = start_all { + *start_all = start; + } + + while start.elapsed() < duration { + let transfers = run(); + completed.fetch_add(transfers, Ordering::Relaxed); + } + }); + } + }); + + let completed = completed.load(Ordering::Relaxed); + let elapsed = start_all.elapsed().as_secs_f64(); + let tps = completed as f64 / elapsed; + + if !cli.quiet { + println!("ran for {elapsed} seconds"); + println!("completed {completed}"); + println!("throughput was {tps} TPS"); + } + + if let Some(path) = bench.tps_write_path.as_deref() { + let path = Path::new(path); + fs::write(path, format!("{tps}")).expect("Failed to write TPS to file {path}"); + } + + for handle in join_handles { + handle.abort(); + } +} + +#[derive(Parser)] +#[command(about)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Args)] +struct Common { + #[arg(short, long, default_value_t = false)] + quiet: bool, + + #[arg(short, long, default_value = LOCALHOST)] + server: String, + + #[arg(short, long, default_value = MODULE)] + module: String, + + #[arg(long, default_value_t = CONFIRMED_READS)] + confirmed_reads: bool, + + #[arg(long, default_value_t = ACCOUNTS)] + accounts: u32, +} + +#[derive(Subcommand)] +enum Commands { + Seed(Seed), + Bench(Bench), +} + +#[derive(Args)] +struct Seed { + #[command(flatten)] + common: Common, + + #[arg(short, long, default_value_t = INIT_BALANCE)] + initial_balance: i64, +} + +#[derive(Args)] +struct Bench { + #[command(flatten)] + common: Common, + + #[arg(short, long, default_value_t = ALPHA)] + alpha: f32, + + #[arg(long, default_value_t = AMOUNT)] + amount: u32, + + #[arg(short, long, default_value_t = CONNECTIONS)] + connections: usize, + + #[arg(long, default_value_t = MAX_INFLIGHT_REDUCERS)] + max_inflight_reducers: u64, + + #[arg(short, long, default_value = DURATION)] + duration: String, + + #[arg(short, long, default_value = WARMUP_DURATION)] + warmup_duration: String, + + #[arg(short, long)] + tps_write_path: Option, +} + +fn main() { + let cli = Cli::parse(); + + match &cli.command { + Commands::Seed(seed_args) => seed(&seed_args.common, seed_args), + Commands::Bench(bench_args) => bench(&bench_args.common, bench_args), + } +} diff --git a/templates/keynote-2/spacetimedb-rust-client/src/websocket.rs b/templates/keynote-2/spacetimedb-rust-client/src/websocket.rs new file mode 100644 index 000000000..f955b80aa --- /dev/null +++ b/templates/keynote-2/spacetimedb-rust-client/src/websocket.rs @@ -0,0 +1,422 @@ +//! Low-level WebSocket plumbing. +//! +//! This module is internal, and may incompatibly change without warning. + +use bytes::Bytes; +use futures::{SinkExt, TryStreamExt}; +use http::uri::{InvalidUri, Scheme, Uri}; +use spacetimedb_client_api_messages::websocket::common::SERVER_MSG_COMPRESSION_TAG_NONE; +use spacetimedb_client_api_messages::websocket::v1::{BIN_PROTOCOL, ClientMessage, Compression}; +use spacetimedb_lib::de::Deserialize; +use spacetimedb_lib::{ConnectionId, bsatn}; +use std::mem; +use std::sync::Arc; +use std::time::Duration; +use thiserror::Error; +use tokio::sync::mpsc; +use tokio::task::JoinHandle; +use tokio::time::Instant; +use tokio::{net::TcpStream, runtime}; +use tokio_tungstenite::{ + MaybeTlsStream, WebSocketStream, connect_async_with_config, + tungstenite::client::IntoClientRequest, + tungstenite::protocol::{Message as WebSocketMessage, WebSocketConfig}, +}; + +fn decompress_server_message(raw: &[u8]) -> &[u8] { + match raw { + [SERVER_MSG_COMPRESSION_TAG_NONE, bytes @ ..] => bytes, + _ => unimplemented!(), + } +} + +#[derive(Error, Debug, Clone)] +pub enum UriError { + #[error("Unknown URI scheme {scheme}, expected http, https, ws or wss")] + UnknownUriScheme { scheme: String }, + + #[error("Expected a URI without a query part, but found {query}")] + UnexpectedQuery { query: String }, + + #[error(transparent)] + InvalidUri { + // `Arc` is required for `Self: Clone`, as `http::uri::InvalidUri: !Clone`. + source: Arc, + }, + + #[error(transparent)] + InvalidUriParts { + // `Arc` is required for `Self: Clone`, as `http::uri::InvalidUriParts: !Clone`. + source: Arc, + }, +} + +#[derive(Error, Debug, Clone)] +pub enum WsError { + #[error(transparent)] + UriError(#[from] UriError), + + #[error("Error in WebSocket connection with {uri}: {source}")] + Tungstenite { + uri: Uri, + #[source] + // `Arc` is required for `Self: Clone`, as `tungstenite::Error: !Clone`. + source: Arc, + }, + + #[error("Failed to deserialize WebSocket message: {source}")] + DeserializeMessage { + #[source] + source: bsatn::DecodeError, + }, +} + +pub struct WsConnection { + sock: WebSocketStream>, +} + +fn parse_scheme(scheme: Option) -> Result { + Ok(match scheme { + Some(s) => match s.as_str() { + "ws" | "wss" => s, + "http" => "ws".parse().unwrap(), + "https" => "wss".parse().unwrap(), + unknown_scheme => { + return Err(UriError::UnknownUriScheme { + scheme: unknown_scheme.into(), + }); + } + }, + None => "ws".parse().unwrap(), + }) +} + +#[derive(Clone, Copy, Default)] +pub struct WsParams { + pub compression: Compression, + pub light: bool, + /// `Some(true)` to enable confirmed reads for the connection, + /// `Some(false)` to disable them. + /// `None` to not set the parameter and let the server choose. + pub confirmed: Option, +} + +pub fn make_uri( + host: Uri, + db_name: &str, + connection_id: Option, + params: WsParams, +) -> Result { + let mut parts = host.into_parts(); + let scheme = parse_scheme(parts.scheme.take())?; + parts.scheme = Some(scheme); + let mut path = if let Some(path_and_query) = parts.path_and_query { + if let Some(query) = path_and_query.query() { + return Err(UriError::UnexpectedQuery { query: query.into() }); + } + path_and_query.path().to_string() + } else { + "/".to_string() + }; + + // Normalize the path, ensuring it ends with `/`. + if !path.ends_with('/') { + path.push('/'); + } + + path.push_str("v1/database/"); + path.push_str(db_name); + path.push_str("/subscribe"); + + // Specify the desired compression for host->client replies. + match params.compression { + Compression::None => path.push_str("?compression=None"), + Compression::Gzip => path.push_str("?compression=Gzip"), + // The host uses the same default as the sdk, + // but in case this changes, we prefer to be explicit now. + Compression::Brotli => path.push_str("?compression=Brotli"), + }; + + // Provide the connection ID if the client provided one. + if let Some(cid) = connection_id { + // If a connection ID is provided, append it to the path. + path.push_str("&connection_id="); + path.push_str(&cid.to_hex()); + } + + // Specify the `light` mode if requested. + if params.light { + path.push_str("&light=true"); + } + + // Enable confirmed reads if requested. + if let Some(confirmed) = params.confirmed { + path.push_str("&confirmed="); + path.push_str(if confirmed { "true" } else { "false" }); + } + + parts.path_and_query = Some(path.parse().map_err(|source: InvalidUri| UriError::InvalidUri { + source: Arc::new(source), + })?); + Uri::from_parts(parts).map_err(|source| UriError::InvalidUriParts { + source: Arc::new(source), + }) +} + +// Tungstenite doesn't offer an interface to specify a WebSocket protocol, which frankly +// seems like a pretty glaring omission in its API. In order to insert our own protocol +// header, we manually the `Request` constructed by +// `tungstenite::IntoClientRequest::into_client_request`. + +// TODO: `core` uses [Hyper](https://docs.rs/hyper/latest/hyper/) as its HTTP library +// rather than having Tungstenite manage its own connections. Should this library do +// the same? + +fn make_request( + host: Uri, + db_name: &str, + token: Option<&str>, + connection_id: Option, + params: WsParams, +) -> Result, WsError> { + let uri = make_uri(host, db_name, connection_id, params)?; + let mut req = IntoClientRequest::into_client_request(uri.clone()).map_err(|source| WsError::Tungstenite { + uri, + source: Arc::new(source), + })?; + request_insert_protocol_header(&mut req); + request_insert_auth_header(&mut req, token); + Ok(req) +} + +fn request_insert_protocol_header(req: &mut http::Request<()>) { + req.headers_mut().insert( + http::header::SEC_WEBSOCKET_PROTOCOL, + const { http::HeaderValue::from_static(BIN_PROTOCOL) }, + ); +} + +fn request_insert_auth_header(req: &mut http::Request<()>, token: Option<&str>) { + if let Some(token) = token { + let auth = ["Bearer ", token].concat().try_into().unwrap(); + req.headers_mut().insert(http::header::AUTHORIZATION, auth); + } +} + +/// If `res` evaluates to `Err(e)`, log a warning in the form `"{}: {:?}", $cause, e`. +/// +/// Could be trivially written as a function, but macro-ifying it preserves the source location of the log. +macro_rules! maybe_log_error { + ($cause:expr, $res:expr) => { + if let Err(e) = $res { + log::warn!("{}: {:?}", $cause, e); + } + }; +} + +#[derive(Deserialize, PartialEq, Debug)] +pub enum ServerMessage { + /// Informs of changes to subscribed rows. + /// This will be removed when we switch to `SubscribeSingle`. + InitialSubscription, + /// Upon reducer run. + TransactionUpdate, + /// Upon reducer run, but limited to just the table updates. + TransactionUpdateLight, + /// After connecting, to inform client of its identity. + IdentityToken, + /// Return results to a one off SQL query. + OneOffQueryResponse, + /// Sent in response to a `SubscribeSingle` message. This contains the initial matching rows. + SubscribeApplied, + /// Sent in response to an `Unsubscribe` message. This contains the matching rows. + UnsubscribeApplied, + /// Communicate an error in the subscription lifecycle. + SubscriptionError, + /// Sent in response to a `SubscribeMulti` message. This contains the initial matching rows. + SubscribeMultiApplied, + /// Sent in response to an `UnsubscribeMulti` message. This contains the matching rows. + UnsubscribeMultiApplied, + /// Sent in response to a [`CallProcedure`] message. This contains the return value. + ProcedureResult, +} + +pub type Recv = mpsc::UnboundedReceiver; +pub type Send = mpsc::UnboundedSender>; + +impl WsConnection { + pub async fn connect( + host: Uri, + db_name: &str, + token: Option<&str>, + connection_id: Option, + params: WsParams, + ) -> Result { + let req = make_request(host, db_name, token, connection_id, params)?; + + // Grab the URI for error-reporting. + let uri = req.uri().clone(); + + let (sock, _): (WebSocketStream>, _) = connect_async_with_config( + req, + // TODO(kim): In order to be able to replicate module WASM blobs, + // `cloud-next` cannot have message / frame size limits. That's + // obviously a bad default for all other clients, though. + Some(WebSocketConfig::default().max_frame_size(None).max_message_size(None)), + false, + ) + .await + .map_err(|source| WsError::Tungstenite { + uri, + source: Arc::new(source), + })?; + Ok(WsConnection { sock }) + } + + fn parse_response(bytes: &[u8]) -> Result { + let bytes = decompress_server_message(bytes); + bsatn::from_slice(bytes).map_err(|source| WsError::DeserializeMessage { source }) + } + + fn encode_message(msg: ClientMessage) -> WebSocketMessage { + WebSocketMessage::Binary(bsatn::to_vec(&msg).unwrap().into()) + } + + async fn message_loop( + mut self, + incoming_messages: mpsc::UnboundedSender, + outgoing_messages: mpsc::UnboundedReceiver>, + ) { + // There is a small but plausible chance that a client's socket will not + // be notified that the remote end has closed the connection, e.g. + // because of the remote machine being power cycled, or middleboxes + // misbehaving. + // + // Unless the client uses dynamic subscriptions, it will only ever try + // to read from the socket, and thus not notice the connection closure. + // + // For certain types of clients it is crucial to eventually time out + // such connections, and attempt to reconnect. We don't, however, want + // to flood the server with `Ping` frames unnecessarily. + // + // Instead, we: + // + // * Check every `IDLE_TIMEOUT` whether some data has arrived. + // + // - If not, send a `Ping` frame. + // + // * Check after another `IDLE_TIMEOUT` whether data has arrived. + // + // - If not, and we were expecting a `Pong` response, consider the + // connection bad and exit the loop, thereby closing the socket. + // + // Note that the server also initiates `Ping`s, currently at `2 * IDLE_TIMEOUT`. + // If both ends cannot communicate, we assume the server has already + // timed out the client, and so don't bother sending a `Close` frame. + const IDLE_TIMEOUT: Duration = Duration::from_secs(30); + let mut idle_timeout_interval = tokio::time::interval_at(Instant::now() + IDLE_TIMEOUT, IDLE_TIMEOUT); + + let mut idle = true; + let mut want_pong = false; + + let mut outgoing_messages = Some(outgoing_messages); + loop { + tokio::select! { + incoming = self.sock.try_next() => match incoming { + Err(tokio_tungstenite::tungstenite::error::Error::ConnectionClosed) | Ok(None) => { + log::info!("Connection closed"); + break; + }, + + Err(e) => { + maybe_log_error!( + "Error reading message from read WebSocket stream", + Result::<(), _>::Err(e) + ); + break; + }, + + Ok(Some(WebSocketMessage::Binary(bytes))) => { + idle = false; + match Self::parse_response(&bytes) { + Err(e) => maybe_log_error!( + "Error decoding WebSocketMessage::Binary payload", + Result::<(), _>::Err(e) + ), + Ok(msg) => maybe_log_error!( + "Error sending decoded message to incoming_messages queue", + incoming_messages.send(msg) + ), + } + } + + Ok(Some(WebSocketMessage::Ping(_))) => { + log::trace!("received ping"); + idle = false; + // No need to explicitly respond with a `Pong`, + // as tungstenite handles this automatically. + // See [https://github.com/snapview/tokio-tungstenite/issues/88]. + }, + + Ok(Some(WebSocketMessage::Pong(_))) => { + log::trace!("received pong"); + idle = false; + want_pong = false; + }, + + Ok(Some(other)) => { + log::warn!("Unexpected WebSocket message {other:?}"); + idle = false; + }, + }, + + _ = idle_timeout_interval.tick() => { + if mem::replace(&mut idle, true) { + if want_pong { + // Nothing received while we were waiting for a pong. + log::warn!("Connection timed out"); + break; + } + + log::trace!("sending client ping"); + let ping = WebSocketMessage::Ping(Bytes::new()); + if let Err(e) = self.sock.send(ping).await { + log::warn!("Error sending ping: {e:?}"); + break; + } + want_pong = true; + } + }, + + // this is stupid. we want to handle the channel close *once*, and then disable this branch + Some(outgoing) = async { Some(outgoing_messages.as_mut()?.recv().await) } => match outgoing { + Some(outgoing) => { + let msg = Self::encode_message(outgoing); + if let Err(e) = self.sock.send(msg).await { + log::warn!("Error sending outgoing message: {e:?}"); + break; + } + } + None => { + maybe_log_error!("Error sending close frame", SinkExt::close(&mut self.sock).await); + outgoing_messages = None; + } + }, + } + } + } + + pub fn spawn_message_loop( + self, + runtime: &runtime::Handle, + ) -> ( + JoinHandle<()>, + mpsc::UnboundedReceiver, + mpsc::UnboundedSender>, + ) { + let (outgoing_send, outgoing_recv) = mpsc::unbounded_channel(); + let (incoming_send, incoming_recv) = mpsc::unbounded_channel(); + let handle = runtime.spawn(self.message_loop(incoming_send, outgoing_recv)); + (handle, incoming_recv, outgoing_send) + } +} diff --git a/templates/keynote-2/src/core/runner.ts b/templates/keynote-2/src/core/runner.ts index e1afad98c..f61efff28 100644 --- a/templates/keynote-2/src/core/runner.ts +++ b/templates/keynote-2/src/core/runner.ts @@ -8,6 +8,25 @@ import { RunResult } from './types.ts'; const OP_TIMEOUT_MS = Number(process.env.BENCH_OP_TIMEOUT_MS ?? '15000'); const MIN_OP_TIMEOUT_MS = Number(process.env.MIN_OP_TIMEOUT_MS ?? '250'); const TAIL_SLACK_MS = Number(process.env.TAIL_SLACK_MS ?? '1000'); +const DEFAULT_PRECOMPUTED_TRANSFER_PAIRS = 10_000_000; + +function precomputeZipfTransferPairs( + accounts: number, + alpha: number, + count: number, +): { from: Uint32Array; to: Uint32Array; count: number } { + const pick = zipfSampler(accounts, alpha); + const from = new Uint32Array(count); + const to = new Uint32Array(count); + + for (let i = 0; i < count; i++) { + const [a, b] = pickTwoDistinct(pick); + from[i] = a; + to[i] = b; + } + + return { from, to, count }; +} async function withOpTimeout( promise: Promise, @@ -121,7 +140,28 @@ export async function runOne({ } } - const pick = zipfSampler(accounts, alpha); + const precomputedPairsRaw = Number( + process.env.BENCH_PRECOMPUTED_TRANSFER_PAIRS ?? + DEFAULT_PRECOMPUTED_TRANSFER_PAIRS, + ); + const precomputedPairs = Number.isFinite(precomputedPairsRaw) + ? Math.max(1, Math.floor(precomputedPairsRaw)) + : DEFAULT_PRECOMPUTED_TRANSFER_PAIRS; + + console.log( + `[${connector.name}] precomputing ${precomputedPairs} Zipf transfer pairs...`, + ); + const precomputeStart = performance.now(); + const transferPairs = precomputeZipfTransferPairs( + accounts, + alpha, + precomputedPairs, + ); + const precomputeElapsedMs = performance.now() - precomputeStart; + console.log( + `[${connector.name}] precomputed ${transferPairs.count} pairs in ${(precomputeElapsedMs / 1000).toFixed(2)}s`, + ); + const start = performance.now(); const endAt = start + seconds * 1000; @@ -153,6 +193,22 @@ export async function runOne({ async function worker(workerIndex: number) { const conn = workers[workerIndex]; + const pairsPerWorker = Math.max( + 1, + Math.floor(transferPairs.count / concurrency), + ); + let pairIndex = workerIndex * pairsPerWorker; + + const nextTransferPair = (): [number, number] => { + if (pairIndex >= transferPairs.count) { + pairIndex = 0; + } + + const from = transferPairs.from[pairIndex]!; + const to = transferPairs.to[pairIndex]!; + pairIndex++; + return [from, to]; + }; // non-pipelined if (!PIPELINED) { @@ -166,7 +222,7 @@ export async function runOne({ Math.min(OP_TIMEOUT_MS, timeLeft + TAIL_SLACK_MS), ); - const [from, to] = pickTwoDistinct(pick); + const [from, to] = nextTransferPair(); collisionTracker.begin(from); collisionTracker.begin(to); @@ -213,7 +269,7 @@ export async function runOne({ const unlimitedInflight = !Number.isFinite(MAX_INFLIGHT_PER_WORKER); const launchOp = (dynamicTimeout: number) => { - const [from, to] = pickTwoDistinct(pick); + const [from, to] = nextTransferPair(); collisionTracker.begin(from); collisionTracker.begin(to); diff --git a/templates/keynote-2/src/demo.ts b/templates/keynote-2/src/demo.ts index e962b8ec6..7a4a27e61 100644 --- a/templates/keynote-2/src/demo.ts +++ b/templates/keynote-2/src/demo.ts @@ -7,6 +7,7 @@ import { CONNECTORS } from './connectors'; import { runOne } from './core/runner'; import { initConvex } from './init/init_convex'; import { sh } from './init/utils'; +import * as fs from 'fs'; // Simple TCP ping - just check if something is listening on the port function ping(port: number, timeoutMs = 2000): Promise { @@ -69,8 +70,8 @@ function hasFlag(name: string): boolean { } const seconds = getArg('seconds', 10); -const concurrency = getArg('concurrency', 50); -const alpha = getArg('alpha', 1.5); +const concurrency = getArg('concurrency', 10); +const alpha = getArg('alpha', 0.5); const systems = getStringArg('systems', 'convex,spacetimedb') .split(',') .map((s) => s.trim()); @@ -235,6 +236,7 @@ async function prepSystem(system: string): Promise { if (system === 'spacetimedb') { const moduleName = process.env.STDB_MODULE || 'test-1'; const server = process.env.STDB_SERVER || 'local'; + const server2 = process.env.STDB_SERVER || 'http://localhost:3000'; const modulePath = process.env.STDB_MODULE_PATH || './spacetimedb'; // Publish module (creates DB if needed, updates if exists) @@ -246,15 +248,24 @@ async function prepSystem(system: string): Promise { '--module-path', modulePath, ]); - await sh('spacetime', [ - 'call', + await sh('cargo', [ + 'run', + //"--quiet", + "--manifest-path", + "spacetimedb-rust-client/Cargo.toml", + "--", + "seed", + //"--quiet", '--server', - server, + server2, + "--module", moduleName, - 'seed', + "--accounts", String(accounts), + "--initial-balance", String(initialBalance), ]); + console.log('[spacetimedb] seed complete.'); } else if (system === 'convex') { await initConvex(); } else { @@ -276,11 +287,9 @@ async function prepSystem(system: string): Promise { interface BenchResult { system: string; tps: number; - p50_ms: number; - p99_ms: number; } -async function runBenchmark(system: string): Promise { +async function runBenchmarkOther(system: string): Promise { const connectorFactory = (CONNECTORS as any)[system]; if (!connectorFactory) { console.log(` ${system}: Unknown connector`); @@ -303,11 +312,56 @@ async function runBenchmark(system: string): Promise { return { system, tps: Math.round(result.tps), - p50_ms: result.p50_ms, - p99_ms: result.p99_ms, }; } +async function runBenchmarkStdb(): Promise { + const moduleName = process.env.STDB_MODULE || 'test-1'; + const server2 = process.env.STDB_SERVER || 'http://localhost:3000'; + + await sh('cargo', [ + 'run', + //"--quiet", + "--manifest-path", + "spacetimedb-rust-client/Cargo.toml", + "--", + "bench", + //"--quiet", + '--server', + server2, + "--module", + moduleName, + "--duration", + `${seconds}s`, + "--connections", + String(concurrency), + "--alpha", + String(alpha), + "--tps-write-path", + "spacetimedb-tps.tmp.log", + ]); + + const tpsStr = fs.readFileSync("spacetimedb-tps.tmp.log", 'utf-8').trim(); + const tps = Number(tpsStr); + if (isNaN(tps)) { + console.warn(`[spacetimedb] Failed to parse TPS from file: ${tpsStr}`); + return null; + } + + return { + system: "spacetimedb", + tps: Math.round(tps), + }; +} + +async function runBenchmark(system: string): Promise { + if (system === 'spacetimedb') { + return await runBenchmarkStdb(); + } else { + return await runBenchmarkOther(system); + } +} + // ============================================================================ // Display // ============================================================================ @@ -381,11 +435,11 @@ async function displayResults(results: BenchResult[]): Promise { console.log(' ' + c('cyan', '║') + ' '.repeat(boxWidth) + c('cyan', '║')); console.log( ' ' + - c('cyan', '║') + - ' '.repeat(msgPadding) + - c('bold', c('green', msgWithEmoji)) + - ' '.repeat(rightPadding) + - c('cyan', '║'), + c('cyan', '║') + + ' '.repeat(msgPadding) + + c('bold', c('green', msgWithEmoji)) + + ' '.repeat(rightPadding) + + c('cyan', '║'), ); console.log(' ' + c('cyan', '║') + ' '.repeat(boxWidth) + c('cyan', '║')); console.log(' ' + c('cyan', '╚' + '═'.repeat(boxWidth) + '╝')); @@ -438,8 +492,8 @@ async function main() { } else { console.log( '\n' + - c('bold', ' [2/4] Preparing databases...') + - c('dim', ' (skipped)\n'), + c('bold', ' [2/4] Preparing databases...') + + c('dim', ' (skipped)\n'), ); } @@ -478,8 +532,6 @@ async function main() { results: results.map((r) => ({ system: r.system, tps: r.tps, - p50_ms: r.p50_ms, - p99_ms: r.p99_ms, })), }, null,