diff --git a/Cargo.lock b/Cargo.lock index 2e8ca830..3823d405 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,12 +62,54 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -96,7 +138,7 @@ dependencies = [ "duplicate", "git-testament", "glommio", - "hashbrown 0.14.3", + "hashbrown 0.14.2", "hex", "hwloc", "indexmap 2.1.0", @@ -108,7 +150,7 @@ dependencies = [ "rustls-pemfile", "serde", "simple_logger", - "toml", + "toml 0.5.11", ] [[package]] @@ -159,7 +201,7 @@ dependencies = [ "futures-lite", "futures-rustls", "glommio", - "hashbrown 0.14.3", + "hashbrown 0.14.2", "log", "mimalloc", "quickcheck", @@ -190,6 +232,24 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "aquatic_load_tester" +version = "0.8.0" +dependencies = [ + "anyhow", + "aquatic_udp", + "aquatic_udp_load_test", + "clap 4.4.8", + "indexmap 2.1.0", + "itertools 0.12.0", + "nonblock", + "once_cell", + "regex", + "serde", + "tempfile", + "toml 0.8.8", +] + [[package]] name = "aquatic_peer_id" version = "0.8.0" @@ -210,7 +270,7 @@ dependencies = [ "quickcheck", "quickcheck_macros", "serde", - "toml", + "toml 0.5.11", ] [[package]] @@ -236,7 +296,7 @@ dependencies = [ "constant_time_eq", "crossbeam-channel", "getrandom", - "hashbrown 0.14.3", + "hashbrown 0.14.2", "hdrhistogram", "hex", "io-uring", @@ -269,7 +329,7 @@ dependencies = [ "aquatic_common", "aquatic_toml_config", "aquatic_udp_protocol", - "hashbrown 0.14.3", + "hashbrown 0.14.2", "mimalloc", "mio", "quickcheck", @@ -309,7 +369,7 @@ dependencies = [ "futures-lite", "futures-rustls", "glommio", - "hashbrown 0.14.3", + "hashbrown 0.14.2", "httparse", "indexmap 2.1.0", "log", @@ -361,7 +421,7 @@ version = "0.8.0" dependencies = [ "anyhow", "criterion 0.5.1", - "hashbrown 0.14.3", + "hashbrown 0.14.2", "quickcheck", "quickcheck_macros", "serde", @@ -433,6 +493,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.5" @@ -598,21 +664,36 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.11" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] name = "clap_builder" -version = "4.4.11" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ + "anstream", "anstyle", "clap_lex 0.6.0", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.39", ] [[package]] @@ -630,12 +711,19 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "colored" -version = "2.1.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ + "is-terminal", "lazy_static", "windows-sys 0.48.0", ] @@ -699,7 +787,7 @@ dependencies = [ "ciborium", "clap 3.2.25", "criterion-plot", - "itertools", + "itertools 0.10.5", "lazy_static", "num-traits", "oorandom", @@ -722,10 +810,10 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.4.11", + "clap 4.4.8", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -746,7 +834,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -828,15 +916,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "deranged" -version = "0.3.10" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ "powerfmt", ] @@ -920,12 +1008,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -993,9 +1081,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -1139,9 +1227,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "git-testament" @@ -1233,9 +1321,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" dependencies = [ "ahash 0.8.6", "allocator-api2", @@ -1244,11 +1332,11 @@ dependencies = [ [[package]] name = "hdrhistogram" -version = "7.5.4" +version = "7.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +checksum = "a5b38e5c02b7c7be48c8dc5217c4f1634af2ea221caae2e024bffc7a7651c691" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", "crossbeam-channel", "flate2", @@ -1296,9 +1384,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", @@ -1357,9 +1445,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.5.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1382,7 +1470,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.2", ] [[package]] @@ -1439,17 +1527,26 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] @@ -1558,9 +1655,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" @@ -1646,7 +1743,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a4964177ddfdab1e3a2b37aec7cf320e14169abb0ed73999f558136409178d5" dependencies = [ - "base64", + "base64 0.21.5", "hyper", "indexmap 1.9.3", "ipnet", @@ -1713,9 +1810,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "log", @@ -1777,6 +1874,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonblock" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51c7a4f22e5f2e2bd805d6ab56f1ae87eb1815673e1b452048896fb687a8a3d4" +dependencies = [ + "libc", +] + [[package]] name = "num" version = "0.1.42" @@ -1859,9 +1965,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "oorandom" @@ -1921,9 +2027,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" @@ -1993,9 +2099,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" [[package]] name = "powerfmt" @@ -2055,9 +2161,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -2228,9 +2334,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "ring" -version = "0.17.7" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" dependencies = [ "cc", "getrandom", @@ -2257,22 +2363,22 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "9ad981d6c340a49cdc40a1028d9c6084ec7e9fa33fcb839cab656a267071e234" dependencies = [ "bitflags 2.4.1", - "errno 0.3.8", + "errno 0.3.7", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" dependencies = [ "log", "ring", @@ -2286,7 +2392,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.5", ] [[package]] @@ -2307,9 +2413,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -2344,9 +2450,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.193" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] @@ -2372,9 +2478,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", @@ -2392,6 +2498,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +dependencies = [ + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2445,14 +2560,14 @@ checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" [[package]] name = "simple_logger" -version = "4.3.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0ca6504625ee1aa5fda33913d2005eab98c7a42dd85f116ecce3ff54c9d3ef" +checksum = "2230cd5c29b815c9b699fb610b49a5ed65588f3509d9f0108be3a885da629333" dependencies = [ "colored", "log", "time", - "windows-sys 0.48.0", + "windows-sys 0.42.0", ] [[package]] @@ -2478,9 +2593,9 @@ dependencies = [ [[package]] name = "slotmap" -version = "1.0.7" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" dependencies = [ "version_check", ] @@ -2548,6 +2663,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.109" @@ -2677,9 +2798,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "libc", @@ -2698,6 +2819,40 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.1.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -2737,9 +2892,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tungstenite" @@ -2768,9 +2923,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" @@ -2795,9 +2950,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -2816,6 +2971,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "value-trait" version = "0.6.1" @@ -2867,9 +3028,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2877,9 +3038,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", @@ -2892,9 +3053,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2902,9 +3063,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", @@ -2915,15 +3076,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" dependencies = [ "js-sys", "wasm-bindgen", @@ -2972,6 +3133,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -3020,6 +3196,12 @@ dependencies = [ "windows_x86_64_msvc 0.52.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3032,6 +3214,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3044,6 +3232,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3056,6 +3250,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3068,6 +3268,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3080,6 +3286,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3092,6 +3304,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3104,6 +3322,15 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "winnow" +version = "0.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" +dependencies = [ + "memchr", +] + [[package]] name = "yansi" version = "0.5.1" @@ -3112,9 +3339,9 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zerocopy" -version = "0.7.30" +version = "0.7.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306dca4455518f1f31635ec308b6b3e4eb1b11758cefafc782827d0aa7acb5c7" +checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" dependencies = [ "byteorder", "zerocopy-derive", @@ -3122,9 +3349,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.7.30" +version = "0.7.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba" +checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index ff345efc..7be91d6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "crates/http", "crates/http_load_test", "crates/http_protocol", + "crates/load_tester", "crates/peer_id", "crates/toml_config", "crates/toml_config_derive", @@ -35,6 +36,7 @@ aquatic_toml_config = { version = "0.8.0", path = "./crates/toml_config" } aquatic_toml_config_derive = { version = "0.8.0", path = "./crates/toml_config_derive" } aquatic_udp_protocol = { version = "0.8.0", path = "./crates/udp_protocol" } aquatic_udp = { version = "0.8.0", path = "./crates/udp" } +aquatic_udp_load_test = { version = "0.8.0", path = "./crates/udp_load_test" } aquatic_ws_protocol = { version = "0.8.0", path = "./crates/ws_protocol" } aquatic_ws = { version = "0.8.0", path = "./crates/ws" } diff --git a/crates/common/src/access_list.rs b/crates/common/src/access_list.rs index 04e75329..459c5ca6 100644 --- a/crates/common/src/access_list.rs +++ b/crates/common/src/access_list.rs @@ -27,7 +27,7 @@ impl AccessListMode { } } -#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)] +#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize, Serialize)] #[serde(default, deny_unknown_fields)] pub struct AccessListConfig { pub mode: AccessListMode, diff --git a/crates/common/src/cpu_pinning.rs b/crates/common/src/cpu_pinning.rs index cd0d2199..870ea591 100644 --- a/crates/common/src/cpu_pinning.rs +++ b/crates/common/src/cpu_pinning.rs @@ -50,7 +50,7 @@ pub mod mod_name { use super::*; /// Experimental cpu pinning - #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)] + #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize, Serialize)] pub struct struct_name { pub active: bool, pub direction: CpuPinningDirection, diff --git a/crates/common/src/privileges.rs b/crates/common/src/privileges.rs index 9cb7db54..907d80bd 100644 --- a/crates/common/src/privileges.rs +++ b/crates/common/src/privileges.rs @@ -5,11 +5,11 @@ use std::{ use anyhow::Context; use privdrop::PrivDrop; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use aquatic_toml_config::TomlConfig; -#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)] +#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize, Serialize)] #[serde(default, deny_unknown_fields)] pub struct PrivilegeConfig { /// Chroot and switch group and user after binding to sockets diff --git a/crates/load_tester/Cargo.toml b/crates/load_tester/Cargo.toml new file mode 100644 index 00000000..44c2ac5c --- /dev/null +++ b/crates/load_tester/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "aquatic_load_tester" +description = "Load test runner for aquatic BitTorrent tracker" +keywords = ["peer-to-peer", "torrent", "bittorrent"] +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +readme.workspace = true + +[[bin]] +name = "aquatic_load_tester" + +[features] +default = ["udp"] +udp = ["aquatic_udp", "aquatic_udp_load_test"] + +[dependencies] +aquatic_udp = { optional = true, workspace = true } +aquatic_udp_load_test = { optional = true, workspace = true } + +anyhow = "1" +clap = { version = "4", features = ["derive"] } +indexmap = "2" +itertools = "0.12" +nonblock = "0.2" +once_cell = "1" +regex = "1" +serde = "1" +tempfile = "3" +toml = "0.8" + +[dev-dependencies] \ No newline at end of file diff --git a/crates/load_tester/src/common.rs b/crates/load_tester/src/common.rs new file mode 100644 index 00000000..3cb4e116 --- /dev/null +++ b/crates/load_tester/src/common.rs @@ -0,0 +1,258 @@ +use std::{fmt::Display, ops::Range, thread::available_parallelism}; + +use itertools::Itertools; + +#[derive(Debug, Clone)] +pub struct TaskSetCpuList(pub Vec); + +impl TaskSetCpuList { + pub fn as_cpu_list(&self) -> String { + let indicator = self.0.iter().map(|indicator| match indicator { + TaskSetCpuIndicator::Single(i) => i.to_string(), + TaskSetCpuIndicator::Range(range) => { + format!( + "{}-{}", + range.start, + range.clone().into_iter().last().unwrap() + ) + } + }); + + Itertools::intersperse_with(indicator, || ",".to_string()) + .into_iter() + .collect() + } + + pub fn new( + mode: CpuMode, + direction: CpuDirection, + requested_cpus: usize, + ) -> anyhow::Result { + let available_parallelism: usize = available_parallelism()?.into(); + + Ok(Self::new_with_available_parallelism( + available_parallelism, + mode, + direction, + requested_cpus, + )) + } + + fn new_with_available_parallelism( + available_parallelism: usize, + mode: CpuMode, + direction: CpuDirection, + requested_cpus: usize, + ) -> Self { + match direction { + CpuDirection::Asc => match mode { + CpuMode::Split => { + let middle = available_parallelism / 2; + + let range_a = 0..(middle.min(requested_cpus)); + let range_b = middle..(available_parallelism.min(middle + requested_cpus)); + + Self(vec![ + range_a.try_into().unwrap(), + range_b.try_into().unwrap(), + ]) + } + CpuMode::All => { + let range = 0..(available_parallelism.min(requested_cpus)); + + Self(vec![range.try_into().unwrap()]) + } + }, + CpuDirection::Desc => match mode { + CpuMode::Split => { + let middle = available_parallelism / 2; + + let range_a = middle.saturating_sub(requested_cpus)..middle; + let range_b = available_parallelism + .saturating_sub(requested_cpus) + .max(middle)..available_parallelism; + + Self(vec![ + range_a.try_into().unwrap(), + range_b.try_into().unwrap(), + ]) + } + CpuMode::All => { + let range = + available_parallelism.saturating_sub(requested_cpus)..available_parallelism; + + Self(vec![range.try_into().unwrap()]) + } + }, + } + } +} + +impl TryFrom>> for TaskSetCpuList { + type Error = String; + + fn try_from(value: Vec>) -> Result { + let mut output = Vec::new(); + + for range in value { + output.push(range.try_into()?); + } + + Ok(Self(output)) + } +} + +#[derive(Debug, Clone)] +pub enum TaskSetCpuIndicator { + Single(usize), + Range(Range), +} + +impl TryFrom> for TaskSetCpuIndicator { + type Error = String; + + fn try_from(value: Range) -> Result { + match value.len() { + 0 => Err("Empty ranges not supported".into()), + 1 => Ok(TaskSetCpuIndicator::Single(value.start)), + _ => Ok(TaskSetCpuIndicator::Range(value)), + } + } +} + +#[derive(Debug, Clone, Copy, clap::ValueEnum)] +pub enum CpuMode { + Split, + All, +} + +impl Display for CpuMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::All => f.write_str("all"), + Self::Split => f.write_str("split"), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum CpuDirection { + Asc, + Desc, +} + +pub fn simple_load_test_runs(cpu_mode: CpuMode, workers: &[usize]) -> Vec<(usize, TaskSetCpuList)> { + workers + .into_iter() + .copied() + .map(|workers| { + ( + workers, + TaskSetCpuList::new(cpu_mode, CpuDirection::Desc, workers).unwrap(), + ) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_task_set_cpu_list_split_asc() { + let f = TaskSetCpuList::new_with_available_parallelism; + + assert_eq!( + f(8, CpuMode::Split, CpuDirection::Asc, 1).as_cpu_list(), + "0,4" + ); + assert_eq!( + f(8, CpuMode::Split, CpuDirection::Asc, 2).as_cpu_list(), + "0-1,4-5" + ); + assert_eq!( + f(8, CpuMode::Split, CpuDirection::Asc, 4).as_cpu_list(), + "0-3,4-7" + ); + assert_eq!( + f(8, CpuMode::Split, CpuDirection::Asc, 8).as_cpu_list(), + "0-3,4-7" + ); + assert_eq!( + f(8, CpuMode::Split, CpuDirection::Asc, 9).as_cpu_list(), + "0-3,4-7" + ); + } + + #[test] + fn test_task_set_cpu_list_split_desc() { + let f = TaskSetCpuList::new_with_available_parallelism; + + assert_eq!( + f(8, CpuMode::Split, CpuDirection::Desc, 1).as_cpu_list(), + "3,7" + ); + assert_eq!( + f(8, CpuMode::Split, CpuDirection::Desc, 2).as_cpu_list(), + "2-3,6-7" + ); + assert_eq!( + f(8, CpuMode::Split, CpuDirection::Desc, 4).as_cpu_list(), + "0-3,4-7" + ); + assert_eq!( + f(8, CpuMode::Split, CpuDirection::Desc, 8).as_cpu_list(), + "0-3,4-7" + ); + assert_eq!( + f(8, CpuMode::Split, CpuDirection::Desc, 9).as_cpu_list(), + "0-3,4-7" + ); + } + + #[test] + fn test_task_set_cpu_list_all_asc() { + let f = TaskSetCpuList::new_with_available_parallelism; + + assert_eq!(f(8, CpuMode::All, CpuDirection::Asc, 1).as_cpu_list(), "0"); + assert_eq!( + f(8, CpuMode::All, CpuDirection::Asc, 2).as_cpu_list(), + "0-1" + ); + assert_eq!( + f(8, CpuMode::All, CpuDirection::Asc, 4).as_cpu_list(), + "0-3" + ); + assert_eq!( + f(8, CpuMode::All, CpuDirection::Asc, 8).as_cpu_list(), + "0-7" + ); + assert_eq!( + f(8, CpuMode::All, CpuDirection::Asc, 9).as_cpu_list(), + "0-7" + ); + } + + #[test] + fn test_task_set_cpu_list_all_desc() { + let f = TaskSetCpuList::new_with_available_parallelism; + + assert_eq!(f(8, CpuMode::All, CpuDirection::Desc, 1).as_cpu_list(), "7"); + assert_eq!( + f(8, CpuMode::All, CpuDirection::Desc, 2).as_cpu_list(), + "6-7" + ); + assert_eq!( + f(8, CpuMode::All, CpuDirection::Desc, 4).as_cpu_list(), + "4-7" + ); + assert_eq!( + f(8, CpuMode::All, CpuDirection::Desc, 8).as_cpu_list(), + "0-7" + ); + assert_eq!( + f(8, CpuMode::All, CpuDirection::Desc, 9).as_cpu_list(), + "0-7" + ); + } +} diff --git a/crates/load_tester/src/main.rs b/crates/load_tester/src/main.rs new file mode 100644 index 00000000..058b1141 --- /dev/null +++ b/crates/load_tester/src/main.rs @@ -0,0 +1,28 @@ +pub mod common; +pub mod protocols; +pub mod run; +pub mod set; + +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[command(author, version, about)] +struct Args { + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + #[cfg(feature = "udp")] + Udp(protocols::udp::UdpCommand), +} + +fn main() { + let args = Args::parse(); + + match args.command { + #[cfg(feature = "udp")] + Command::Udp(command) => command.run().unwrap(), + } +} diff --git a/crates/load_tester/src/protocols/mod.rs b/crates/load_tester/src/protocols/mod.rs new file mode 100644 index 00000000..1fa6e129 --- /dev/null +++ b/crates/load_tester/src/protocols/mod.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "udp")] +pub mod udp; diff --git a/crates/load_tester/src/protocols/udp.rs b/crates/load_tester/src/protocols/udp.rs new file mode 100644 index 00000000..7c2cff67 --- /dev/null +++ b/crates/load_tester/src/protocols/udp.rs @@ -0,0 +1,236 @@ +use std::{ + io::Write, + path::PathBuf, + process::{Child, Command, Stdio}, + rc::Rc, +}; + +use clap::Parser; +use indexmap::{indexmap, IndexMap}; +use tempfile::NamedTempFile; + +use crate::{ + common::{simple_load_test_runs, CpuMode, TaskSetCpuList}, + run::ProcessRunner, + set::{run_sets, Server, SetConfig}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum UdpServer { + Aquatic, + OpenTracker, +} + +impl Server for UdpServer { + fn name(&self) -> String { + match self { + Self::Aquatic => "aquatic_udp".into(), + Self::OpenTracker => "opentracker".into(), + } + } +} + +#[derive(Parser, Debug)] +pub struct UdpCommand { + #[arg(long, default_value_t = CpuMode::Split)] + cpu_mode: CpuMode, + #[arg(long, default_value = "./target/release-debug/aquatic_udp_load_test")] + load_test: PathBuf, + #[arg(long, default_value = "./target/release-debug/aquatic_udp")] + aquatic: PathBuf, + #[arg(long, default_value = "opentracker")] + opentracker: PathBuf, +} + +impl UdpCommand { + pub fn run(&self) -> anyhow::Result<()> { + run_sets(self, self.cpu_mode, self.sets(), |workers| { + Box::new(AquaticUdpLoadTestProcessConfig { workers }) + }); + + Ok(()) + } + + fn sets(&self) -> IndexMap> { + indexmap::indexmap! { + 1 => SetConfig { + implementations: indexmap! { + UdpServer::Aquatic => vec![ + Rc::new(AquaticUdpProcessConfig { + socket_workers: 1, + swarm_workers: 1, + }) as Rc>, + ], + /* + UdpServer::OpenTracker => vec![ + Rc::new(OpenTrackerUdpProcessConfig { + workers: 1, + }) as Rc>, + Rc::new(OpenTrackerUdpProcessConfig { + workers: 2, + }) as Rc>, + ], + */ + }, + load_test_runs: simple_load_test_runs(self.cpu_mode, &[1, 2, 4]), + }, + 2 => SetConfig { + implementations: indexmap! { + UdpServer::Aquatic => vec![ + Rc::new(AquaticUdpProcessConfig { + socket_workers: 1, + swarm_workers: 1, + }) as Rc>, + Rc::new(AquaticUdpProcessConfig { + socket_workers: 2, + swarm_workers: 1, + }) as Rc>, + ], + /* + UdpServer::OpenTracker => vec![ + Rc::new(OpenTrackerUdpProcessConfig { + workers: 2, + }) as Rc>, + Rc::new(OpenTrackerUdpProcessConfig { + workers: 4, + }) as Rc>, + ], + */ + }, + load_test_runs: simple_load_test_runs(self.cpu_mode, &[1, 2, 4]), + }, + } + } +} + +#[derive(Debug, Clone)] +pub struct AquaticUdpProcessConfig { + socket_workers: usize, + swarm_workers: usize, +} + +impl ProcessRunner for AquaticUdpProcessConfig { + type Command = UdpCommand; + + fn run( + &self, + command: &Self::Command, + vcpus: &TaskSetCpuList, + tmp_file: &mut NamedTempFile, + ) -> anyhow::Result { + let mut c = aquatic_udp::config::Config::default(); + + c.socket_workers = self.socket_workers; + c.swarm_workers = self.swarm_workers; + + let c = toml::to_string_pretty(&c)?; + + tmp_file.write_all(c.as_bytes())?; + + Ok(Command::new("taskset") + .arg("--cpu-list") + .arg(vcpus.as_cpu_list()) + .arg(&command.aquatic) + .arg("-c") + .arg(tmp_file.path()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?) + } + + fn info(&self) -> String { + format!( + "socket workers: {}, swarm workers: {}", + self.socket_workers, self.swarm_workers + ) + } + fn keys(&self) -> IndexMap { + indexmap! { + "socket workers".to_string() => self.socket_workers.to_string(), + "swarm workers".to_string() => self.swarm_workers.to_string(), + } + } +} + +#[derive(Debug, Clone)] +pub struct OpenTrackerUdpProcessConfig { + workers: usize, +} + +impl ProcessRunner for OpenTrackerUdpProcessConfig { + type Command = UdpCommand; + + fn run( + &self, + command: &Self::Command, + vcpus: &TaskSetCpuList, + tmp_file: &mut NamedTempFile, + ) -> anyhow::Result { + writeln!(tmp_file, "{}", self.workers)?; // FIXME + + Ok(Command::new("taskset") + .arg("--cpu-list") + .arg(vcpus.as_cpu_list()) + .arg(&command.opentracker) + .arg("-f") + .arg(tmp_file.path()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?) + } + + fn info(&self) -> String { + format!("workers: {}", self.workers) + } + + fn keys(&self) -> IndexMap { + indexmap! { + "workers".to_string() => self.workers.to_string(), + } + } +} + +#[derive(Debug, Clone)] +pub struct AquaticUdpLoadTestProcessConfig { + workers: usize, +} + +impl ProcessRunner for AquaticUdpLoadTestProcessConfig { + type Command = UdpCommand; + + fn run( + &self, + command: &Self::Command, + vcpus: &TaskSetCpuList, + tmp_file: &mut NamedTempFile, + ) -> anyhow::Result { + let mut c = aquatic_udp_load_test::config::Config::default(); + + c.workers = self.workers as u8; + c.duration = 60; + + let c = toml::to_string_pretty(&c)?; + + tmp_file.write_all(c.as_bytes())?; + + Ok(Command::new("taskset") + .arg("--cpu-list") + .arg(vcpus.as_cpu_list()) + .arg(&command.load_test) + .arg("-c") + .arg(tmp_file.path()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?) + } + + fn info(&self) -> String { + format!("workers: {}", self.workers) + } + + fn keys(&self) -> IndexMap { + indexmap! { + "workers".to_string() => self.workers.to_string(), + } + } +} diff --git a/crates/load_tester/src/run.rs b/crates/load_tester/src/run.rs new file mode 100644 index 00000000..2278f5a1 --- /dev/null +++ b/crates/load_tester/src/run.rs @@ -0,0 +1,274 @@ +use std::{ + process::{Child, Command}, + rc::Rc, + str::FromStr, + time::Duration, +}; + +use indexmap::IndexMap; +use nonblock::NonBlockingReader; +use once_cell::sync::Lazy; +use regex::Regex; +use tempfile::NamedTempFile; + +use crate::common::TaskSetCpuList; + +pub trait ProcessRunner: ::std::fmt::Debug { + type Command; + + fn run( + &self, + command: &Self::Command, + vcpus: &TaskSetCpuList, + tmp_file: &mut NamedTempFile, + ) -> anyhow::Result; + fn info(&self) -> String; + fn keys(&self) -> IndexMap; +} + +#[derive(Debug)] +pub struct RunConfig { + pub server_runner: Rc>, + pub server_vcpus: TaskSetCpuList, + pub load_test_runner: Box>, + pub load_test_vcpus: TaskSetCpuList, +} + +impl RunConfig { + pub fn run(self, command: &C) -> Result, RunResults> { + let mut server_config_file = NamedTempFile::new().unwrap(); + let mut load_test_config_file = NamedTempFile::new().unwrap(); + + let server = + match self + .server_runner + .run(command, &self.server_vcpus, &mut server_config_file) + { + Ok(handle) => ChildWrapper(handle), + Err(err) => return Err(RunResults::new(self).set_error(err.into(), "run server")), + }; + + ::std::thread::sleep(Duration::from_secs(1)); + + let mut load_tester = match self.load_test_runner.run( + command, + &self.load_test_vcpus, + &mut load_test_config_file, + ) { + Ok(handle) => ChildWrapper(handle), + Err(err) => { + return Err(RunResults::new(self) + .set_error(err.into(), "run load test") + .set_server(server)) + } + }; + + ::std::thread::sleep(Duration::from_secs(59)); + + let cpu_stats_res = Command::new("ps") + .arg("-p") + .arg(server.0.id().to_string()) + .arg("-o") + .arg("%cpu,rss") + .arg("--noheader") + .output(); + + let server_process_stats = match cpu_stats_res { + Ok(output) if output.status.success() => { + ProcessStats::from_str(&String::from_utf8_lossy(&output.stdout)).unwrap() + } + Ok(_) => { + return Err(RunResults::new(self) + .set_error_context("run ps") + .set_server(server) + .set_load_test(load_tester)); + } + Err(err) => { + return Err(RunResults::new(self) + .set_error(err.into(), "run ps") + .set_server(server) + .set_load_test(load_tester)); + } + }; + + ::std::thread::sleep(Duration::from_secs(5)); + + let load_test_data = match load_tester.0.try_wait() { + Ok(Some(status)) if status.success() => read_child_outputs(load_tester), + Ok(Some(_)) => { + return Err(RunResults::new(self) + .set_error_context("wait for load tester") + .set_server(server) + .set_load_test(load_tester)) + } + Ok(None) => { + if let Err(err) = load_tester.0.kill() { + return Err(RunResults::new(self) + .set_error(err.into(), "kill load tester") + .set_server(server) + .set_load_test(load_tester)); + } + + ::std::thread::sleep(Duration::from_secs(1)); + + match load_tester.0.try_wait() { + Ok(_) => { + return Err(RunResults::new(self) + .set_error_context("load tester didn't finish in time") + .set_load_test(load_tester)) + } + Err(err) => { + return Err(RunResults::new(self) + .set_error(err.into(), "wait for load tester after kill") + .set_server(server)); + } + } + } + Err(err) => { + return Err(RunResults::new(self) + .set_error(err.into(), "wait for load tester") + .set_server(server) + .set_load_test(load_tester)) + } + }; + + let mut results = RunResults::new(self); + + results.server_process_stats = Some(server_process_stats); + results.load_test_stdout = load_test_data.0; + results.load_test_stderr = load_test_data.1; + + Ok(results) + } +} + +#[derive(Debug)] +pub struct RunResults { + pub run_config: RunConfig, + pub server_process_stats: Option, + pub server_stdout: Option, + pub server_stderr: Option, + pub load_test_stdout: Option, + pub load_test_stderr: Option, + pub error: Option, + pub error_context: Option, +} + +impl RunResults { + pub fn avg_responses(&self) -> Option { + static RE: Lazy = + Lazy::new(|| Regex::new(r"Average responses per second: ([0-9]+\.?[0-9]+)").unwrap()); + + self.load_test_stdout.as_ref().and_then(|stdout| { + RE.captures_iter(&stdout).next().map(|c| { + let (_, [avg_responses]) = c.extract(); + + avg_responses.to_string() + }) + }) + } + + fn new(run_config: RunConfig) -> Self { + Self { + run_config, + server_process_stats: Default::default(), + server_stdout: Default::default(), + server_stderr: Default::default(), + load_test_stdout: Default::default(), + load_test_stderr: Default::default(), + error: Default::default(), + error_context: Default::default(), + } + } + + fn set_server(mut self, server: ChildWrapper) -> Self { + let (stdout, stderr) = read_child_outputs(server); + + self.server_stdout = stdout; + self.server_stderr = stderr; + + self + } + + fn set_load_test(mut self, load_test: ChildWrapper) -> Self { + let (stdout, stderr) = read_child_outputs(load_test); + + self.load_test_stdout = stdout; + self.load_test_stderr = stderr; + + self + } + + fn set_error(mut self, error: anyhow::Error, context: &str) -> Self { + self.error = Some(error); + self.error_context = Some(context.to_string()); + + self + } + + fn set_error_context(mut self, context: &str) -> Self { + self.error_context = Some(context.to_string()); + + self + } +} + +#[derive(Debug, Clone, Copy)] +pub struct ProcessStats { + pub avg_cpu_utilization: f32, + pub peak_rss_kb: f32, +} + +impl FromStr for ProcessStats { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut parts = s.trim().split_whitespace(); + + Ok(Self { + avg_cpu_utilization: parts.next().ok_or(())?.parse().map_err(|_| ())?, + peak_rss_kb: parts.next().ok_or(())?.parse().map_err(|_| ())?, + }) + } +} + +struct ChildWrapper(Child); + +impl Drop for ChildWrapper { + fn drop(&mut self) { + let _ = self.0.kill(); + + ::std::thread::sleep(Duration::from_secs(1)); + + let _ = self.0.try_wait(); + } +} + +impl AsMut for ChildWrapper { + fn as_mut(&mut self) -> &mut Child { + &mut self.0 + } +} + +fn read_child_outputs(mut child: ChildWrapper) -> (Option, Option) { + let stdout = child.0.stdout.take().map(|stdout| { + let mut buf = String::new(); + + let mut reader = NonBlockingReader::from_fd(stdout).unwrap(); + + reader.read_available_to_string(&mut buf).unwrap(); + + buf + }); + let stderr = child.0.stderr.take().map(|stderr| { + let mut buf = String::new(); + + let mut reader = NonBlockingReader::from_fd(stderr).unwrap(); + + reader.read_available_to_string(&mut buf).unwrap(); + + buf + }); + + (stdout, stderr) +} diff --git a/crates/load_tester/src/set.rs b/crates/load_tester/src/set.rs new file mode 100644 index 00000000..bfeb05d2 --- /dev/null +++ b/crates/load_tester/src/set.rs @@ -0,0 +1,291 @@ +use std::rc::Rc; + +use indexmap::{IndexMap, IndexSet}; +use itertools::Itertools; + +use crate::{ + common::{CpuDirection, CpuMode, TaskSetCpuList}, + run::{ProcessRunner, ProcessStats, RunConfig}, +}; + +pub trait Server: ::std::fmt::Debug + Copy + Clone + ::std::hash::Hash { + fn name(&self) -> String; +} + +pub struct SetConfig { + pub implementations: IndexMap>>>, + pub load_test_runs: Vec<(usize, TaskSetCpuList)>, +} + +pub fn run_sets( + command: &C, + cpu_mode: CpuMode, + set_configs: IndexMap>, + load_test_gen: F, +) where + C: ::std::fmt::Debug, + I: Server, + F: Fn(usize) -> Box>, +{ + println!("# Load test report"); + + let results = set_configs + .into_iter() + .map(|(server_core_count, set_config)| { + let server_vcpus = + TaskSetCpuList::new(cpu_mode, CpuDirection::Asc, server_core_count).unwrap(); + + println!( + "## Tracker cores: {} (cpus: {})", + server_core_count, + server_vcpus.as_cpu_list() + ); + + let server_results = set_config + .implementations + .into_iter() + .map(|(implementation, server_runs)| { + let server_run_results = server_runs + .iter() + .map(|server_run| { + let load_test_run_results = set_config + .load_test_runs + .clone() + .into_iter() + .map(|(workers, load_test_vcpus)| { + LoadTestRunResults::produce( + command, + &load_test_gen, + implementation, + &server_run, + server_vcpus.clone(), + workers, + load_test_vcpus, + ) + }) + .collect(); + + ServerConfigurationResults { + config_keys: server_run.keys(), + load_tests: load_test_run_results, + } + }) + .collect(); + + ImplementationResults { + name: implementation.name(), + configurations: server_run_results, + } + }) + .collect(); + + ServerCoreCountResults { + core_count: server_core_count, + implementations: server_results, + } + }) + .collect::>(); + + html_summary(&results); +} + +pub struct ServerCoreCountResults { + core_count: usize, + implementations: Vec, +} + +pub struct ImplementationResults { + name: String, + configurations: Vec, +} + +impl ImplementationResults { + fn best_result(&self) -> Option { + self.configurations + .iter() + .filter_map(|c| c.best_result()) + .reduce(|acc, r| { + if r.average_responses > acc.average_responses { + r + } else { + acc + } + }) + } +} + +pub struct ServerConfigurationResults { + config_keys: IndexMap, + load_tests: Vec, + // best_index: Option, +} + +impl ServerConfigurationResults { + fn best_result(&self) -> Option { + self.load_tests + .iter() + .filter_map(|r| match r { + LoadTestRunResults::Success(r) => Some(r.clone()), + LoadTestRunResults::Failure(_) => None, + }) + .reduce(|acc, r| { + if r.average_responses > acc.average_responses { + r + } else { + acc + } + }) + } +} + +pub enum LoadTestRunResults { + Success(LoadTestRunResultsSuccess), + Failure(LoadTestRunResultsFailure), +} + +impl LoadTestRunResults { + pub fn produce( + command: &C, + load_test_gen: &F, + implementation: I, + server_process: &Rc>, + server_vcpus: TaskSetCpuList, + workers: usize, + load_test_vcpus: TaskSetCpuList, + ) -> Self + where + C: ::std::fmt::Debug, + I: Server, + F: Fn(usize) -> Box>, + { + println!( + "### {} run ({}) (load test workers: {}, cpus: {})", + implementation.name(), + server_process.info(), + workers, + load_test_vcpus.as_cpu_list() + ); + + let load_test_runner = load_test_gen(workers); + let load_test_keys = load_test_runner.keys(); + + let run_config = RunConfig { + server_runner: server_process.clone(), + server_vcpus: server_vcpus.clone(), + load_test_runner, + load_test_vcpus, + }; + + match run_config.run(command) { + Ok(results) => { + let avg_responses = results.avg_responses().unwrap().parse::().unwrap(); + let server_process_stats = results.server_process_stats.unwrap(); + + println!("- Average responses per second: {}", avg_responses); + println!( + "- Average server CPU utilization: {}%", + server_process_stats.avg_cpu_utilization, + ); + println!("- Peak server RSS: {} kB", server_process_stats.peak_rss_kb); + + LoadTestRunResults::Success(LoadTestRunResultsSuccess { + config_keys: load_test_keys, + average_responses: avg_responses, + server_process_stats, + }) + } + Err(results) => { + println!("\nRun failed:\n{:?}\n", results); + + LoadTestRunResults::Failure(LoadTestRunResultsFailure { + config_keys: load_test_keys, + }) + } + } + } +} + +#[derive(Clone)] +pub struct LoadTestRunResultsSuccess { + config_keys: IndexMap, + average_responses: f32, + server_process_stats: ProcessStats, +} + +pub struct LoadTestRunResultsFailure { + config_keys: IndexMap, +} + +pub fn html_summary(results: &[ServerCoreCountResults]) { + let mut all_implementation_names = IndexSet::new(); + + for core_count_results in results { + all_implementation_names.extend( + core_count_results + .implementations + .iter() + .map(|r| r.name.clone()), + ); + } + + let mut data_rows = Vec::new(); + + for core_count_results in results { + let best_results = core_count_results + .implementations + .iter() + .map(|implementation| (implementation.name.clone(), implementation.best_result())) + .collect::>(); + + let best_results_for_all_implementations = all_implementation_names + .iter() + .map(|name| { + best_results + .get(name) + .and_then(|r| r.as_ref().map(|r| r.average_responses)) + }) + .collect::>(); + + let data_row = format!( + " + + {} + {} + + ", + core_count_results.core_count, + best_results_for_all_implementations + .into_iter() + .map(|result| format!( + "{}", + result + .map(|r| r.to_string()) + .unwrap_or_else(|| "-".to_string()) + )) + .join("\n"), + ); + + data_rows.push(data_row); + } + + println!( + " + + + + + {} + + + + {} + +
CPU cores
+ ", + all_implementation_names + .iter() + .map(|name| format!("{name}")) + .join("\n"), + data_rows.join("\n") + ) +} diff --git a/crates/udp/src/config.rs b/crates/udp/src/config.rs index 852fbcf4..fb46ee8b 100644 --- a/crates/udp/src/config.rs +++ b/crates/udp/src/config.rs @@ -2,13 +2,13 @@ use std::{net::SocketAddr, path::PathBuf}; use aquatic_common::{access_list::AccessListConfig, privileges::PrivilegeConfig}; use cfg_if::cfg_if; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use aquatic_common::cli::LogLevel; use aquatic_toml_config::TomlConfig; /// aquatic_udp configuration -#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)] +#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize, Serialize)] #[serde(default, deny_unknown_fields)] pub struct Config { /// Number of socket worker. One per physical core is recommended. @@ -78,7 +78,7 @@ impl aquatic_common::cli::Config for Config { } } -#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)] +#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize, Serialize)] #[serde(default, deny_unknown_fields)] pub struct NetworkConfig { /// Bind to this address @@ -138,7 +138,7 @@ impl Default for NetworkConfig { } } -#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)] +#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize, Serialize)] #[serde(default, deny_unknown_fields)] pub struct ProtocolConfig { /// Maximum number of torrents to allow in scrape request @@ -159,7 +159,7 @@ impl Default for ProtocolConfig { } } -#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)] +#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize, Serialize)] #[serde(default, deny_unknown_fields)] pub struct StatisticsConfig { /// Collect and print/write statistics this often (seconds) @@ -231,7 +231,7 @@ impl Default for StatisticsConfig { } } -#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)] +#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize, Serialize)] #[serde(default, deny_unknown_fields)] pub struct CleaningConfig { /// Clean torrents this often (seconds) diff --git a/crates/udp_load_test/Cargo.toml b/crates/udp_load_test/Cargo.toml index 5c3df5c7..e42aad38 100644 --- a/crates/udp_load_test/Cargo.toml +++ b/crates/udp_load_test/Cargo.toml @@ -13,6 +13,9 @@ rust-version.workspace = true [features] cpu-pinning = ["aquatic_common/hwloc"] +[lib] +name = "aquatic_udp_load_test" + [[bin]] name = "aquatic_udp_load_test" diff --git a/crates/udp_load_test/src/config.rs b/crates/udp_load_test/src/config.rs index 963c80fe..8db6bae0 100644 --- a/crates/udp_load_test/src/config.rs +++ b/crates/udp_load_test/src/config.rs @@ -1,6 +1,6 @@ use std::net::SocketAddr; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use aquatic_common::cli::LogLevel; #[cfg(feature = "cpu-pinning")] @@ -8,7 +8,7 @@ use aquatic_common::cpu_pinning::desc::CpuPinningConfigDesc; use aquatic_toml_config::TomlConfig; /// aquatic_udp_load_test configuration -#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)] +#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize, Serialize)] #[serde(default, deny_unknown_fields)] pub struct Config { /// Server address @@ -42,7 +42,7 @@ impl Default for Config { } } -#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)] +#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize, Serialize)] #[serde(default, deny_unknown_fields)] pub struct NetworkConfig { /// True means bind to one localhost IP per socket. @@ -56,8 +56,6 @@ pub struct NetworkConfig { pub first_port: u16, /// Socket worker poll timeout in microseconds pub poll_timeout: u64, - /// Socket worker polling event number - pub poll_event_capacity: usize, /// Size of socket recv buffer. Use 0 for OS default. /// /// This setting can have a big impact on dropped packages. It might @@ -79,13 +77,12 @@ impl Default for NetworkConfig { multiple_client_ipv4s: true, first_port: 45_000, poll_timeout: 276, - poll_event_capacity: 2_877, recv_buffer: 6_000_000, } } } -#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)] +#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize, Serialize)] #[serde(default, deny_unknown_fields)] pub struct RequestConfig { /// Number of torrents to simulate diff --git a/crates/udp_load_test/src/lib.rs b/crates/udp_load_test/src/lib.rs new file mode 100644 index 00000000..b8b3ebbe --- /dev/null +++ b/crates/udp_load_test/src/lib.rs @@ -0,0 +1,191 @@ +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::sync::atomic::AtomicUsize; +use std::sync::{atomic::Ordering, Arc}; +use std::thread::{self, Builder}; +use std::time::{Duration, Instant}; + +#[cfg(feature = "cpu-pinning")] +use aquatic_common::cpu_pinning::{pin_current_if_configured_to, WorkerIndex}; +use rand_distr::Gamma; + +pub mod common; +pub mod config; +pub mod utils; +pub mod worker; + +use common::*; +use config::Config; +use utils::*; +use worker::*; + +impl aquatic_common::cli::Config for Config { + fn get_log_level(&self) -> Option { + Some(self.log_level) + } +} + +pub fn run(config: Config) -> ::anyhow::Result<()> { + if config.requests.weight_announce + + config.requests.weight_connect + + config.requests.weight_scrape + == 0 + { + panic!("Error: at least one weight must be larger than zero."); + } + + println!("Starting client with config: {:#?}", config); + + let mut info_hashes = Vec::with_capacity(config.requests.number_of_torrents); + + for _ in 0..config.requests.number_of_torrents { + info_hashes.push(generate_info_hash()); + } + + let state = LoadTestState { + info_hashes: Arc::new(info_hashes), + statistics: Arc::new(Statistics::default()), + }; + + let gamma = Gamma::new( + config.requests.torrent_gamma_shape, + config.requests.torrent_gamma_scale, + ) + .unwrap(); + + // Start workers + + for i in 0..config.workers { + let port = config.network.first_port + (i as u16); + + let ip = if config.server_address.is_ipv6() { + Ipv6Addr::LOCALHOST.into() + } else { + if config.network.multiple_client_ipv4s { + Ipv4Addr::new(127, 0, 0, 1 + i).into() + } else { + Ipv4Addr::LOCALHOST.into() + } + }; + + let addr = SocketAddr::new(ip, port); + let config = config.clone(); + let state = state.clone(); + + Builder::new().name("load-test".into()).spawn(move || { + #[cfg(feature = "cpu-pinning")] + pin_current_if_configured_to( + &config.cpu_pinning, + config.workers as usize, + 0, + WorkerIndex::SocketWorker(i as usize), + ); + + run_worker_thread(state, gamma, &config, addr) + })?; + } + + #[cfg(feature = "cpu-pinning")] + pin_current_if_configured_to( + &config.cpu_pinning, + config.workers as usize, + 0, + WorkerIndex::Util, + ); + + monitor_statistics(state, &config); + + Ok(()) +} + +fn monitor_statistics(state: LoadTestState, config: &Config) { + let mut report_avg_connect: Vec = Vec::new(); + let mut report_avg_announce: Vec = Vec::new(); + let mut report_avg_scrape: Vec = Vec::new(); + let mut report_avg_error: Vec = Vec::new(); + + let interval = 5; + + let start_time = Instant::now(); + let duration = Duration::from_secs(config.duration as u64); + + let mut last = start_time; + + let time_elapsed = loop { + thread::sleep(Duration::from_secs(interval)); + + let requests = fetch_and_reset(&state.statistics.requests); + let response_peers = fetch_and_reset(&state.statistics.response_peers); + let responses_connect = fetch_and_reset(&state.statistics.responses_connect); + let responses_announce = fetch_and_reset(&state.statistics.responses_announce); + let responses_scrape = fetch_and_reset(&state.statistics.responses_scrape); + let responses_error = fetch_and_reset(&state.statistics.responses_error); + + let now = Instant::now(); + + let elapsed = (now - last).as_secs_f64(); + + last = now; + + let peers_per_announce_response = response_peers / responses_announce; + + let avg_requests = requests / elapsed; + let avg_responses_connect = responses_connect / elapsed; + let avg_responses_announce = responses_announce / elapsed; + let avg_responses_scrape = responses_scrape / elapsed; + let avg_responses_error = responses_error / elapsed; + + let avg_responses = avg_responses_connect + + avg_responses_announce + + avg_responses_scrape + + avg_responses_error; + + report_avg_connect.push(avg_responses_connect); + report_avg_announce.push(avg_responses_announce); + report_avg_scrape.push(avg_responses_scrape); + report_avg_error.push(avg_responses_error); + + println!(); + println!("Requests out: {:.2}/second", avg_requests); + println!("Responses in: {:.2}/second", avg_responses); + println!(" - Connect responses: {:.2}", avg_responses_connect); + println!(" - Announce responses: {:.2}", avg_responses_announce); + println!(" - Scrape responses: {:.2}", avg_responses_scrape); + println!(" - Error responses: {:.2}", avg_responses_error); + println!( + "Peers per announce response: {:.2}", + peers_per_announce_response + ); + + let time_elapsed = start_time.elapsed(); + + if config.duration != 0 && time_elapsed >= duration { + break time_elapsed; + } + }; + + let len = report_avg_connect.len() as f64; + + let avg_connect: f64 = report_avg_connect.into_iter().sum::() / len; + let avg_announce: f64 = report_avg_announce.into_iter().sum::() / len; + let avg_scrape: f64 = report_avg_scrape.into_iter().sum::() / len; + let avg_error: f64 = report_avg_error.into_iter().sum::() / len; + + let avg_total = avg_connect + avg_announce + avg_scrape + avg_error; + + println!(); + println!("# aquatic load test report"); + println!(); + println!("Test ran for {} seconds", time_elapsed.as_secs()); + println!("Average responses per second: {:.2}", avg_total); + println!(" - Connect responses: {:.2}", avg_connect); + println!(" - Announce responses: {:.2}", avg_announce); + println!(" - Scrape responses: {:.2}", avg_scrape); + println!(" - Error responses: {:.2}", avg_error); + println!(); + println!("Config: {:#?}", config); + println!(); +} + +fn fetch_and_reset(atomic_usize: &AtomicUsize) -> f64 { + atomic_usize.fetch_and(0, Ordering::Relaxed) as f64 +} diff --git a/crates/udp_load_test/src/main.rs b/crates/udp_load_test/src/main.rs index 48af77ba..74c76b82 100644 --- a/crates/udp_load_test/src/main.rs +++ b/crates/udp_load_test/src/main.rs @@ -1,22 +1,4 @@ -use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; -use std::sync::atomic::AtomicUsize; -use std::sync::{atomic::Ordering, Arc}; -use std::thread::{self, Builder}; -use std::time::{Duration, Instant}; - -#[cfg(feature = "cpu-pinning")] -use aquatic_common::cpu_pinning::{pin_current_if_configured_to, WorkerIndex}; -use rand_distr::Gamma; - -mod common; -mod config; -mod utils; -mod worker; - -use common::*; -use config::Config; -use utils::*; -use worker::*; +use aquatic_udp_load_test::config::Config; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; @@ -25,179 +7,7 @@ pub fn main() { aquatic_common::cli::run_app_with_cli_and_config::( "aquatic_udp_load_test: BitTorrent load tester", env!("CARGO_PKG_VERSION"), - run, + aquatic_udp_load_test::run, None, ) } - -impl aquatic_common::cli::Config for Config { - fn get_log_level(&self) -> Option { - Some(self.log_level) - } -} - -fn run(config: Config) -> ::anyhow::Result<()> { - if config.requests.weight_announce - + config.requests.weight_connect - + config.requests.weight_scrape - == 0 - { - panic!("Error: at least one weight must be larger than zero."); - } - - println!("Starting client with config: {:#?}", config); - - let mut info_hashes = Vec::with_capacity(config.requests.number_of_torrents); - - for _ in 0..config.requests.number_of_torrents { - info_hashes.push(generate_info_hash()); - } - - let state = LoadTestState { - info_hashes: Arc::new(info_hashes), - statistics: Arc::new(Statistics::default()), - }; - - let gamma = Gamma::new( - config.requests.torrent_gamma_shape, - config.requests.torrent_gamma_scale, - ) - .unwrap(); - - // Start workers - - for i in 0..config.workers { - let port = config.network.first_port + (i as u16); - - let ip = if config.server_address.is_ipv6() { - Ipv6Addr::LOCALHOST.into() - } else { - if config.network.multiple_client_ipv4s { - Ipv4Addr::new(127, 0, 0, 1 + i).into() - } else { - Ipv4Addr::LOCALHOST.into() - } - }; - - let addr = SocketAddr::new(ip, port); - let config = config.clone(); - let state = state.clone(); - - Builder::new().name("load-test".into()).spawn(move || { - #[cfg(feature = "cpu-pinning")] - pin_current_if_configured_to( - &config.cpu_pinning, - config.workers as usize, - 0, - WorkerIndex::SocketWorker(i as usize), - ); - - run_worker_thread(state, gamma, &config, addr) - })?; - } - - #[cfg(feature = "cpu-pinning")] - pin_current_if_configured_to( - &config.cpu_pinning, - config.workers as usize, - 0, - WorkerIndex::Util, - ); - - monitor_statistics(state, &config); - - Ok(()) -} - -fn monitor_statistics(state: LoadTestState, config: &Config) { - let mut report_avg_connect: Vec = Vec::new(); - let mut report_avg_announce: Vec = Vec::new(); - let mut report_avg_scrape: Vec = Vec::new(); - let mut report_avg_error: Vec = Vec::new(); - - let interval = 5; - - let start_time = Instant::now(); - let duration = Duration::from_secs(config.duration as u64); - - let mut last = start_time; - - let time_elapsed = loop { - thread::sleep(Duration::from_secs(interval)); - - let requests = fetch_and_reset(&state.statistics.requests); - let response_peers = fetch_and_reset(&state.statistics.response_peers); - let responses_connect = fetch_and_reset(&state.statistics.responses_connect); - let responses_announce = fetch_and_reset(&state.statistics.responses_announce); - let responses_scrape = fetch_and_reset(&state.statistics.responses_scrape); - let responses_error = fetch_and_reset(&state.statistics.responses_error); - - let now = Instant::now(); - - let elapsed = (now - last).as_secs_f64(); - - last = now; - - let peers_per_announce_response = response_peers / responses_announce; - - let avg_requests = requests / elapsed; - let avg_responses_connect = responses_connect / elapsed; - let avg_responses_announce = responses_announce / elapsed; - let avg_responses_scrape = responses_scrape / elapsed; - let avg_responses_error = responses_error / elapsed; - - let avg_responses = avg_responses_connect - + avg_responses_announce - + avg_responses_scrape - + avg_responses_error; - - report_avg_connect.push(avg_responses_connect); - report_avg_announce.push(avg_responses_announce); - report_avg_scrape.push(avg_responses_scrape); - report_avg_error.push(avg_responses_error); - - println!(); - println!("Requests out: {:.2}/second", avg_requests); - println!("Responses in: {:.2}/second", avg_responses); - println!(" - Connect responses: {:.2}", avg_responses_connect); - println!(" - Announce responses: {:.2}", avg_responses_announce); - println!(" - Scrape responses: {:.2}", avg_responses_scrape); - println!(" - Error responses: {:.2}", avg_responses_error); - println!( - "Peers per announce response: {:.2}", - peers_per_announce_response - ); - - let time_elapsed = start_time.elapsed(); - - if config.duration != 0 && time_elapsed >= duration { - break time_elapsed; - } - }; - - let len = report_avg_connect.len() as f64; - - let avg_connect: f64 = report_avg_connect.into_iter().sum::() / len; - let avg_announce: f64 = report_avg_announce.into_iter().sum::() / len; - let avg_scrape: f64 = report_avg_scrape.into_iter().sum::() / len; - let avg_error: f64 = report_avg_error.into_iter().sum::() / len; - - let avg_total = avg_connect + avg_announce + avg_scrape + avg_error; - - println!(); - println!("# aquatic load test report"); - println!(); - println!("Test ran for {} seconds", time_elapsed.as_secs()); - println!("Average responses per second: {:.2}", avg_total); - println!(" - Connect responses: {:.2}", avg_connect); - println!(" - Announce responses: {:.2}", avg_announce); - println!(" - Scrape responses: {:.2}", avg_scrape); - println!(" - Error responses: {:.2}", avg_error); - println!(); - println!("Config: {:#?}", config); - println!(); -} - -fn fetch_and_reset(atomic_usize: &AtomicUsize) -> f64 { - atomic_usize.fetch_and(0, Ordering::Relaxed) as f64 -} diff --git a/crates/udp_load_test/src/worker/mod.rs b/crates/udp_load_test/src/worker/mod.rs index b8636600..a06e7249 100644 --- a/crates/udp_load_test/src/worker/mod.rs +++ b/crates/udp_load_test/src/worker/mod.rs @@ -41,7 +41,7 @@ pub fn run_worker_thread( .register(&mut socket, token, interests) .unwrap(); - let mut events = Events::with_capacity(config.network.poll_event_capacity); + let mut events = Events::with_capacity(1); let mut statistics = SocketWorkerLocalStatistics::default();