From 5496612bd37b052ac766556ca801dc2603a74c00 Mon Sep 17 00:00:00 2001 From: Nathan Lie Date: Tue, 20 Aug 2024 10:43:18 +0200 Subject: [PATCH] feat(tests): initial performance test (#2828) * feat(tests): initial performance test * feat: add test shell scripts, run inside docker network * feat: run script in docker container * chore: formatting * chore: remove unused script * fix: update test script parameters * fix: ping wallet address hosts instead of wallet addresses * fix README * feat: move wallet address retrieval into setup function * feat: add another error condition * formatting * fix: typechecks * chore: remove comments * chore: remove empty metadata object --- pnpm-lock.yaml | 79 ++++----- pnpm-workspace.yaml | 2 +- test/performance/README.md | 30 ++++ test/performance/package.json | 18 ++ .../scripts/create-outgoing-payments.js | 154 ++++++++++++++++++ test/performance/scripts/run-tests.sh | 57 +++++++ 6 files changed, 301 insertions(+), 39 deletions(-) create mode 100644 test/performance/README.md create mode 100644 test/performance/package.json create mode 100644 test/performance/scripts/create-outgoing-payments.js create mode 100755 test/performance/scripts/run-tests.sh diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7f897338c..ba7cf22bc9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -784,6 +784,12 @@ importers: specifier: ^2.5.0 version: 2.5.0 + test/performance: + dependencies: + hostile: + specifier: ^1.4.0 + version: 1.4.0 + packages: /@aashutoshrathi/word-wrap@1.2.6: @@ -870,7 +876,6 @@ packages: zen-observable-ts: 1.2.5 transitivePeerDependencies: - '@types/react' - dev: true /@apollo/client@3.9.9(@types/react@18.2.73)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-/sMecU/M0WK9knrguts1lSLV8xFKzIgOMVb4mi6MOxgJXjliDB8PvOtmXhTqh2cVMMR4TzXgOnb+af/690zlQw==} @@ -5058,7 +5063,6 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 - dev: true /@mdx-js/mdx@2.3.0: resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==} @@ -5227,7 +5231,7 @@ packages: resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: - semver: 7.6.2 + semver: 7.6.0 dev: true /@npmcli/git@4.1.0: @@ -5240,7 +5244,7 @@ packages: proc-log: 3.0.0 promise-inflight: 1.0.1 promise-retry: 2.0.1 - semver: 7.6.2 + semver: 7.6.0 which: 3.0.1 transitivePeerDependencies: - bluebird @@ -5256,7 +5260,7 @@ packages: json-parse-even-better-errors: 3.0.1 normalize-package-data: 5.0.0 proc-log: 3.0.0 - semver: 7.6.2 + semver: 7.6.0 transitivePeerDependencies: - bluebird dev: true @@ -5452,7 +5456,7 @@ packages: '@opentelemetry/api': ^1.7.0 dependencies: '@opentelemetry/api': 1.8.0 - '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.8.0) + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.8.0) transitivePeerDependencies: - supports-color @@ -5467,8 +5471,8 @@ packages: '@opentelemetry/api': 1.8.0 '@opentelemetry/api-logs': 0.52.1 '@types/shimmer': 1.2.0 - import-in-the-middle: 1.9.0 - require-in-the-middle: 7.3.0 + import-in-the-middle: 1.11.0 + require-in-the-middle: 7.4.0 semver: 7.6.2 shimmer: 1.2.1 transitivePeerDependencies: @@ -5724,7 +5728,7 @@ packages: '@opentelemetry/api': ^1.1.0 dependencies: '@opentelemetry/api': 1.8.0 - '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.8.0) + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) dev: false /@ory/client@1.9.0: @@ -6717,7 +6721,7 @@ packages: resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==} dependencies: '@types/node': 20.14.15 - '@types/qs': 6.9.12 + '@types/qs': 6.9.14 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -6726,7 +6730,7 @@ packages: dependencies: '@types/body-parser': 1.19.5 '@types/express-serve-static-core': 4.17.43 - '@types/qs': 6.9.12 + '@types/qs': 6.9.14 '@types/serve-static': 1.15.5 /@types/graceful-fs@4.1.5: @@ -6839,7 +6843,7 @@ packages: '@types/http-errors': 2.0.4 '@types/keygrip': 1.0.2 '@types/koa-compose': 3.2.5 - '@types/node': 20.12.7 + '@types/node': 20.14.15 dev: true /@types/koa__cors@5.0.0: @@ -6961,12 +6965,8 @@ packages: /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} - /@types/qs@6.9.12: - resolution: {integrity: sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg==} - /@types/qs@6.9.14: resolution: {integrity: sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==} - dev: true /@types/range-parser@1.2.7: resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} @@ -7116,7 +7116,7 @@ packages: grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 - semver: 7.6.2 + semver: 7.6.0 tsutils: 3.21.0(typescript@5.4.3) typescript: 5.4.3 transitivePeerDependencies: @@ -7229,7 +7229,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 5.60.1(typescript@5.4.3) '@typescript-eslint/utils': 5.60.1(eslint@8.57.0)(typescript@5.4.3) - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 eslint: 8.57.0 tsutils: 3.21.0(typescript@5.4.3) typescript: 5.4.3 @@ -7283,7 +7283,7 @@ packages: dependencies: '@typescript-eslint/types': 5.60.1 '@typescript-eslint/visitor-keys': 5.60.1 - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.2 @@ -7329,7 +7329,7 @@ packages: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.2 + semver: 7.6.0 ts-api-utils: 1.0.1(typescript@5.4.3) typescript: 5.4.3 transitivePeerDependencies: @@ -7389,7 +7389,7 @@ packages: '@typescript-eslint/types': 7.5.0 '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.3) eslint: 8.57.0 - semver: 7.6.2 + semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript @@ -7668,7 +7668,7 @@ packages: resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} engines: {node: '>= 14'} dependencies: - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -8630,7 +8630,6 @@ packages: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: true /chalk@5.3.0: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} @@ -9779,7 +9778,7 @@ packages: resolution: {integrity: sha512-h0Ow21gclbYsZ3mkHDfsYNDqtRhXS8fXr51bU0qr1dxgTMJj0XufbzX+jhNOvA8KuEEzn6JbvLVhXyv+fny9Uw==} engines: {node: '>= 8.0'} dependencies: - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 readable-stream: 3.6.0 split-ca: 1.0.1 ssh2: 1.11.0 @@ -11834,7 +11833,6 @@ packages: minimist: 1.2.8 once: 1.4.0 split: 1.0.1 - dev: true /html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} @@ -11903,7 +11901,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -11926,7 +11924,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -12035,8 +12033,8 @@ packages: engines: {node: '>=12.2'} dev: true - /import-in-the-middle@1.9.0: - resolution: {integrity: sha512-Ng1SJINJDBzyUEkx9Mj32XD8G0TQCUb5TMoL9V91CTn6F3wYZLygLuhNFrv0cNMBZaeptnL1zecV6XrIdHJ+xQ==} + /import-in-the-middle@1.11.0: + resolution: {integrity: sha512-5DimNQGoe0pLUHbR9qK84iWaWjjbsxiqXnw6Qz64+azRgleqv9k2kTt5fw7QsOpmaGYtuxxursnPPsnTKEx10Q==} dependencies: acorn: 8.12.0 acorn-import-attributes: 1.9.5(acorn@8.12.0) @@ -12629,7 +12627,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -13430,7 +13428,7 @@ packages: content-disposition: 0.5.4 content-type: 1.0.5 cookies: 0.9.1 - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 delegates: 1.0.0 depd: 2.0.0 destroy: 1.2.0 @@ -14741,7 +14739,7 @@ packages: resolution: {integrity: sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==} dependencies: '@types/debug': 4.1.7 - debug: 4.3.4(supports-color@9.4.0) + debug: 4.3.5 decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.0.6 micromark-factory-space: 1.0.0 @@ -15148,7 +15146,7 @@ packages: dependencies: hosted-git-info: 6.1.1 is-core-module: 2.13.0 - semver: 7.6.2 + semver: 7.6.0 validate-npm-package-license: 3.0.4 dev: true @@ -16616,7 +16614,6 @@ packages: optional: true react: optional: true - dev: true /rehype-expressive-code@0.35.6: resolution: {integrity: sha512-pPdE+pRcRw01kxMOwHQjuRxgwlblZt5+wAc3w2aPGgmcnn57wYjn07iKO7zaznDxYVxMYVvYlnL+R3vWFQS4Gw==} @@ -16889,8 +16886,8 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - /require-in-the-middle@7.3.0: - resolution: {integrity: sha512-nQFEv9gRw6SJAwWD2LrL0NmQvAcO7FBwJbwmr2ttPAacfy0xuiOjE5zt+zM4xDyuyvUaxBi/9gb2SoCyNEVJcw==} + /require-in-the-middle@7.4.0: + resolution: {integrity: sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==} engines: {node: '>=8.6.0'} dependencies: debug: 4.3.5 @@ -17217,6 +17214,14 @@ packages: lru-cache: 6.0.0 dev: true + /semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + /semver@7.6.2: resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} engines: {node: '>=10'} @@ -17526,7 +17531,6 @@ packages: resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} dependencies: through: 2.3.8 - dev: true /sponge-case@1.0.1: resolution: {integrity: sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==} @@ -18089,7 +18093,6 @@ packages: /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - dev: true /tigerbeetle-node@0.15.4: resolution: {integrity: sha512-HJRZ9OQzv56cE+EqqkzLk2VMkPRroFlv1/HGLN7wjcdElOAz78GX5fBfi7/IsJWacIrpIdgSkZnvKDOWQtJXyQ==} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7d4d7b52cc..9566f88d3c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,5 @@ packages: - 'packages/*' - 'localenv/mock-account-servicing-entity' - - 'test/integration' + - 'test/*' - 'bruno/collections/Rafiki' diff --git a/test/performance/README.md b/test/performance/README.md new file mode 100644 index 0000000000..7ae13794e5 --- /dev/null +++ b/test/performance/README.md @@ -0,0 +1,30 @@ +# Performance Tests + +This package contains a script that will determine the performance of Rafiki by repeatedly making a series of requests to a Rafiki instance to create several kinds of resources (receivers, quotes, outgoing payments). + +## Prerequisites + +- [Grafana k6](https://grafana.com/docs/k6/latest/set-up/install-k6/) + + - [Grafana k6](https://grafana.com/docs/k6/latest/) is used to run performance test scripts against Rafiki. + +- [Running local playground for Rafiki](../../localenv/README.md) + - It is recommended to start the local playground with Telemetry running in order to see the impact of a performance test. + +If the local environment isn't running it may be started by using the command `pnpm localenv:compose:telemetry:up`. + +## Run tests + +To run the performance tests (of which there is currently only one): + +``` +pnpm --filter performance run-tests +``` + +The test makes a few checks to verify the local playground is running, then runs the k6 binary on the [create-outgoing-payments.js](./scripts/create-outgoing-payments.js) script. + +The test can also be run inside of a Docker container on the same Docker network as the Local Playground: + +``` +pnpm --filter performance run-tests-docker +``` diff --git a/test/performance/package.json b/test/performance/package.json new file mode 100644 index 0000000000..64414cd095 --- /dev/null +++ b/test/performance/package.json @@ -0,0 +1,18 @@ +{ + "name": "performance", + "version": "1.0.0", + "description": "", + "scripts": { + "test": "k6 run ./scripts/create-outgoing-payments.js", + "test-docker": "docker run --rm --network=rafiki_rafiki -v ./scripts:/scripts -i grafana/k6 run /scripts/create-outgoing-payments.js", + "run-tests": "./scripts/run-tests.sh", + "run-tests-docker": "./scripts/run-tests.sh --docker", + "hostile": "hostile" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "hostile": "^1.4.0" + } +} diff --git a/test/performance/scripts/create-outgoing-payments.js b/test/performance/scripts/create-outgoing-payments.js new file mode 100644 index 0000000000..1b3b883206 --- /dev/null +++ b/test/performance/scripts/create-outgoing-payments.js @@ -0,0 +1,154 @@ +import http from 'k6/http' +import { fail } from 'k6' +export const options = { + // A number specifying the number of VUs to run concurrently. + vus: 1, + // A string specifying the total duration of the test run. + duration: '600s' +} + +const HEADERS = { + 'Content-Type': 'application/json' +} + +const CLOUD_NINE_GQL_ENDPOINT = 'http://cloud-nine-wallet-backend:3001/graphql' +const CLOUD_NINE_WALLET_ADDRESS = + 'https://cloud-nine-wallet-backend/accounts/gfranklin' +const HAPPY_LIFE_BANK_WALLET_ADDRESS = + 'https://happy-life-bank-backend/accounts/pfry' + +export function setup() { + const c9WalletAddressesRes = http.post( + CLOUD_NINE_GQL_ENDPOINT, + JSON.stringify({ + query: ` + query GetWalletAddresses { + walletAddresses { + edges { + node { + id + url + } + } + } + } + ` + }), + { headers: HEADERS } + ) + + if (c9WalletAddressesRes.status !== 200) { + fail(`GraphQL Request failed to find ${CLOUD_NINE_WALLET_ADDRESS}`) + } + const c9WalletAddresses = JSON.parse(c9WalletAddressesRes.body).data + .walletAddresses.edges + const c9WalletAddress = c9WalletAddresses.find( + (edge) => edge.node.url === CLOUD_NINE_WALLET_ADDRESS + ).node + if (!c9WalletAddress) { + fail(`could not find wallet address: ${CLOUD_NINE_WALLET_ADDRESS}`) + } + + return { data: { c9WalletAddress } } +} + +// The function that defines VU logic. +// +// See https://grafana.com/docs/k6/latest/examples/get-started-with-k6/ to learn more +// about authoring k6 scripts. +// +export default function (data) { + const { + data: { c9WalletAddress } + } = data + + const createReceiverResponse = http.post( + CLOUD_NINE_GQL_ENDPOINT, + JSON.stringify({ + query: ` + mutation CreateReceiver($input: CreateReceiverInput!) { + createReceiver(input: $input) { + receiver { + id + } + } + } + `, + variables: { + input: { + expiresAt: null, + metadata: { + description: 'Hello my friend', + externalRef: null + }, + incomingAmount: { + assetCode: 'USD', + assetScale: 2, + value: 1002 + }, + walletAddressUrl: HAPPY_LIFE_BANK_WALLET_ADDRESS + } + } + }), + { + headers: HEADERS + } + ) + + const receiver = JSON.parse(createReceiverResponse.body).data.createReceiver + .receiver + + const createQuoteResponse = http.post( + CLOUD_NINE_GQL_ENDPOINT, + JSON.stringify({ + query: ` + mutation CreateQuote($input: CreateQuoteInput!) { + createQuote(input: $input) { + quote { + id + } + } + } + `, + variables: { + input: { + walletAddressId: c9WalletAddress.id, + receiveAmount: null, + receiver: receiver.id, + debitAmount: { + assetCode: 'USD', + assetScale: 2, + value: 500 + } + } + } + }), + { + headers: HEADERS + } + ) + + const quote = JSON.parse(createQuoteResponse.body).data.createQuote.quote + + http.post( + CLOUD_NINE_GQL_ENDPOINT, + JSON.stringify({ + query: ` + mutation CreateOutgoingPayment($input: CreateOutgoingPaymentInput!) { + createOutgoingPayment(input: $input) { + payment { + id + } + } + } + `, + variables: { + input: { + walletAddressId: c9WalletAddress.id, + quoteId: quote.id + } + } + }), + { headers: HEADERS } + ) +} diff --git a/test/performance/scripts/run-tests.sh b/test/performance/scripts/run-tests.sh new file mode 100755 index 0000000000..17b23bc059 --- /dev/null +++ b/test/performance/scripts/run-tests.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +c9_gql_url="http://localhost:3001/graphql" +c9_wallet_address="http://localhost:3000/" +hlb_wallet_address="http://localhost:4000/" + +# Verify that the localenv backend is live +if curl -s --head --request GET "$c9_gql_url" | grep "HTTP/1.[01]" > /dev/null; then + echo "Localenv is up: $c9_gql_url" +else + echo "Localenv is down: $c9_gql_url" + exit 1 +fi + +# Verify that cloud nine mock ase is live +if curl -s --head --request GET "$c9_wallet_address" | grep "HTTP/1.[01]" > /dev/null; then + echo "Cloud Nine Wallet Address is up: $c9_wallet_address" +else + echo "Cloud Nine Wallet Address is down: $c9_wallet_address" + exit 1 +fi + +# Verify that happy life bank mock ase is live +if curl -s --head --request GET "$hlb_wallet_address" | grep "HTTP/1.[01]" > /dev/null; then + echo "Happy Life Bank Address is up: $hlb_wallet_address" +else + echo "Happy Life Bank Address is down: $hlb_wallet_address" + exit 1 +fi + +# setup hosts +addHost() { + local hostname="$1" + + # check first to avoid sudo prompt if host is already set + if pnpm --filter performance hostile list | grep -q "127.0.0.1 $hostname"; then + echo "$hostname already set" + else + sudo pnpm --filter performance hostile set 127.0.0.1 "$hostname" + if [ $? -ne 0 ]; then + echo "Error: Failed to write hosts to hostfile." + exit 1 + fi + fi +} +addHost "cloud-nine-wallet-backend" +addHost "cloud-nine-wallet-auth" +addHost "happy-life-bank-backend" +addHost "happy-life-bank-auth" + +# run tests +if [[ $* == *--docker* ]]; then + pnpm --filter performance test-docker +else + pnpm --filter performance test +fi +exit $?