diff --git a/.dockerignore b/.dockerignore index 378eac2..a2177da 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ build +tests/performance_tester/target diff --git a/Makefile b/Makefile index 874d5d6..2adfc22 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,10 @@ init: clean: cd $(BUILDDIR) && make clean +integration-tests: + python3.8 -mpytest -s tests/performance_tester/integration-tests.py + ./tests/acceptance/run.sh + DOCKER_IMAGE ?= amqpprox DOCKER_BUILDDIR ?= build/docker-$(DOCKER_IMAGE) DOCKER_ARGS ?= $(DOCKER_EXTRA_ARGS) -v $(CUR_DIR):/source -v $(CUR_DIR)/$(DOCKER_BUILDDIR):/build -it $(DOCKER_IMAGE) @@ -50,7 +54,7 @@ docker-integration-tests: BUILD_FLAVOUR ?= conan docker-integration-tests: BUILD_DOCKERFILE ?= buildfiles/$(BUILD_FLAVOUR)/integration.Dockerfile docker-integration-tests: docker build -t $(DOCKER_IMAGE) -f $(BUILD_DOCKERFILE) . - docker run $(DOCKER_IMAGE) + docker run $(DOCKER_IMAGE) make integration-tests docs: doxygen Doxygen.config diff --git a/buildfiles/conan/integration.Dockerfile b/buildfiles/conan/integration.Dockerfile index b092d3e..91c7acc 100644 --- a/buildfiles/conan/integration.Dockerfile +++ b/buildfiles/conan/integration.Dockerfile @@ -1,13 +1,16 @@ -FROM rabbitmq:3.7.9 +FROM rabbitmq:3.7.28 ENV DEBIAN_FRONTEND=noninteractive + +# Install dependencies for integration tests RUN apt-get update && apt-get dist-upgrade -y --force-yes RUN apt-get install -y --force-yes python3.8 python3.8-distutils \ curl llvm make cmake build-essential npm RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py RUN python3.8 get-pip.py - -RUN python3.8 -m pip install setuptools conan robotframework pika amqp +RUN python3.8 -m pip install setuptools conan robotframework pika amqp pytest +ENV HOME="/root" PATH="/root/.cargo/bin:${PATH}" +RUN curl https://sh.rustup.rs -sSf | bash -s -- -y EXPOSE 15800 EXPOSE 15801 @@ -21,6 +24,5 @@ RUN npm install WORKDIR /source RUN make setup && make init && make -ENV ROBOT_SOURCE_DIR=/source/tests/acceptance -ENV ROBOT_BINARY_DIR=/opt/rabbitmq/sbin -ENTRYPOINT [ "/source/tests/acceptance/run.sh"] +ENV ROBOT_SOURCE_DIR=/source/tests/acceptance ROBOT_BINARY_DIR=/opt/rabbitmq/sbin +ENV AMQPPROX_BIN_DIR=/build/bin diff --git a/tests/acceptance/connection.robot b/tests/acceptance/connection.robot index 1e10911..eb6d6d4 100644 --- a/tests/acceptance/connection.robot +++ b/tests/acceptance/connection.robot @@ -36,14 +36,14 @@ Smoke suite setup ${SMOKE_PATH}= Get Environment Variable SMOKE_PATH ${WAIT_TIME}= Get Environment Variable WAIT_TIME ${LOG_CONSOLE}= Get Environment Variable LOG_CONSOLE - ${BUILD_PATH}= Get Environment Variable BUILD_PATH + ${AMQPPROX_BIN_DIR}= Get Environment Variable AMQPPROX_BIN_DIR Set suite variable ${ROBOT_SOURCE_DIR} Set suite variable ${BINARY_PATH} Set suite variable ${SOURCE_PATH} Set suite variable ${SMOKE_PATH} Set suite variable ${WAIT_TIME} Set suite variable ${LOG_CONSOLE} - Set suite variable ${BUILD_PATH} + Set suite variable ${AMQPPROX_BIN_DIR} Connection Test Setup Log "" console=yes diff --git a/tests/acceptance/libs/AMQPProx.robot b/tests/acceptance/libs/AMQPProx.robot index 080ba62..63bdf3a 100644 --- a/tests/acceptance/libs/AMQPProx.robot +++ b/tests/acceptance/libs/AMQPProx.robot @@ -20,8 +20,8 @@ Library Process AMQPProx start Create Directory /tmp/logs/amqpprox ${SOURCE_PATH}= Get Environment Variable SOURCE_PATH - ${BUILD_PATH}= Get Environment Variable BUILD_PATH - ${result}= Start Process ${BUILD_PATH}/amqpprox --cleanupIntervalMs 10 --controlSocket /tmp/amqpprox --logDirectory /tmp/logs/amqpprox + ${AMQPPROX_BIN_DIR}= Get Environment Variable AMQPPROX_BIN_DIR + ${result}= Start Process ${AMQPPROX_BIN_DIR}/amqpprox --cleanupIntervalMs 10 --controlSocket /tmp/amqpprox --logDirectory /tmp/logs/amqpprox ... shell=yes [Return] ${result} diff --git a/tests/acceptance/libs/AMQPProxCTL.robot b/tests/acceptance/libs/AMQPProxCTL.robot index c67aefc..7045049 100644 --- a/tests/acceptance/libs/AMQPProxCTL.robot +++ b/tests/acceptance/libs/AMQPProxCTL.robot @@ -222,8 +222,8 @@ AMQPProxCTL VHOST force_disconnect AMQPProxCTL send command [Arguments] @{arguments} ${SOURCE_PATH}= Get Environment Variable SOURCE_PATH - ${BUILD_PATH}= Get Environment Variable BUILD_PATH - ${result}= Run Process ${BUILD_PATH}/amqpprox_ctl + ${AMQPPROX_BIN_DIR}= Get Environment Variable AMQPPROX_BIN_DIR + ${result}= Run Process ${AMQPPROX_BIN_DIR}/amqpprox_ctl ... /tmp/amqpprox ... @{arguments} ... shell=yes diff --git a/tests/acceptance/run.sh b/tests/acceptance/run.sh index 7e42952..94b7447 100755 --- a/tests/acceptance/run.sh +++ b/tests/acceptance/run.sh @@ -16,7 +16,7 @@ export BINARY_PATH=${ROBOT_BINARY_DIR:=/usr/bin} export SOURCE_PATH=${SOURCE_PATH:=/source} -export BUILD_PATH=${BUILD_PATH:=/build/bin} +export AMQPPROX_BIN_DIR=${AMQPPROX_BIN_DIR:=/build/bin} export ACCEPTANCE_PATH=${ACCEPTANCE_PATH:=/source/tests/acceptance} export SMOKE_PATH=${SMOKE_PATH:=/source/tests/acceptance/integration} export WAIT_TIME=${WAIT_TIME:=30} diff --git a/tests/acceptance/smoke.robot b/tests/acceptance/smoke.robot index cb11fb0..81374c8 100644 --- a/tests/acceptance/smoke.robot +++ b/tests/acceptance/smoke.robot @@ -29,14 +29,14 @@ Smoke suite setup ${SMOKE_PATH}= Get Environment Variable SMOKE_PATH ${WAIT_TIME}= Get Environment Variable WAIT_TIME ${LOG_CONSOLE}= Get Environment Variable LOG_CONSOLE - ${BUILD_PATH}= Get Environment Variable BUILD_PATH + ${AMQPPROX_BIN_DIR}= Get Environment Variable AMQPPROX_BIN_DIR Set suite variable ${ROBOT_SOURCE_DIR} Set suite variable ${BINARY_PATH} Set suite variable ${SOURCE_PATH} Set suite variable ${SMOKE_PATH} Set suite variable ${WAIT_TIME} Set suite variable ${LOG_CONSOLE} - Set suite variable ${BUILD_PATH} + Set suite variable ${AMQPPROX_BIN_DIR} *** Test Cases *** @@ -69,8 +69,8 @@ Smoke Test ... console=${LOG_CONSOLE} ${result} = Run Process node ... ${SMOKE_PATH}/index.js - ... ${BUILD_PATH}/amqpprox - ... ${BUILD_PATH}/amqpprox_ctl + ... ${AMQPPROX_BIN_DIR}/amqpprox + ... ${AMQPPROX_BIN_DIR}/amqpprox_ctl ... ${WAIT_TIME} ... stdout=STDOUT ... stderr=STDOUT diff --git a/tests/performance_tester/.gitignore b/tests/performance_tester/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/tests/performance_tester/.gitignore @@ -0,0 +1 @@ +target diff --git a/tests/performance_tester/Cargo.lock b/tests/performance_tester/Cargo.lock new file mode 100644 index 0000000..be3c737 --- /dev/null +++ b/tests/performance_tester/Cargo.lock @@ -0,0 +1,1618 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "amiquip" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e34be7db1d0210da6ac2dde4236e0177daa321a589715e495e8b2226325f9f" +dependencies = [ + "amq-protocol 1.4.0", + "built", + "bytes", + "cookie-factory 0.2.4", + "crossbeam-channel", + "indexmap", + "input_buffer", + "log", + "mio 0.6.23", + "mio-extras", + "percent-encoding 2.1.0", + "snafu", + "url 2.2.2", +] + +[[package]] +name = "amq-protocol" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d687fc53c2d85f31d22d91d8b62a7a6f8fc2b0e8dfd2c23d52a0433db4d01f2b" +dependencies = [ + "amq-protocol-codegen 1.4.0", + "amq-protocol-types 1.2.0", + "cookie-factory 0.2.4", + "nom 4.2.3", + "url 1.7.2", +] + +[[package]] +name = "amq-protocol" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "591036b5c667eb00b2d0def928f5967ba0471814edaa6eb555d947255c2d8dfe" +dependencies = [ + "amq-protocol-codegen 4.2.2", + "amq-protocol-tcp", + "amq-protocol-types 4.2.2", + "amq-protocol-uri", + "cookie-factory 0.3.2", + "nom 5.1.2", +] + +[[package]] +name = "amq-protocol-codegen" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b367f31f4feba2ca7959f476f81076db213734a053d2b0ab78bcffab0acbfae6" +dependencies = [ + "amq-protocol-types 1.2.0", + "handlebars 1.1.0", + "serde", + "serde_json", +] + +[[package]] +name = "amq-protocol-codegen" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "700a1bcc975c3c67574fd05bb2fea3a9d38a4c319c3fc982ac8661dfa0febd6b" +dependencies = [ + "amq-protocol-types 4.2.2", + "handlebars 3.5.5", + "serde", + "serde_json", +] + +[[package]] +name = "amq-protocol-tcp" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c44aca5636c30f7876b9fa0a31d0cd52733d946db15b168d2a89418dab85baa9" +dependencies = [ + "amq-protocol-uri", + "log", + "mio 0.6.23", + "tcp-stream", +] + +[[package]] +name = "amq-protocol-types" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6857d51c6c9e9b45eacd355917c0e792cd3ceeaeab76a75d6475ea8980009fea" +dependencies = [ + "cookie-factory 0.2.4", + "nom 4.2.3", + "serde", + "serde_json", +] + +[[package]] +name = "amq-protocol-types" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a45836fdf1b70ecd5dd7fb2241c53229f47ff8f945afd7d61cef15bfaf72317" +dependencies = [ + "cookie-factory 0.3.2", + "nom 5.1.2", + "serde", + "serde_json", +] + +[[package]] +name = "amq-protocol-uri" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f4de681a0be026f6df2fd440d9dd9c94bb15d2afa6f8dbfd5fa454371965f0" +dependencies = [ + "percent-encoding 2.1.0", + "url 2.2.2", +] + +[[package]] +name = "amqpprox_perf_tester" +version = "0.1.0" +dependencies = [ + "amiquip", + "amq-protocol 4.2.2", + "anyhow", + "bytes", + "clap", + "env_logger", + "futures", + "log", + "rustls-pemfile", + "thiserror", + "tokio", + "tokio-rustls", + "tokio-util", +] + +[[package]] +name = "anyhow" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "built" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f346b6890a0dfa7266974910e7df2d5088120dd54721b9b0e5aae1ae5e05715" +dependencies = [ + "cargo-lock", +] + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cargo-lock" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb04b88bd5b2036e30704f95c6ee16f3b5ca3b4ca307da2889d9006648e5c88" +dependencies = [ + "semver", + "serde", + "toml", + "url 2.2.2", +] + +[[package]] +name = "cc" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e8611f9ae4e068fa3e56931fded356ff745e70987ff76924a6e0ab1c8ef2e3" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "517358c28fcef6607bf6f76108e02afad7e82297d132a6b846dcc1fc3efcd153" +dependencies = [ + "heck 0.4.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cookie-factory" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98a479f8099cc5ac64915a3dd76c87be27f929ba406ad705aacb13f19b791207" + +[[package]] +name = "cookie-factory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding 2.1.0", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" + +[[package]] +name = "futures-executor" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" + +[[package]] +name = "futures-macro" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" + +[[package]] +name = "futures-task" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" + +[[package]] +name = "futures-util" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "handlebars" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82e5750d8027a97b9640e3fefa66bbaf852a35228e1c90790efd13c4b09c166" +dependencies = [ + "lazy_static", + "log", + "pest", + "pest_derive", + "quick-error 1.2.3", + "regex", + "serde", + "serde_json", + "walkdir", +] + +[[package]] +name = "handlebars" +version = "3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error 2.0.1", + "serde", + "serde_json", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "input_buffer" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acee673b88a760f5d1f7b2677a90ab797878282ca36ebd0ed8d560361bee9810" +dependencies = [ + "bytes", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 1.0.0", + "ryu", + "static_assertions", +] + +[[package]] +name = "libc" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "lock_api" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.2", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +dependencies = [ + "libc", + "log", + "miow 0.3.7", + "ntapi", + "winapi 0.3.9", +] + +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +dependencies = [ + "lazycell", + "log", + "mio 0.6.23", + "slab", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +dependencies = [ + "memchr", + "version_check 0.1.5", +] + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check 0.9.3", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check 0.9.3", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check 0.9.3", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + +[[package]] +name = "rustls" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1" +dependencies = [ + "base64 0.11.0", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", +] + +[[package]] +name = "rustls" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +dependencies = [ + "log", + "ring", + "sct 0.7.0", + "webpki 0.22.0", +] + +[[package]] +name = "rustls-connector" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51523e659cf9f4d6ec738e58854c7670c898ffc2f8c7d80afca8ca3b063854f4" +dependencies = [ + "log", + "rustls 0.17.0", + "webpki 0.21.4", + "webpki-roots", +] + +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64 0.13.0", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "snafu" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eba135d2c579aa65364522eb78590cdf703176ef71ad4c32b00f58f7afb2df5" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a7fe9b0669ef117c5cabc5549638528f36771f058ff977d7689deb517833a75" +dependencies = [ + "heck 0.3.3", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "static_assertions" +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.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tcp-stream" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde4c09dccd2f92acfd27cafd2f4fb46e073959e8044928073f22a96fe51c75d" +dependencies = [ + "cfg-if 0.1.10", + "mio 0.6.23", + "rustls-connector", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" + +[[package]] +name = "thiserror" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio 0.7.13", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" +dependencies = [ + "rustls 0.20.2", + "tokio", + "webpki 0.22.0", +] + +[[package]] +name = "tokio-util" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "typenum" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicode-bidi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna 0.1.5", + "matches", + "percent-encoding 1.0.1", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna 0.2.3", + "matches", + "percent-encoding 2.1.0", +] + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi 0.3.9", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eff4b7516a57307f9349c64bf34caa34b940b66fed4b2fb3136cb7386e5739" +dependencies = [ + "webpki 0.21.4", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/tests/performance_tester/Cargo.toml b/tests/performance_tester/Cargo.toml new file mode 100644 index 0000000..9c2f8ff --- /dev/null +++ b/tests/performance_tester/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "amqpprox_perf_tester" +version = "0.1.0" +authors = ["Alaric "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "1.15.0", features = ["full"] } +tokio-util = { version = "0.6", features = ["codec"] } + +# The latest amq-protocol is 7.0.0 but somewhere along the way the interface +# became incompatible with tokio-codec as we're using it +amq-protocol = { version = "4.2.2", features = ["rustls"], default_features=false } +anyhow = "1.0" +thiserror = "1.0" +clap = { version = "3.0.7", features = ["derive"] } +env_logger = "0.8" +log = "0.4" +bytes = "1.0" +futures = "0.3" +rustls-pemfile = "0.2.1" +tokio-rustls = "0.23.1" +amiquip = { version = "0.4", default-features = false } diff --git a/tests/performance_tester/README.md b/tests/performance_tester/README.md new file mode 100644 index 0000000..6c89693 --- /dev/null +++ b/tests/performance_tester/README.md @@ -0,0 +1,146 @@ +# amqpprox Performance Testing + +`amqpprox` performance has been tested mainly in two ways: + +1. Total data throughput achieved by connected clients (MB/s) +2. Total connection establishment throughput achieved (connections/s) + +The performance tester in this folder helps with both of these by: + +1. It contains a dummy AMQP 0.9.1. server which walks the AMQP handshake and ignores all future frames except close. +2. It can run parallel AMQP clients connecting to an amqpprox instance. + 1. Testing data throughput probably wants to send more, larger messages with fewer connections + 2. Testing connection throughput probably wants to send fewer, smaller messages with many connections. + +## Usage Help + +``` +amqpprox_perf_tester + +USAGE: + amqpprox_perf_tester [OPTIONS] --listen-address + +OPTIONS: + --address
+ [default: amqp://localhost:5672/] + + --clients + Number of total AMQP clients to run [default: 10] + + -h, --help + Print help information + + --listen-address + IP Address/port for the dummy AMQP server to listen on + + --listen-cert + TLS cer used by the dummy AMQP server + + --listen-key + TLS key used by the dummy AMQP server. Must be the appropriate key for the provided cert + + --max-threads + Max AMQP clients which can run in parallel [default: 50] + + --message-size + [default: 100] + + --num-messages + [default: 10] + + --routing-key + Routing key passed for sent messages [default: routing-key] +``` + +## Performance Testing + +### Setting up `amqpprox` + +In order to use the perf tool we need to run amqpprox somewhere configured to point at the dummy AMQP server started by this tool. + +`amqpprox` can either run on the same machine as `amqpprox_perf_tester`, or elsewhere. In this example the IP addresses for +these hosts are replaced with `` and `` +``` +$ amqpprox --listenPort 30672 --destinationPort 5672 --destinationDNS +``` + +It's important to build `amqpprox_perf_tester` in release mode, especially for the TLS tests using `rustls`. + +### Connection throughput testing + +Run the perf tester with parameters which minimise the work required per connection, such as: +``` +$ RUST_LOG=warn cargo run --release -- --address :30672 --listen-address 0.0.0.0:5672 --clients 100000 --max-threads 100 --message-size 1 --num-messages 1 +100000 clients and 100000KB in 91.034013869seconds +1857.9424459252837 connections/second, 0.0018579424459252835 MB/second +``` + +These numbers were reached by tweaking until the connections/second figure stopped rising. +`amqpprox` showed ~70% CPU usage during the test run above. Since `amqpprox` is primarily +single threaded, this is a vague indication of it approaching saturation. + +### Data throughput testing +Just like connection throughput testing but with paramters which exercise more, larger messages +rather than connections. + +``` +$ RUST_LOG=warn cargo run --release -- --address :30672 --listen-address 0.0.0.0:5672 --clients 10 --max-threads 10 --message-size 10000000 --num-messages 50 +10 clients and 5000000000KB in 9.054987161seconds +1.1043637966788278 connections/second, 552.1818983394139 MB/second +``` + +### Testing with TLS + +TLS impacts the performance of amqpprox by introducing extra overhead with each connection establishment & on-going overhead for data transferred. + +This tool can be used to study part of this impact, by enabling TLS on the dummy AMQP server & configuring amqpprox +to connect to that dummy AMQP server over TLS. + +#### Generate Certificates +To test TLS we need some keys/certificates for the connections. + +In this setup we want to generate a key & certificate for the dummy AMQP server, then pass +this same certificate to amqpprox for validation. + +``` +openssl ecparam -out ec_key.pem -name secp256r1 -genkey +openssl req -new -key ec_key.pem -x509 -nodes -days 365 -out cert.pem +openssl pkcs8 -topk8 -nocrypt -in ec_key.pem -out key.pem # Convert EC Key to pkcs8 +``` + +#### Start amqpprox +`amqpprox` doesn't have a quick start option for a TLS backend, so we need to manually start and configure it: + +``` +amqpprox & +amqpprox_ctl /tmp/amqpprox TLS EGRESS CA_CERT_FILE cert.pem +amqpprox_ctl /tmp/amqpprox TLS EGRESS VERIFY_MODE PEER + +# Depending on your openssl version/settings you may need to specifically enable EC ciphers +amqpprox_ctl /tmp/amqpprox TLS EGRESS CIPHERS SET TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 + +amqpprox_ctl /tmp/amqpprox BACKEND ADD backend dc 5671 TLS +amqpprox_ctl /tmp/amqpprox FARM ADD farm round-robin backend +amqpprox_ctl /tmp/amqpprox MAP DEFAULT farm +amqpprox_ctl /tmp/amqpprox LISTEN START 30671 +``` + +#### TLS Connection Throughput Testing + +Running `amqpprox_perf_tester` with `--listen-cert <> --listen-key` tells the dummy AMQP server to listen for inbound TLS connections. + +An example run, against an amqpprox instance configured as above: +``` +RUST_LOG=warn cargo run --release -- --address amqp://:30672/ --listen-address 0.0.0.0:5671 --message-size 1 --num-messages 1 --clients 50000 --max-threads 300 --listen-cert cert.pem --listen-key key.pem +50000 clients and 50000KB in 50.811844034seconds +984.0225433767613 connections/second, 0.0009840225433767613 MB/second +``` + +#### TLS Data Throughput Testing + + +``` +RUST_LOG=warn cargo run --release -- --address amqp://:30672/ --listen-address 0.0.0.0:5671 --clients 10 --max-threads 10 --message-size 10000000 --num-messages 100 --listen-cert cert.pem --listen-key key.pem +10 clients and 10000000000KB in 15.120871009seconds +0.6613375640892619 connections/second, 661.337564089262 MB/second +``` diff --git a/tests/performance_tester/integration-tests.py b/tests/performance_tester/integration-tests.py new file mode 100644 index 0000000..4d217cf --- /dev/null +++ b/tests/performance_tester/integration-tests.py @@ -0,0 +1,96 @@ +# +# Copyright 2022 Bloomberg Finance L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import pytest +import socket +from contextlib import closing +import subprocess +import os +from time import sleep + +AMQPPROX_PORT = 5672 +PERF_TEST_PORT = 5671 + + +def check_socket(host, port): + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: + if sock.connect_ex((host, port)) == 0: + return True + else: + return False + + +@pytest.fixture(scope="module") +def amqpprox(): + amqpprox = os.environ.get("AMQPPROX_BIN_DIR") + + if not amqpprox: + amqpprox = "amqpprox" + else: + amqpprox = amqpprox + "/amqpprox" + + instance = subprocess.Popen( + [ + amqpprox, + "--listenPort", + str(AMQPPROX_PORT), + "--destinationPort", + str(PERF_TEST_PORT), + "--destinationDNS", + "localhost" + ], + ) + + while not check_socket("localhost", AMQPPROX_PORT): + sleep(0.5) + + yield f"amqp://localhost:{AMQPPROX_PORT}" + + instance.kill() + + +def run_perf_test_command(amqpprox_url, message_size, num_messages, max_threads, clients): + port = 19305 + env = os.environ.copy() + env["RUST_LOG"] = "info" + + return subprocess.run( + [ + env.get("CARGO_PATH", "cargo"), + "run", + "--release", + "--manifest-path", + "tests/performance_tester/Cargo.toml", + "--", + "--address", + amqpprox_url, + "--listen-address", + f"127.0.0.1:{PERF_TEST_PORT}", + "--message-size", + str(message_size), + "--num-messages", + str(num_messages), + "--max-threads", + str(max_threads), + "--clients", + str(clients) + ], + env=env, + ) + + +def test_100_clients(amqpprox): + assert run_perf_test_command(amqpprox, message_size=100, num_messages=1, max_threads=10, clients=100).returncode == 0 diff --git a/tests/performance_tester/src/client.rs b/tests/performance_tester/src/client.rs new file mode 100644 index 0000000..8c82521 --- /dev/null +++ b/tests/performance_tester/src/client.rs @@ -0,0 +1,45 @@ +/* +** Copyright 2022 Bloomberg Finance L.P. +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +use amiquip::{Connection, Exchange, Publish}; +use anyhow::Result; + +/// Start an AMQP client connecting to address, sending num_messages of message_size. +pub(crate) fn run_sync_client( + address: String, + message_size: usize, + num_messages: usize, + routing_key: &str, +) -> Result<()> { + let mut connection = Connection::insecure_open(&address)?; + let channel = connection.open_channel(None)?; + let exchange = Exchange::direct(&channel); + + let mut arr = Vec::new(); + arr.resize(message_size, 0); + + let mut count = 0; + loop { + if count > num_messages { + break; + } + exchange.publish(Publish::new(&arr, routing_key))?; + + count += 1; + } + + Ok(()) +} diff --git a/tests/performance_tester/src/main.rs b/tests/performance_tester/src/main.rs new file mode 100644 index 0000000..50cb4e7 --- /dev/null +++ b/tests/performance_tester/src/main.rs @@ -0,0 +1,191 @@ +/* +** Copyright 2022 Bloomberg Finance L.P. +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +use anyhow::Result; +use clap::Parser; +use std::net::SocketAddr; +use std::path::PathBuf; +use std::time::Duration; +use std::time::Instant; +use tokio::runtime::Builder; + +mod client; +mod server; + +#[derive(Debug, Parser, Clone)] +struct PerfTesterOpts { + #[clap(long, default_value = "amqp://localhost:5672/")] + address: String, + + #[clap( + long, + default_value_t = 10, + help = "Number of total AMQP clients to run" + )] + clients: usize, + + #[clap(long, default_value_t = 100)] + message_size: usize, + + #[clap(long, default_value_t = 10)] + num_messages: usize, + + #[clap( + long, + default_value_t = 50, + help = "Max AMQP clients which can run in parallel" + )] + max_threads: usize, + + #[clap(long, help = "IP Address/port for the dummy AMQP server to listen on")] + listen_address: SocketAddr, + + #[clap(long, help = "TLS cer used by the dummy AMQP server")] + listen_cert: Option, + + #[clap( + long, + help = "TLS key used by the dummy AMQP server. Must be the appropriate key for the provided cert" + )] + listen_key: Option, + + #[clap( + long, + default_value = "routing-key", + help = "Routing key passed for sent messages" + )] + routing_key: String, +} + +fn main() -> Result<()> { + env_logger::init(); + let opts = PerfTesterOpts::parse(); + + let start = Instant::now(); + + let mut success = 0; + + { + let runtime = Builder::new_multi_thread() + .enable_all() + .max_blocking_threads(opts.max_threads) + .build() + .unwrap(); + + let opts = opts.clone(); + runtime.block_on(async { + println!("Starting performance test of amqpprox"); + + let address = opts.listen_address; + let _server = if let (Some(listen_cert), Some(listen_key)) = + (opts.listen_cert, opts.listen_key) + { + println!("Starting TLS dummy amqp server"); + let acceptor = server::create_tls_acceptor(&listen_cert, &listen_key).unwrap(); + tokio::spawn(async move { + server::run_tls_server(address, acceptor).await + }) + } else { + println!("Starting non-TLS dummy amqp server"); + tokio::spawn(async move { server::run_server(address).await }) + }; + + wait_for_addr(opts.listen_address, Duration::from_millis(10000)) + .await + .unwrap(); + + let mut handles = Vec::new(); + for _ in 0..opts.clients { + let address = opts.address.clone(); + let message_size = opts.message_size; + let num_messages = opts.num_messages; + let routing_key = opts.routing_key.clone(); + + let handle = tokio::task::spawn_blocking(move || { + crate::client::run_sync_client(address, message_size, num_messages, &routing_key) + }); + handles.push(handle); + } + + for handle in handles { + match handle.await.unwrap() { + Ok(_) => success += 1, + Err(err) => log::error!("Client failed: {:?}", err), + } + } + }); + } + + if success != opts.clients { + println!("{} clients were not fully successful. Check the logs to see if this will impact perf results", opts.clients - success); + } + + let duration = start.elapsed(); + let total_bytes = opts.clients * opts.num_messages * opts.message_size; + println!( + "{} clients and {}KiB in {}seconds", + opts.clients, + total_bytes / 1024, + duration.as_secs_f64() + ); + + let clients_per_sec = opts.clients as f64 / duration.as_secs_f64(); + let bytes_per_sec = total_bytes as f64 / duration.as_secs_f64(); + + println!( + "{} connections/second, {} MiB/second", + clients_per_sec, + bytes_per_sec / 1024f64 / 1024f64 + ); + + Ok(()) +} + +async fn wait_for_addr(addr: SocketAddr, timeout_total: Duration) -> Result<()> { + let iterations = 10; + let timeout_step = timeout_total / iterations; + + let mut iteration = 1; + + loop { + let start = Instant::now(); + + match try_addr(addr, timeout_step).await { + Ok(()) => return Ok(()), + Err(err) => { + println!("Waiting for dummy server to start: {:?}", err); + if iteration == iterations { + return Err(err); + } + } + } + + let target = start.checked_add(timeout_step).ok_or(anyhow::anyhow!("Timeout add overflowed"))?; + if let Some(sleep_time) = target.checked_duration_since(Instant::now()) { + tokio::time::sleep(sleep_time).await; + } + + iteration += 1; + } +} + +async fn try_addr(addr: SocketAddr, timeout: Duration) -> Result<()> { + let connect = tokio::net::TcpStream::connect(addr); + + let _: tokio::net::TcpStream = tokio::time::timeout(timeout, connect).await??; + + Ok(()) +} diff --git a/tests/performance_tester/src/server.rs b/tests/performance_tester/src/server.rs new file mode 100644 index 0000000..f662399 --- /dev/null +++ b/tests/performance_tester/src/server.rs @@ -0,0 +1,262 @@ +/* +** Copyright 2022 Bloomberg Finance L.P. +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +use amq_protocol::frame::AMQPFrame; +use amq_protocol::frame::GenError; +use amq_protocol::frame::WriteContext; +use amq_protocol::protocol::channel; +use amq_protocol::protocol::connection; +use amq_protocol::protocol::AMQPClass; +use amq_protocol::types::AMQPValue; +use anyhow::bail; +use anyhow::Result; +use bytes::BytesMut; +use futures::SinkExt; +use futures::StreamExt; +use rustls_pemfile::{certs, pkcs8_private_keys}; +use std::fs::File; +use std::io; +use std::io::BufReader; +use std::io::Cursor; +use std::net::SocketAddr; +use std::path::Path; +use std::sync::Arc; +use tokio::io::AsyncReadExt; +use tokio::net::TcpListener; +use tokio_rustls::rustls::{self, Certificate, PrivateKey}; +use tokio_rustls::TlsAcceptor; +use AMQPFrame::Method; + +fn load_certs(path: &Path) -> io::Result> { + certs(&mut BufReader::new(File::open(path)?)) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert")) + .map(|mut certs| certs.drain(..).map(Certificate).collect()) +} + +fn load_keys(path: &Path) -> io::Result> { + pkcs8_private_keys(&mut BufReader::new(File::open(path)?)) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid key")) + .map(|mut keys| keys.drain(..).map(PrivateKey).collect()) +} + +#[derive(thiserror::Error, Debug)] +enum AMQPCodecError { + #[error("Underlying Error: {0}")] + Underlying(String), + + #[error("Generate error")] + GenError(#[from] GenError), + + #[error(transparent)] + Other(#[from] anyhow::Error), +} + +impl From for AMQPCodecError { + fn from(error: std::io::Error) -> AMQPCodecError { + AMQPCodecError::Underlying(error.to_string()) + } +} + +struct AMQPCodec {} + +impl tokio_util::codec::Decoder for AMQPCodec { + type Item = AMQPFrame; + type Error = AMQPCodecError; + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + log::trace!("Attempt decode from: {} bytes", src.len()); + let (consumed, res) = match amq_protocol::frame::parse_frame(src) { + Ok((consumed, frame)) => (src.len() - consumed.len(), Ok(Some(frame))), + Err(e) => { + if e.is_incomplete() { + (0, Ok(None)) + } else { + ( + 0, + Err(AMQPCodecError::Underlying(format!( + "Parse error for frame: {:?}", + e + ))), + ) + } + } + }; + + log::trace!("Consumed: {}, Res: {:?}", consumed, res); + let _ = src.split_to(consumed); + res + } +} + +impl tokio_util::codec::Encoder for AMQPCodec { + type Error = AMQPCodecError; + fn encode(&mut self, item: AMQPFrame, dst: &mut BytesMut) -> Result<(), Self::Error> { + loop { + let res = amq_protocol::frame::gen_frame(&item)(WriteContext::from(Cursor::new( + dst.as_mut(), + ))); + match res { + Ok(wc) => { + let (_writer, position) = wc.into_inner(); + dst.resize(position as usize, 0); + return Ok(()); + } + Err(amq_protocol::frame::GenError::BufferTooSmall(sz)) => { + let capacity = dst.capacity(); + dst.resize(capacity + sz, 0); + } + Err(e) => { + return Err(e.into()); + } + } + } + } +} + +impl AMQPCodec { + fn new() -> Self { + Self {} + } +} + +pub async fn process_connection< + Stream: tokio::io::AsyncRead + std::marker::Unpin + tokio::io::AsyncWrite, +>( + mut socket: Stream, +) -> Result<()> { + let mut buf: [u8; 8] = [0; 8]; + socket.read_exact(&mut buf).await?; // We ignore if it's correct + + log::debug!("Protocol header received"); + + let codec = AMQPCodec::new(); + let mut framed = tokio_util::codec::Framed::new(socket, codec); + + let mut server_props = amq_protocol::types::FieldTable::default(); + server_props.insert( + String::from("product").into(), + AMQPValue::LongString(String::from("amqpprox_perf_test").into()), + ); + + let start_method = connection::AMQPMethod::Start(connection::Start { + version_major: 0, + version_minor: 9, + mechanisms: "PLAIN".to_string().into(), + locales: "en_US".to_string().into(), + server_properties: server_props, + }); + let start = Method(0, AMQPClass::Connection(start_method)); + framed.send(start).await?; + + let frame = framed.next().await; + if let Some(Ok(Method(0, AMQPClass::Connection(connection::AMQPMethod::StartOk(frame))))) = + frame + { + log::debug!("Should be start-ok: {:?}", frame); + let tune_method = connection::AMQPMethod::Tune(connection::Tune { + channel_max: 2047, + frame_max: 131072, + heartbeat: 60, + }); + let tune = Method(0, AMQPClass::Connection(tune_method)); + framed.send(tune).await?; + } else { + bail!("Invalid protocol, received: {:?}", frame); + } + + let frame = framed.next().await; + if let Some(Ok(Method(0, AMQPClass::Connection(connection::AMQPMethod::TuneOk(frame))))) = frame + { + log::debug!("Should be tune-ok: {:?}", frame); + } else { + bail!("Invalid protocol, received: {:?}", frame); + } + + let frame = framed.next().await; + if let Some(Ok(Method(0, AMQPClass::Connection(connection::AMQPMethod::Open(frame))))) = frame { + log::debug!("Should be open: {:?}", frame); + let openok_method = connection::AMQPMethod::OpenOk(connection::OpenOk {}); + let openok = Method(0, AMQPClass::Connection(openok_method)); + framed.send(openok).await?; + } else { + bail!("Invalid protocol, received: {:?}", frame); + } + + log::info!("Handshake complete"); + while let Some(frame) = framed.next().await { + log::trace!("Received: {:?}", &frame); + match frame { + Ok(Method(channel, AMQPClass::Channel(channelmsg))) => { + log::debug!("Set up channel: {} {:?}", channel, channelmsg); + let channelok_method = channel::AMQPMethod::OpenOk(channel::OpenOk {}); + let channelok = Method(channel, AMQPClass::Channel(channelok_method)); + framed.send(channelok).await?; + } + Ok(Method(0, AMQPClass::Connection(connection::AMQPMethod::Close(closemsg)))) => { + log::info!("Closing connection requested: {:?}", closemsg); + let closeok_method = connection::AMQPMethod::CloseOk(connection::CloseOk {}); + let closeok = Method(0, AMQPClass::Connection(closeok_method)); + framed.send(closeok).await?; + log::info!("Closing connection requested: {:?}. Sent CloseOk", closemsg); + return Ok(()); + } + _ => {} + } + } + Ok(()) +} + +pub fn create_tls_acceptor(cert_chain: &Path, key: &Path) -> Result { + let certs = load_certs(cert_chain)?; + let mut keys = load_keys(key)?; + + log::info!("{:?} {:?}", certs, keys); + + let config = rustls::ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(certs, keys.remove(0)) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?; + + Ok(TlsAcceptor::from(Arc::new(config))) +} + +pub async fn run_tls_server(address: SocketAddr, acceptor: TlsAcceptor) -> Result<()> { + + log::info!("Listening on {:?}", &address); + let listener = TcpListener::bind(address).await?; + + loop { + let (socket, peer) = listener.accept().await?; + let acceptor = acceptor.clone(); + log::debug!("Connection from {}", peer); + tokio::spawn(async move { + let stream = acceptor.accept(socket).await?; + + process_connection(stream).await + }); + } +} + +pub async fn run_server(address: SocketAddr) -> Result<()> { + log::info!("Listening on {:?}", &address); + let listener = TcpListener::bind(address).await?; + + loop { + let (socket, peer) = listener.accept().await?; + log::debug!("Connection from {}", peer); + tokio::spawn(async move { process_connection(socket).await }); + } +}