diff --git a/.github/workflows/release-tag.yaml b/.github/workflows/release-tag.yaml index 2e47063..a258fdd 100644 --- a/.github/workflows/release-tag.yaml +++ b/.github/workflows/release-tag.yaml @@ -62,6 +62,10 @@ jobs: name: Create GitHub release runs-on: ubuntu-latest steps: + - id: get-tag + run: | + tag=$(echo "${{github.ref}}" | sed 's/^refs\/tags\/v//') + echo "::set-output name=tag::$tag" - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: @@ -72,7 +76,7 @@ jobs: make build working-directory: server env: - DSPS_VERSION_ID=${{ steps.get-tag.outputs.tag }} + DSPS_VERSION_ID: ${{ steps.get-tag.outputs.tag }} # https://zenn.dev/seita/articles/d1dba77043be8fd50eeb - name: Get the version diff --git a/loadtest/.gitignore b/loadtest/.gitignore new file mode 100644 index 0000000..40b1010 --- /dev/null +++ b/loadtest/.gitignore @@ -0,0 +1,6 @@ +/summary.json +/test.json + +/node_modules +/report.*.json +/yarn-error.log diff --git a/loadtest/.keep b/loadtest/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/loadtest/experiment-local.sh b/loadtest/experiment-local.sh new file mode 100755 index 0000000..5c078c0 --- /dev/null +++ b/loadtest/experiment-local.sh @@ -0,0 +1,77 @@ +#!/bin/bash -e +if [ $# -le 1 ]; then + echo "Usage: $0 EXPERIMENT_DIR OUTPUT_DIR" + echo " EXPERIMENT_DIR: directory of the experiment, must have dsps.config.yaml file" + exit 1 +fi + +EXPERIMENT_DIR="$(realpath "$1")" +OUTPUT_DIR="$(realpath "$2")" +cd "$(dirname "$0")" +trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT + +# Ensure $1 points valid directory +if [ ! -f "${EXPERIMENT_DIR}/dsps.config.yaml" ]; then + echo "DSPS config file not found: ${EXPERIMENT_DIR}/dsps.config.yaml" >&2 + exit 2 +fi + +# Create output directories +LOG_DIR="${OUTPUT_DIR}/logs" +RESULT_DIR="${OUTPUT_DIR}/result" +test -d "${LOG_DIR}" && rm -r "${LOG_DIR}" +mkdir -p "${LOG_DIR}" +test -d "${RESULT_DIR}" && rm -r "${RESULT_DIR}" +mkdir -p "${RESULT_DIR}" +exec > >(tee -a "${LOG_DIR}/experiment-local.log") 2>&1 # Save all logs after this + +# Show some limits +ulimit -a +sysctl kern.ipc.somaxconn || true + +export REDIS1_PORT=$(($(($RANDOM%1000))+10000)) +export REDIS2_PORT=$(($(($RANDOM%1000))+11000)) +DSPS_PORT=$(($(($RANDOM%1000))+12000)) + +echo "Starting redis servers... (port: ${REDIS1_PORT}, ${REDIS2_PORT})" +redis-server --port ${REDIS1_PORT} > "${LOG_DIR}/redis1.log" 2>&1 & +redis-server --port ${REDIS2_PORT} > "${LOG_DIR}/redis2.log" 2>&1 & + +echo "Check redis server readiness..." +until redis-cli -p "${REDIS1_PORT}" ping +do + echo "Waiting until redis get ready..." + sleep 1 +done +until redis-cli -p "${REDIS2_PORT}" ping +do + echo "Waiting until redis get ready..." + sleep 1 +done + +echo "Starting DSPS server... (port: ${DSPS_PORT})" +pushd ../server +cat "${EXPERIMENT_DIR}/dsps.config.yaml" | envsubst '${REDIS1_PORT} ${REDIS2_PORT}' | \ + go run main.go --port ${DSPS_PORT} --dump-config - > "${LOG_DIR}/dsps.log" 2>&1 & +popd + +echo "Check DSPS server readiness..." +until curl -s "http://localhost:${DSPS_PORT}/probe/readiness" +do + echo "Waiting until DSPS server get ready..." + sleep 1 +done + +echo "Starting loadtest... ($(LC_ALL=C date))" +BASE_URL="http://localhost:${DSPS_PORT}" \ + time k6 run \ + "--summary-export=${RESULT_DIR}/summary.json" \ + --out "json=${RESULT_DIR}/data.json" \ + --summary-trend-stats="min,avg,med,max,p(90),p(95),p(99)" \ + ./loadtest.k6.js \ + | tee "${LOG_DIR}/k6.log" +echo "Experiment completed ($(LC_ALL=C date))." + +echo "Compressing logs..." +gzip "${LOG_DIR}"/*.log +gzip "${RESULT_DIR}/data.json" diff --git a/loadtest/experiments/20201229-on-macbook/README.md b/loadtest/experiments/20201229-on-macbook/README.md new file mode 100644 index 0000000..d25873e --- /dev/null +++ b/loadtest/experiments/20201229-on-macbook/README.md @@ -0,0 +1,53 @@ +# Quick load test experiment on single macbook + +## Environment + +- MacBook Pro (mac OS 10.15.7, mem 16 GB, Core i7-8557U 8 threads) + - [k6 (load test tool)](https://k6.io/) v0.29.0 + - DSPS server (Darwin/amd64 build, source code revision `804907ea773b8cbeba55fee4594be2bf79b68a43`) + - 2x [redis:6.0.9](https://hub.docker.com/_/redis) server processes + +## Test scenario + +- Load test script runs... + - `1` publisher per channel + - `3` subscribers per channel +- Publisher publishes `3` messages with `1` second interval +- Subscriber call message acknowledge API after `0.01` sec, then polling next message after `0.01` sec sleep. + +For detail, see [../../loadtest.k6.js](../../loadtest.k6.js). + +## Key results + +Keep in mind: everything runs on single machine in this experiment (**load testing tool occupies most of CPU so that it should cause latency**). + +| # | msg/sec | HTTP req/sec | Msg delay [ms]
(incl. 20ms sleep *1) | Publish API TTFB [ms] | Acknowledge API TTFB [ms] | Total CPU Usage
Sys + User | +| -------------- | -------- | ------------ | --------------------------------------- | ------------------------ | ------------------------- | ----------------------------- | +| ` 50` ( `150`) | ` 406.7` | ` 689.1` | `22,  27,  29,   37` | `1.2,  2.9,  3.9,  10.7` | `0.8,  2.2,  3.0,   5.6` | 10% + 15-18% | +| `100` ( `300`) | ` 811.6` | `1374.8` | `22,  26,  29,   64` | `0.9,  2.8,  4.2,  30.3` | `0.7,  2.0,  2.9,   9.4` | 10% + 20% | +| `200` ( `600`) | `1608.1` | `2714.1` | `22,  33,  42,  329` | `2.0,  6.8, 10.3, 179.4` | `1.0,  4.1,  7.1,  16.6` | 15% + 35-50% | +| `300` ( `900`) | `2278.2` | `3838.5` | `23,  41,  62, 1470.3` | `1.6,  7.5, 12.3, 450.7` | `1.1,  5.3,  9.2,  22.9` | 20% + 50-60% | +| `400` (`1200`) | `2636.6` | `4524.1` | `36, 215, 326, 2616.1` | `7.9, 61.2, 90.3, 830.4` | `5.2, 49.5, 70.5, 115.9` | 30% + 60-70% | + +- Values of 4-tuple are `median, 90 percentile, 95 percentile, 99 percentile`. +- `TTFB` = [Time to first byte](https://en.wikipedia.org/wiki/Time_to_first_byte) +- `#` column shows total number of `channels (subscribers)`. +- `msg/sec` column shows average count of received messages per second. +- `HTTP req/sec` column shows HTTP request rate to DSPS server (note that this experiment runs only 1 server process). +- `Total CPU Usage` column shows CPU usage of the machine, 100% means all CPU cores running in top gear. + +`*1`: Note that subscribers sleep `20` ms (`2 * 0.01` sec) for each API cycles in this scenario. `Msg delay` (duration from messsage JSON creation to received) contains the sleep durations. + +## Raw data + +See [result](./result) directory for k6 output files. + +Look [../../experiment-local.sh](../../experiment-local.sh) and [../../loadtest.k6.js](../../loadtest.k6.js) for experiment detail. + +## Steps to reproduction + +1. Increase `nofile` soft ulimit and `kern.ipc.somaxconn` sysctl value (macOS default is to small) +2. Run `CHANNELS=nnn TEST_RAMPUP_SEC=0 TEST_DURATION_SEC=30 ../../experiment-local.sh . output-name` + - Replace `CHANNEL=nnn` to desired channel count + - Replace `output-name` to test pattern name (directory name) + - See [../../loadtest.k6.js](../../loadtest.k6.js) for more available options diff --git a/loadtest/experiments/20201229-on-macbook/dsps.config.yaml b/loadtest/experiments/20201229-on-macbook/dsps.config.yaml new file mode 100644 index 0000000..48158b2 --- /dev/null +++ b/loadtest/experiments/20201229-on-macbook/dsps.config.yaml @@ -0,0 +1,17 @@ +logging: + category: + http: WARN + +storages: + redis1: + redis: + singleNode: "127.0.0.1:${REDIS1_PORT}" + connection: + max: 2048 + min: 2048 + redis2: + redis: + singleNode: "127.0.0.1:${REDIS2_PORT}" + connection: + max: 2048 + min: 2048 diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-100/logs/dsps.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-100/logs/dsps.log.gz new file mode 100644 index 0000000..e088d4a Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-100/logs/dsps.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-100/logs/k6.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-100/logs/k6.log.gz new file mode 100644 index 0000000..5a8beb6 Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-100/logs/k6.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-100/logs/redis1.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-100/logs/redis1.log.gz new file mode 100644 index 0000000..22d65ce Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-100/logs/redis1.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-100/logs/redis2.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-100/logs/redis2.log.gz new file mode 100644 index 0000000..6d4c2d2 Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-100/logs/redis2.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-100/result/data.json.gz b/loadtest/experiments/20201229-on-macbook/result/channels-100/result/data.json.gz new file mode 100644 index 0000000..340edd5 Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-100/result/data.json.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-100/result/summary.json b/loadtest/experiments/20201229-on-macbook/result/channels-100/result/summary.json new file mode 100644 index 0000000..32222ed --- /dev/null +++ b/loadtest/experiments/20201229-on-macbook/result/channels-100/result/summary.json @@ -0,0 +1,224 @@ +{ + "metrics": { + "checks": { + "fails": 0, + "passes": 63573, + "thresholds": { + "rate >= 1.0": false + }, + "value": 0 + }, + "data_received": { + "count": 16532061, + "rate": 500656.54146150406 + }, + "data_sent": { + "count": 9156485, + "rate": 277294.77359441994 + }, + "dsps_fetched_messages": { + "count": 26800, + "rate": 811.6105615124641, + "thresholds": { + "count >= 10800": false + } + }, + "dsps_msg_delay_ms": { + "avg": 21.427835820895524, + "max": 1052, + "med": 22, + "min": 0, + "p(90)": 26, + "p(95)": 29 + }, + "dsps_ttfb_ms_ack": { + "avg": 1.1637135524186788, + "max": 714.848, + "med": 0.685, + "min": 0.195, + "p(90)": 1.961800000000001, + "p(95)": 2.9386999999999937 + }, + "dsps_ttfb_ms_publish": { + "avg": 5.262406555555541, + "max": 736.772, + "med": 0.882, + "min": 0.254, + "p(90)": 2.8497000000000026, + "p(95)": 4.226 + }, + "group_duration": { + "avg": 382.9728388429216, + "max": 3020.788957, + "med": 21.874523, + "min": 0.000283, + "p(90)": 982.9938394, + "p(95)": 1002.9519002000001 + }, + "http_req_blocked": { + "avg": 0.2462839772681683, + "max": 52.504, + "med": 0.002, + "min": 0.001, + "p(90)": 0.004, + "p(95)": 0.007 + }, + "http_req_connecting": { + "avg": 0.24100724701528697, + "max": 43.27, + "med": 0, + "min": 0, + "p(90)": 0, + "p(95)": 0 + }, + "http_req_duration": { + "avg": 204.68227979205966, + "max": 3007.625, + "med": 1.085, + "min": 0.216, + "p(90)": 959.422, + "p(95)": 961.253 + }, + "http_req_receiving": { + "avg": 0.04582640204414061, + "max": 31.406, + "med": 0.025, + "min": 0.008, + "p(90)": 0.052, + "p(95)": 0.081 + }, + "http_req_sending": { + "avg": 0.05237973038460094, + "max": 19.573, + "med": 0.011, + "min": 0.005, + "p(90)": 0.035, + "p(95)": 0.088 + }, + "http_req_tls_handshaking": { + "avg": 0, + "max": 0, + "med": 0, + "min": 0, + "p(90)": 0, + "p(95)": 0 + }, + "http_req_waiting": { + "avg": 204.58407365963194, + "max": 3007.581, + "med": 1.025, + "min": 0.195, + "p(90)": 959.3563, + "p(95)": 961.1981499999999 + }, + "http_reqs": { + "count": 45398, + "rate": 1374.831950430703 + }, + "iteration_duration": { + "avg": 599.2223159736039, + "max": 3020.823341, + "med": 978.705485, + "min": 0.229524, + "p(90)": 1003.7370645, + "p(95)": 1007.05829225 + }, + "iterations": { + "count": 21175, + "rate": 641.2631955233742 + }, + "vus": { + "max": 400, + "min": 6, + "value": 6 + }, + "vus_max": { + "max": 400, + "min": 400, + "value": 400 + } + }, + "root_group": { + "name": "", + "path": "", + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": { + "publisher": { + "name": "publisher", + "path": "::publisher", + "id": "42834ed9b0fab346fb485eef504a199e", + "groups": {}, + "checks": { + "is status 200": { + "name": "is status 200", + "path": "::publisher::is status 200", + "id": "033b4844056b121c9936aedc4ab12017", + "passes": 9000, + "fails": 0 + } + } + }, + "setup": { + "name": "setup", + "path": "::setup", + "id": "5c0f8025f7e0b6654089e5b00e950f1a", + "groups": {}, + "checks": {} + }, + "subscriber": { + "name": "subscriber", + "path": "::subscriber", + "id": "8cd2fe283a36cc055d74315ba7db7039", + "groups": { + "ack": { + "name": "ack", + "path": "::subscriber::ack", + "id": "896b291f5e9f4a0b4a9d63c5bd0aa610", + "groups": {}, + "checks": { + "is status 204": { + "name": "is status 204", + "path": "::subscriber::ack::is status 204", + "id": "1675e96687d722c0ea932d5d2fbfd93f", + "passes": 17923, + "fails": 0 + } + } + }, + "fetch": { + "name": "fetch", + "path": "::subscriber::fetch", + "id": "219dd6d5879771ae4590fb2c3826ad84", + "groups": {}, + "checks": { + "has messages array": { + "name": "has messages array", + "path": "::subscriber::fetch::has messages array", + "id": "8bcfe7144bfdfba71334cca76e6453c6", + "passes": 18175, + "fails": 0 + }, + "is status 200": { + "name": "is status 200", + "path": "::subscriber::fetch::is status 200", + "id": "bee4378527eb142a3168d665c6ac821a", + "passes": 18175, + "fails": 0 + } + } + } + }, + "checks": { + "is status 200": { + "name": "is status 200", + "path": "::subscriber::is status 200", + "id": "96fdc221e3cd9ed60329154e5bb89f28", + "passes": 300, + "fails": 0 + } + } + } + }, + "checks": {} + } +} diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-200/logs/dsps.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-200/logs/dsps.log.gz new file mode 100644 index 0000000..e718653 Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-200/logs/dsps.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-200/logs/k6.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-200/logs/k6.log.gz new file mode 100644 index 0000000..9a2e08a Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-200/logs/k6.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-200/logs/redis1.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-200/logs/redis1.log.gz new file mode 100644 index 0000000..b29ac87 Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-200/logs/redis1.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-200/logs/redis2.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-200/logs/redis2.log.gz new file mode 100644 index 0000000..8543608 Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-200/logs/redis2.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-200/result/data.json.gz b/loadtest/experiments/20201229-on-macbook/result/channels-200/result/data.json.gz new file mode 100644 index 0000000..962c674 Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-200/result/data.json.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-200/result/summary.json b/loadtest/experiments/20201229-on-macbook/result/channels-200/result/summary.json new file mode 100644 index 0000000..5c0b5a7 --- /dev/null +++ b/loadtest/experiments/20201229-on-macbook/result/channels-200/result/summary.json @@ -0,0 +1,224 @@ +{ + "metrics": { + "checks": { + "fails": 0, + "passes": 125487, + "thresholds": { + "rate >= 1.0": false + }, + "value": 0 + }, + "data_received": { + "count": 32677893, + "rate": 989706.1201036749 + }, + "data_sent": { + "count": 18125801, + "rate": 548971.0178523846 + }, + "dsps_fetched_messages": { + "count": 53096, + "rate": 1608.1035626447742, + "thresholds": { + "count >= 21600": false + } + }, + "dsps_msg_delay_ms": { + "avg": 26.38835317161368, + "max": 1228, + "med": 22, + "min": 0, + "p(90)": 33, + "p(95)": 42 + }, + "dsps_ttfb_ms_ack": { + "avg": 2.0270198414944773, + "max": 70.381, + "med": 0.992, + "min": 0.188, + "p(90)": 4.14, + "p(95)": 7.0865499999999955 + }, + "dsps_ttfb_ms_publish": { + "avg": 8.488173544438867, + "max": 877.705, + "med": 1.98, + "min": 0.284, + "p(90)": 6.754, + "p(95)": 10.3185 + }, + "group_duration": { + "avg": 389.2914580048516, + "max": 3021.040137, + "med": 22.5484945, + "min": 0.000606, + "p(90)": 990.2148010000001, + "p(95)": 1007.7678539999999 + }, + "http_req_blocked": { + "avg": 0.37921010109945, + "max": 166.643, + "med": 0.002, + "min": 0.001, + "p(90)": 0.004, + "p(95)": 0.007 + }, + "http_req_connecting": { + "avg": 0.3732484433235874, + "max": 155.589, + "med": 0, + "min": 0, + "p(90)": 0, + "p(95)": 0 + }, + "http_req_duration": { + "avg": 209.19263639609758, + "max": 3010.212, + "med": 1.995, + "min": 0.215, + "p(90)": 960.69, + "p(95)": 966.1507 + }, + "http_req_receiving": { + "avg": 0.06560775102104156, + "max": 75.403, + "med": 0.023, + "min": 0.008, + "p(90)": 0.052, + "p(95)": 0.095 + }, + "http_req_sending": { + "avg": 0.09644968420114108, + "max": 43.129, + "med": 0.011, + "min": 0.004, + "p(90)": 0.056, + "p(95)": 0.14734999999999127 + }, + "http_req_tls_handshaking": { + "avg": 0, + "max": 0, + "med": 0, + "min": 0, + "p(90)": 0, + "p(95)": 0 + }, + "http_req_waiting": { + "avg": 209.0305789608755, + "max": 3010.117, + "med": 1.9, + "min": 0.188, + "p(90)": 960.61, + "p(95)": 966.06635 + }, + "http_reqs": { + "count": 89614, + "rate": 2714.1139193696094 + }, + "iteration_duration": { + "avg": 610.1495090745699, + "max": 3021.062741, + "med": 975.550473, + "min": 0.159508, + "p(90)": 1009.905706, + "p(95)": 1020.092668 + }, + "iterations": { + "count": 41810, + "rate": 1266.2876667579103 + }, + "vus": { + "max": 800, + "min": 0, + "value": 0 + }, + "vus_max": { + "max": 800, + "min": 800, + "value": 800 + } + }, + "root_group": { + "name": "", + "path": "", + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": { + "publisher": { + "name": "publisher", + "path": "::publisher", + "id": "42834ed9b0fab346fb485eef504a199e", + "groups": {}, + "checks": { + "is status 200": { + "name": "is status 200", + "path": "::publisher::is status 200", + "id": "033b4844056b121c9936aedc4ab12017", + "passes": 17811, + "fails": 0 + } + } + }, + "setup": { + "name": "setup", + "path": "::setup", + "id": "5c0f8025f7e0b6654089e5b00e950f1a", + "groups": {}, + "checks": {} + }, + "subscriber": { + "name": "subscriber", + "path": "::subscriber", + "id": "8cd2fe283a36cc055d74315ba7db7039", + "groups": { + "ack": { + "name": "ack", + "path": "::subscriber::ack", + "id": "896b291f5e9f4a0b4a9d63c5bd0aa610", + "groups": {}, + "checks": { + "is status 204": { + "name": "is status 204", + "path": "::subscriber::ack::is status 204", + "id": "1675e96687d722c0ea932d5d2fbfd93f", + "passes": 35330, + "fails": 0 + } + } + }, + "fetch": { + "name": "fetch", + "path": "::subscriber::fetch", + "id": "219dd6d5879771ae4590fb2c3826ad84", + "groups": {}, + "checks": { + "has messages array": { + "name": "has messages array", + "path": "::subscriber::fetch::has messages array", + "id": "8bcfe7144bfdfba71334cca76e6453c6", + "passes": 35873, + "fails": 0 + }, + "is status 200": { + "name": "is status 200", + "path": "::subscriber::fetch::is status 200", + "id": "bee4378527eb142a3168d665c6ac821a", + "passes": 35873, + "fails": 0 + } + } + } + }, + "checks": { + "is status 200": { + "name": "is status 200", + "path": "::subscriber::is status 200", + "id": "96fdc221e3cd9ed60329154e5bb89f28", + "passes": 600, + "fails": 0 + } + } + } + }, + "checks": {} + } +} diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-300/logs/dsps.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-300/logs/dsps.log.gz new file mode 100644 index 0000000..0bc30e7 Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-300/logs/dsps.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-300/logs/k6.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-300/logs/k6.log.gz new file mode 100644 index 0000000..ddaf86a Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-300/logs/k6.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-300/logs/redis1.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-300/logs/redis1.log.gz new file mode 100644 index 0000000..c76173d Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-300/logs/redis1.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-300/logs/redis2.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-300/logs/redis2.log.gz new file mode 100644 index 0000000..1b05bdc Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-300/logs/redis2.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-300/result/data.json.gz b/loadtest/experiments/20201229-on-macbook/result/channels-300/result/data.json.gz new file mode 100644 index 0000000..1600050 Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-300/result/data.json.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-300/result/summary.json b/loadtest/experiments/20201229-on-macbook/result/channels-300/result/summary.json new file mode 100644 index 0000000..505ec03 --- /dev/null +++ b/loadtest/experiments/20201229-on-macbook/result/channels-300/result/summary.json @@ -0,0 +1,224 @@ +{ + "metrics": { + "checks": { + "fails": 0, + "passes": 177470, + "thresholds": { + "rate >= 1.0": false + }, + "value": 0 + }, + "data_received": { + "count": 46270831, + "rate": 1400545.4007832198 + }, + "data_sent": { + "count": 25667488, + "rate": 776914.5591540919 + }, + "dsps_fetched_messages": { + "count": 75267, + "rate": 2278.2139071751403, + "thresholds": { + "count >= 32400": false + } + }, + "dsps_msg_delay_ms": { + "avg": 59.32956009937954, + "max": 6172, + "med": 23, + "min": 0, + "p(90)": 41, + "p(95)": 62 + }, + "dsps_ttfb_ms_ack": { + "avg": 2.524096623794175, + "max": 114.377, + "med": 1.075, + "min": 0.187, + "p(90)": 5.319199999999997, + "p(95)": 9.230049999999997 + }, + "dsps_ttfb_ms_publish": { + "avg": 23.279035686274398, + "max": 5773.416, + "med": 1.599, + "min": 0.272, + "p(90)": 7.466100000000003, + "p(95)": 12.309099999999997 + }, + "group_duration": { + "avg": 406.69089204222087, + "max": 7047.806164, + "med": 23.290035, + "min": 0.00026, + "p(90)": 990.4311532, + "p(95)": 1008.7664149999999 + }, + "http_req_blocked": { + "avg": 0.7481098608179423, + "max": 110.081, + "med": 0.003, + "min": 0.001, + "p(90)": 0.005, + "p(95)": 0.01 + }, + "http_req_connecting": { + "avg": 0.7397475140953348, + "max": 107.905, + "med": 0, + "min": 0, + "p(90)": 0, + "p(95)": 0 + }, + "http_req_duration": { + "avg": 224.87377527106796, + "max": 5775.351, + "med": 2.07, + "min": 0.209, + "p(90)": 959.9616, + "p(95)": 964.2076 + }, + "http_req_receiving": { + "avg": 0.0891607223120533, + "max": 106.81, + "med": 0.027, + "min": 0.008, + "p(90)": 0.051, + "p(95)": 0.093 + }, + "http_req_sending": { + "avg": 0.15000894215966185, + "max": 77.247, + "med": 0.013, + "min": 0.004, + "p(90)": 0.068, + "p(95)": 0.261 + }, + "http_req_tls_handshaking": { + "avg": 0, + "max": 0, + "med": 0, + "min": 0, + "p(90)": 0, + "p(95)": 0 + }, + "http_req_waiting": { + "avg": 224.63460560659286, + "max": 5773.416, + "med": 1.95, + "min": 0.187, + "p(90)": 959.8792000000001, + "p(95)": 964.0862999999999 + }, + "http_reqs": { + "count": 126815, + "rate": 3838.490927477054 + }, + "iteration_duration": { + "avg": 647.7388498626557, + "max": 7083.069628, + "med": 975.034647, + "min": 0.074195, + "p(90)": 1011.2709435, + "p(95)": 1040.8787275 + }, + "iterations": { + "count": 59155, + "rate": 1790.5289659338812 + }, + "vus": { + "max": 1200, + "min": 6, + "value": 6 + }, + "vus_max": { + "max": 1200, + "min": 1200, + "value": 1200 + } + }, + "root_group": { + "name": "", + "path": "", + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": { + "publisher": { + "name": "publisher", + "path": "::publisher", + "id": "42834ed9b0fab346fb485eef504a199e", + "groups": {}, + "checks": { + "is status 200": { + "name": "is status 200", + "path": "::publisher::is status 200", + "id": "033b4844056b121c9936aedc4ab12017", + "passes": 25500, + "fails": 0 + } + } + }, + "setup": { + "name": "setup", + "path": "::setup", + "id": "5c0f8025f7e0b6654089e5b00e950f1a", + "groups": {}, + "checks": {} + }, + "subscriber": { + "name": "subscriber", + "path": "::subscriber", + "id": "8cd2fe283a36cc055d74315ba7db7039", + "groups": { + "ack": { + "name": "ack", + "path": "::subscriber::ack", + "id": "896b291f5e9f4a0b4a9d63c5bd0aa610", + "groups": {}, + "checks": { + "is status 204": { + "name": "is status 204", + "path": "::subscriber::ack::is status 204", + "id": "1675e96687d722c0ea932d5d2fbfd93f", + "passes": 49760, + "fails": 0 + } + } + }, + "fetch": { + "name": "fetch", + "path": "::subscriber::fetch", + "id": "219dd6d5879771ae4590fb2c3826ad84", + "groups": {}, + "checks": { + "has messages array": { + "name": "has messages array", + "path": "::subscriber::fetch::has messages array", + "id": "8bcfe7144bfdfba71334cca76e6453c6", + "passes": 50655, + "fails": 0 + }, + "is status 200": { + "name": "is status 200", + "path": "::subscriber::fetch::is status 200", + "id": "bee4378527eb142a3168d665c6ac821a", + "passes": 50655, + "fails": 0 + } + } + } + }, + "checks": { + "is status 200": { + "name": "is status 200", + "path": "::subscriber::is status 200", + "id": "96fdc221e3cd9ed60329154e5bb89f28", + "passes": 900, + "fails": 0 + } + } + } + }, + "checks": {} + } +} diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-400/logs/dsps.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-400/logs/dsps.log.gz new file mode 100644 index 0000000..8110b67 Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-400/logs/dsps.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-400/logs/k6.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-400/logs/k6.log.gz new file mode 100644 index 0000000..65343bb Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-400/logs/k6.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-400/logs/redis1.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-400/logs/redis1.log.gz new file mode 100644 index 0000000..c41643e Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-400/logs/redis1.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-400/logs/redis2.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-400/logs/redis2.log.gz new file mode 100644 index 0000000..301af4b Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-400/logs/redis2.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-400/result/data.json.gz b/loadtest/experiments/20201229-on-macbook/result/channels-400/result/data.json.gz new file mode 100644 index 0000000..90a4bc2 Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-400/result/data.json.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-400/result/summary.json b/loadtest/experiments/20201229-on-macbook/result/channels-400/result/summary.json new file mode 100644 index 0000000..73ca4ac --- /dev/null +++ b/loadtest/experiments/20201229-on-macbook/result/channels-400/result/summary.json @@ -0,0 +1,224 @@ +{ + "metrics": { + "checks": { + "fails": 0, + "passes": 209445, + "thresholds": { + "rate >= 1.0": false + }, + "value": 0 + }, + "data_received": { + "count": 54519978, + "rate": 1648488.9243541933 + }, + "data_sent": { + "count": 30326543, + "rate": 916966.0752513728 + }, + "dsps_fetched_messages": { + "count": 87198, + "rate": 2636.5553050266626, + "thresholds": { + "count >= 43200": false + } + }, + "dsps_msg_delay_ms": { + "avg": 131.50333723250534, + "max": 6328, + "med": 36, + "min": 0, + "p(90)": 215, + "p(95)": 326 + }, + "dsps_ttfb_ms_ack": { + "avg": 16.62900326103567, + "max": 180.732, + "med": 5.199, + "min": 0.188, + "p(90)": 49.53140000000001, + "p(95)": 70.51739999999995 + }, + "dsps_ttfb_ms_publish": { + "avg": 63.543295283272414, + "max": 5847.173, + "med": 7.86, + "min": 0.306, + "p(90)": 61.267900000000004, + "p(95)": 90.32489999999999 + }, + "group_duration": { + "avg": 437.76350004147906, + "max": 7103.337582, + "med": 78.11372750000001, + "min": 0.000274, + "p(90)": 1020.9506247, + "p(95)": 1128.4400069 + }, + "http_req_blocked": { + "avg": 1.4159386056933243, + "max": 190.06, + "med": 0.003, + "min": 0.001, + "p(90)": 0.006, + "p(95)": 0.011 + }, + "http_req_connecting": { + "avg": 1.4059587563409375, + "max": 168.663, + "med": 0, + "min": 0, + "p(90)": 0, + "p(95)": 0 + }, + "http_req_duration": { + "avg": 246.63950601177567, + "max": 5847.267, + "med": 14.694, + "min": 0.22, + "p(90)": 959.3646000000001, + "p(95)": 977.8367999999999 + }, + "http_req_receiving": { + "avg": 0.22845847897718707, + "max": 126.862, + "med": 0.029, + "min": 0.007, + "p(90)": 0.059, + "p(95)": 0.149 + }, + "http_req_sending": { + "avg": 0.8592670311383076, + "max": 174.212, + "med": 0.015, + "min": 0.004, + "p(90)": 0.267, + "p(95)": 2.677899999999994 + }, + "http_req_tls_handshaking": { + "avg": 0, + "max": 0, + "med": 0, + "min": 0, + "p(90)": 0, + "p(95)": 0 + }, + "http_req_waiting": { + "avg": 245.55178050166296, + "max": 5847.173, + "med": 13.797, + "min": 0.188, + "p(90)": 959.1416, + "p(95)": 977.0154999999999 + }, + "http_reqs": { + "count": 149623, + "rate": 4524.063790499832 + }, + "iteration_duration": { + "avg": 721.0031081017922, + "max": 7103.37327, + "med": 950.465135, + "min": 0.07901, + "p(90)": 1151.317182, + "p(95)": 1307.4926759999998 + }, + "iterations": { + "count": 69730, + "rate": 2108.3855297083555 + }, + "vus": { + "max": 1600, + "min": 26, + "value": 26 + }, + "vus_max": { + "max": 1600, + "min": 1600, + "value": 1600 + } + }, + "root_group": { + "name": "", + "path": "", + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": { + "publisher": { + "name": "publisher", + "path": "::publisher", + "id": "42834ed9b0fab346fb485eef504a199e", + "groups": {}, + "checks": { + "is status 200": { + "name": "is status 200", + "path": "::publisher::is status 200", + "id": "033b4844056b121c9936aedc4ab12017", + "passes": 29724, + "fails": 0 + } + } + }, + "setup": { + "name": "setup", + "path": "::setup", + "id": "5c0f8025f7e0b6654089e5b00e950f1a", + "groups": {}, + "checks": {} + }, + "subscriber": { + "name": "subscriber", + "path": "::subscriber", + "id": "8cd2fe283a36cc055d74315ba7db7039", + "groups": { + "ack": { + "name": "ack", + "path": "::subscriber::ack", + "id": "896b291f5e9f4a0b4a9d63c5bd0aa610", + "groups": {}, + "checks": { + "is status 204": { + "name": "is status 204", + "path": "::subscriber::ack::is status 204", + "id": "1675e96687d722c0ea932d5d2fbfd93f", + "passes": 58877, + "fails": 0 + } + } + }, + "fetch": { + "name": "fetch", + "path": "::subscriber::fetch", + "id": "219dd6d5879771ae4590fb2c3826ad84", + "groups": {}, + "checks": { + "has messages array": { + "name": "has messages array", + "path": "::subscriber::fetch::has messages array", + "id": "8bcfe7144bfdfba71334cca76e6453c6", + "passes": 59822, + "fails": 0 + }, + "is status 200": { + "name": "is status 200", + "path": "::subscriber::fetch::is status 200", + "id": "bee4378527eb142a3168d665c6ac821a", + "passes": 59822, + "fails": 0 + } + } + } + }, + "checks": { + "is status 200": { + "name": "is status 200", + "path": "::subscriber::is status 200", + "id": "96fdc221e3cd9ed60329154e5bb89f28", + "passes": 1200, + "fails": 0 + } + } + } + }, + "checks": {} + } +} diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-50/logs/dsps.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-50/logs/dsps.log.gz new file mode 100644 index 0000000..ca8ea8a Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-50/logs/dsps.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-50/logs/k6.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-50/logs/k6.log.gz new file mode 100644 index 0000000..026c085 Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-50/logs/k6.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-50/logs/redis1.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-50/logs/redis1.log.gz new file mode 100644 index 0000000..dcb280b Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-50/logs/redis1.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-50/logs/redis2.log.gz b/loadtest/experiments/20201229-on-macbook/result/channels-50/logs/redis2.log.gz new file mode 100644 index 0000000..b085861 Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-50/logs/redis2.log.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-50/result/data.json.gz b/loadtest/experiments/20201229-on-macbook/result/channels-50/result/data.json.gz new file mode 100644 index 0000000..37ec2cc Binary files /dev/null and b/loadtest/experiments/20201229-on-macbook/result/channels-50/result/data.json.gz differ diff --git a/loadtest/experiments/20201229-on-macbook/result/channels-50/result/summary.json b/loadtest/experiments/20201229-on-macbook/result/channels-50/result/summary.json new file mode 100644 index 0000000..133164c --- /dev/null +++ b/loadtest/experiments/20201229-on-macbook/result/channels-50/result/summary.json @@ -0,0 +1,224 @@ +{ + "metrics": { + "checks": { + "fails": 0, + "passes": 31815, + "thresholds": { + "rate >= 1.0": false + }, + "value": 0 + }, + "data_received": { + "count": 8263517, + "rate": 250731.13455005726 + }, + "data_sent": { + "count": 4570917, + "rate": 138690.48800215987 + }, + "dsps_fetched_messages": { + "count": 13403, + "rate": 406.6730178414854, + "thresholds": { + "count >= 5400": false + } + }, + "dsps_msg_delay_ms": { + "avg": 21.508617473699918, + "max": 1030, + "med": 22, + "min": 0, + "p(90)": 27, + "p(95)": 29 + }, + "dsps_ttfb_ms_ack": { + "avg": 1.1608371859296513, + "max": 12.231, + "med": 0.833, + "min": 0.194, + "p(90)": 2.2272000000000007, + "p(95)": 2.965299999999992 + }, + "dsps_ttfb_ms_publish": { + "avg": 4.095637555555554, + "max": 717.688, + "med": 1.152, + "min": 0.265, + "p(90)": 2.8922999999999996, + "p(95)": 3.8911500000000006 + }, + "group_duration": { + "avg": 386.6544554704826, + "max": 3017.305062, + "med": 22.01059, + "min": 0.000292, + "p(90)": 984.0920726, + "p(95)": 1003.8870944 + }, + "http_req_blocked": { + "avg": 0.21584222809341708, + "max": 48.728, + "med": 0.003, + "min": 0.001, + "p(90)": 0.004, + "p(95)": 0.007 + }, + "http_req_connecting": { + "avg": 0.2108453985028623, + "max": 48.7, + "med": 0, + "min": 0, + "p(90)": 0, + "p(95)": 0 + }, + "http_req_duration": { + "avg": 206.91370981946235, + "max": 3005.639, + "med": 1.343, + "min": 0.222, + "p(90)": 959.6564, + "p(95)": 961.9601 + }, + "http_req_receiving": { + "avg": 0.03806081021576389, + "max": 24.412, + "med": 0.025, + "min": 0.01, + "p(90)": 0.053, + "p(95)": 0.078 + }, + "http_req_sending": { + "avg": 0.03413535887274189, + "max": 20.404, + "med": 0.012, + "min": 0.005, + "p(90)": 0.037, + "p(95)": 0.09 + }, + "http_req_tls_handshaking": { + "avg": 0, + "max": 0, + "med": 0, + "min": 0, + "p(90)": 0, + "p(95)": 0 + }, + "http_req_waiting": { + "avg": 206.84151365037357, + "max": 3005.472, + "med": 1.2805, + "min": 0.194, + "p(90)": 959.5852, + "p(95)": 961.89265 + }, + "http_reqs": { + "count": 22710, + "rate": 689.0654506588176 + }, + "iteration_duration": { + "avg": 603.0194496801789, + "max": 3017.34404, + "med": 978.654395, + "min": 0.103627, + "p(90)": 1004.9525134999999, + "p(95)": 1007.383953 + }, + "iterations": { + "count": 10605, + "rate": 321.77627055203703 + }, + "vus": { + "max": 200, + "min": 150, + "value": 150 + }, + "vus_max": { + "max": 200, + "min": 200, + "value": 200 + } + }, + "root_group": { + "name": "", + "path": "", + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": { + "publisher": { + "name": "publisher", + "path": "::publisher", + "id": "42834ed9b0fab346fb485eef504a199e", + "groups": {}, + "checks": { + "is status 200": { + "name": "is status 200", + "path": "::publisher::is status 200", + "id": "033b4844056b121c9936aedc4ab12017", + "passes": 4500, + "fails": 0 + } + } + }, + "setup": { + "name": "setup", + "path": "::setup", + "id": "5c0f8025f7e0b6654089e5b00e950f1a", + "groups": {}, + "checks": {} + }, + "subscriber": { + "name": "subscriber", + "path": "::subscriber", + "id": "8cd2fe283a36cc055d74315ba7db7039", + "groups": { + "ack": { + "name": "ack", + "path": "::subscriber::ack", + "id": "896b291f5e9f4a0b4a9d63c5bd0aa610", + "groups": {}, + "checks": { + "is status 204": { + "name": "is status 204", + "path": "::subscriber::ack::is status 204", + "id": "1675e96687d722c0ea932d5d2fbfd93f", + "passes": 8955, + "fails": 0 + } + } + }, + "fetch": { + "name": "fetch", + "path": "::subscriber::fetch", + "id": "219dd6d5879771ae4590fb2c3826ad84", + "groups": {}, + "checks": { + "has messages array": { + "name": "has messages array", + "path": "::subscriber::fetch::has messages array", + "id": "8bcfe7144bfdfba71334cca76e6453c6", + "passes": 9105, + "fails": 0 + }, + "is status 200": { + "name": "is status 200", + "path": "::subscriber::fetch::is status 200", + "id": "bee4378527eb142a3168d665c6ac821a", + "passes": 9105, + "fails": 0 + } + } + } + }, + "checks": { + "is status 200": { + "name": "is status 200", + "path": "::subscriber::is status 200", + "id": "96fdc221e3cd9ed60329154e5bb89f28", + "passes": 150, + "fails": 0 + } + } + } + }, + "checks": {} + } +} diff --git a/loadtest/loadtest.k6.js b/loadtest/loadtest.k6.js new file mode 100644 index 0000000..db3a2b3 --- /dev/null +++ b/loadtest/loadtest.k6.js @@ -0,0 +1,173 @@ +import http from 'k6/http'; +import { group, sleep, check } from 'k6'; +import { Counter, Trend } from 'k6/metrics'; + +// +// To run in local, use ./experiment-local.sh +// See experiment logbook (e.g. ./experiments/20201229-on-macbook/README.md) for steps to reproduce. +// + + +// +// --- Compute client role & parameter --- +// + +const settings = { + BASE_URL: __ENV.BASE_URL || "http://localhost:3000", + + TEST_RAMPUP_SEC: __ENV.TEST_RAMPUP_SEC || 1, + TEST_DURATION_SEC: __ENV.TEST_DURATION_SEC || 3, + TEST_RAMPDOWN_SEC: __ENV.TEST_RAMPUP_SEC || 0, + + CHANNELS: +(__ENV.CHANNELS) || 50, + PUBLISHER_PER_CHANEL: +(__ENV.PUBLISHER_PER_CHANEL) || 1, + SUBSCRIBER_PER_PUBLISHER: +(__ENV.SUBSCRIBER_PER_PUBLISHER) || 3, + + PUBLISH_MESSAGES_PER_ITERATION: +(__ENV.PUBLISH_MESSAGES_PER_ITERATION) || 3, + + PUBLISH_INTERVAL_SEC: +(__ENV.PUBLISH_INTERVAL_SEC) || 1, + SUBSCRIBE_ACK_WAIT_SEC: +(__ENV.SUBSCRIBE_INERVAL_SEC) || 0.01, + SUBSCRIBE_INERVAL_SEC: +(__ENV.SUBSCRIBE_INERVAL_SEC) || 0.01, +} +const clientsPerChannel = (1 + settings.SUBSCRIBER_PER_PUBLISHER) * settings.PUBLISHER_PER_CHANEL; +const targetVU = settings.CHANNELS * clientsPerChannel; +if (__VU === 1) console.log(`clientsPerChannel: ${clientsPerChannel}, targetVU: ${targetVU}, settings: ${JSON.stringify(settings, null, 2)}`); + +const channelNumber = Math.floor((__VU - 1) / clientsPerChannel); +const isPublisher = (((__VU - 1) % clientsPerChannel) < settings.PUBLISHER_PER_CHANEL); + +// +// --- Custom metrics --- +// +const fetchedMessagesCounter = new Counter('dsps_fetched_messages'); +const ttfbTrends = { // TTFB = time to first byte + publish: new Trend('dsps_ttfb_ms_publish'), + ack: new Trend('dsps_ttfb_ms_ack'), +}; +const messageDelayTrends = new Trend('dsps_msg_delay_ms'); + +// +// --- k6 config --- +// +export const options = { + stages: [ + { duration: `${settings.TEST_RAMPUP_SEC}s`, target: targetVU }, + { duration: `${settings.TEST_DURATION_SEC}s`, target: targetVU }, + { duration: `${settings.TEST_RAMPDOWN_SEC}s`, target: 0 }, + ], + thresholds: { // https://k6.io/docs/using-k6/thresholds + checks: ['rate >= 1.0'], + dsps_fetched_messages: [`count >= ${0.9 * (targetVU * settings.TEST_DURATION_SEC) / settings.PUBLISH_INTERVAL_SEC}`], + }, +}; + +// +// --- Load test implementation --- +// +export function setup() { + const chars = "abcdefghijklmnopqrstuvwxyz".split(""); + let randomID = ""; + for (let i = 0; i < 16; i++) { + randomID += chars[Math.floor(chars.length * Math.random())]; + } + + const data = { + randomID: randomID, + }; + console.log(`data: ${JSON.stringify(data)}`); + return data; +} + +export default function (data) { + const { randomID } = data; + const channelID = `${randomID}-${channelNumber}`; + const subscriberID = `sbsc-${__VU}`; + + if (isPublisher) { + group("publisher", () => { + publisherScenario(channelID); + }); + } else { + group("subscriber", () => { + subscriberScenario(channelID, subscriberID); + }); + } +} + +function publisherScenario(channelID) { + for (let i = 0; i < settings.PUBLISH_MESSAGES_PER_ITERATION; i++) { + publishMessage(channelID, `${__VU}-${__ITER}-${i}`); + } + sleep(settings.PUBLISH_INTERVAL_SEC); +} + +function subscriberScenario(channelID, subscriberID) { + if (__ITER === 0) { + createSubscription(channelID, subscriberID); + } + + let ackHandle; + group("fetch", () => { + ackHandle = fetchMessages(channelID, subscriberID); + }); + group("ack", () => { + if (ackHandle) { + sleep(settings.SUBSCRIBE_ACK_WAIT_SEC); + consumeAckHandle(channelID, subscriberID, ackHandle); + } + }); + + sleep(settings.SUBSCRIBE_INERVAL_SEC); +} + +function publishMessage(channelID, messageID) { + const message = createMessage(); + const res = http.put(`${settings.BASE_URL}/channel/${channelID}/message/${messageID}`, message, { tags: { endpoint: "publish" } }); + check(res, { + "is status 200": (r) => r.status === 200, + }); + ttfbTrends.publish.add(res.timings.waiting); +} + +function createSubscription(channelID, subscriberID) { + const res = http.put(`${settings.BASE_URL}/channel/${channelID}/subscription/polling/${subscriberID}`, undefined, { tags: { endpoint: "createSubscription" } }); + check(res, { + "is status 200": (r) => r.status === 200, + }); +} + +function fetchMessages(channelID, subscriberID) { + const res = http.get(`${settings.BASE_URL}/channel/${channelID}/subscription/polling/${subscriberID}?timeout=3s`, undefined, { tags: { endpoint: "fetch" } }); + const receivedAt = new Date(); + + const body = res.json(); + check(res, { + "is status 200": (r) => r.status === 200, + "has messages array": (r) => (typeof (body.messages) === "object" && typeof (body.messages.length) === "number"), + }); + if (body.messages) { + fetchedMessagesCounter.add(body.messages.length); + for (let i = 0; i < body.messages.length; i++) { + readMessage(receivedAt, body.messages[i]); + } + } + return body.ackHandle; +} + +function consumeAckHandle(channelID, subscriberID, ackHandle) { + const res = http.del(`${settings.BASE_URL}/channel/${channelID}/subscription/polling/${subscriberID}/message?ackHandle=${ackHandle}`, undefined, { tags: { endpoint: "ack" } }); + check(res, { + "is status 204": (r) => r.status === 204, + }); + ttfbTrends.ack.add(res.timings.waiting); +} + +function createMessage() { + return JSON.stringify({ + at: (new Date()).getTime(), + }); +} + +function readMessage(receivedAt, msg) { + messageDelayTrends.add(receivedAt.getTime() - msg.content.at); +} diff --git a/loadtest/log/20yymmdd-redis-jsclient/.keep b/loadtest/log/20yymmdd-redis-jsclient/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/loadtest/package.json b/loadtest/package.json new file mode 100644 index 0000000..be46d36 --- /dev/null +++ b/loadtest/package.json @@ -0,0 +1,12 @@ +{ + "name": "@dsps/load-test", + "version": "0.0.0", + "private": true, + "license": "MIT", + "description": "DSPS load testing code", + "main": "loadtest.k6.js", + "devDependencies": { + "@types/k6": "^0.28.3", + "typescript": "^4.1.2" + } +} diff --git a/loadtest/yarn.lock b/loadtest/yarn.lock new file mode 100644 index 0000000..a12b04e --- /dev/null +++ b/loadtest/yarn.lock @@ -0,0 +1,13 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/k6@^0.28.3": + version "0.28.3" + resolved "https://registry.yarnpkg.com/@types/k6/-/k6-0.28.3.tgz#69ab758f450e558722dff553ab3eb5542892a440" + integrity sha512-qUOneA7Zn8erpBxhFkkshr6lRNKYIBGo0MjNQWaepGcbegfQaQy0fVIlHYjTJhlqxnmwDZPNkKbIrTpL0VHb6A== + +typescript@^4.1.2: + version "4.1.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" + integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== diff --git a/server/storage/redis/internal/pubsub/dispatcher_test.go b/server/storage/redis/internal/pubsub/dispatcher_test.go index 925c9ff..e2849d2 100644 --- a/server/storage/redis/internal/pubsub/dispatcher_test.go +++ b/server/storage/redis/internal/pubsub/dispatcher_test.go @@ -49,6 +49,7 @@ func TestDispatcherAwaitAfterShutdown(t *testing.T) { pubsub := newRedisRawPubSubStub(t).EnqueueDefaultSubscribeMessage().EnqueuePingResultForever(nil) dispatcher, pubsubActivated := newDispatcher(t, pubsub) <-pubsubActivated + time.Sleep(10 * time.Millisecond) // Await reconcile completion dispatcher.Shutdown(ctx) await, cancel := dispatcher.Await(ctx, "ch-1") diff --git a/server/storage/redis/internal/pubsub/redis_test.go b/server/storage/redis/internal/pubsub/redis_test.go index 4200e6d..3579252 100644 --- a/server/storage/redis/internal/pubsub/redis_test.go +++ b/server/storage/redis/internal/pubsub/redis_test.go @@ -20,6 +20,7 @@ type redisRawPubSubStub struct { closeResult error pingResultQueue chan error + fixedPingResult error messageQueue chan interface{} channelInit sync.Once @@ -47,15 +48,8 @@ func (s *redisRawPubSubStub) EnqueuePingResult(nCalls int, result error) *redisR } func (s *redisRawPubSubStub) EnqueuePingResultForever(result error) *redisRawPubSubStub { - go func() { - for { - select { - case s.pingResultQueue <- result: - case <-s.closed: - return - } - } - }() + s.fixedPingResult = result + close(s.pingResultQueue) return s } @@ -63,7 +57,10 @@ func (s *redisRawPubSubStub) Ping(ctx context.Context, payload ...string) error select { case <-s.closed: return errors.New("stub RedisRawPubSub closed") - case result := <-s.pingResultQueue: + case result, open := <-s.pingResultQueue: + if !open { + return s.fixedPingResult + } return result default: assert.FailNow(s.t, "Unexpected ping call")