From 1e9a4c0bc3fc1d3de7bc03c6353f7c625d647e37 Mon Sep 17 00:00:00 2001 From: Marek Fedorovic Date: Thu, 2 May 2024 11:03:38 +1000 Subject: [PATCH 1/5] Initial commit --- examples/consumer-metrics/package-lock.json | 322 ++++++++++++++++++ examples/consumer-metrics/package.json | 27 ++ .../consumer-metrics/src/consumer/index.ts | 13 + examples/consumer-metrics/src/logger.ts | 18 + .../consumer-metrics/src/publisher/index.ts | 13 + 5 files changed, 393 insertions(+) create mode 100644 examples/consumer-metrics/package-lock.json create mode 100644 examples/consumer-metrics/package.json create mode 100644 examples/consumer-metrics/src/consumer/index.ts create mode 100644 examples/consumer-metrics/src/logger.ts create mode 100644 examples/consumer-metrics/src/publisher/index.ts diff --git a/examples/consumer-metrics/package-lock.json b/examples/consumer-metrics/package-lock.json new file mode 100644 index 0000000..05ec28a --- /dev/null +++ b/examples/consumer-metrics/package-lock.json @@ -0,0 +1,322 @@ +{ + "name": "@kindredgroup/pit-k8s-deployer", + "version": "1.1.8-dev", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@kindredgroup/pit-k8s-deployer", + "version": "1.1.8-dev", + "license": "MIT", + "dependencies": { + "uuid": "^9.0.1", + "winston": "^3.11.0" + }, + "devDependencies": { + "@types/node": "^20.8.9", + "@types/uuid": "^9.0.8", + "typescript": "^5.2.2" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "node_modules/logform": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", + "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/winston": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz", + "integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", + "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + } + } +} diff --git a/examples/consumer-metrics/package.json b/examples/consumer-metrics/package.json new file mode 100644 index 0000000..c2f31b0 --- /dev/null +++ b/examples/consumer-metrics/package.json @@ -0,0 +1,27 @@ +{ + "name": "@kindredgroup/pit-k8s-deployer", + "version": "1.1.8-dev", + "description": "The deployment utility for apps designed to run in K8s clusters", + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/kindredgroup/pit-toolkit.git" + }, + "author": "kindredgroup", + "license": "MIT", + "homepage": "https://github.com/kindredgroup/pit-toolkit#readme", + "devDependencies": { + "@types/node": "^20.8.9", + "@types/uuid": "^9.0.8", + "typescript": "^5.2.2" + }, + "scripts": { + "test": "" + }, + "dependencies": { + "kafkajs": "^2.2.4", + "lz4": "0.6.5", + "uuid": "^9.0.1", + "winston": "^3.11.0" + } +} diff --git a/examples/consumer-metrics/src/consumer/index.ts b/examples/consumer-metrics/src/consumer/index.ts new file mode 100644 index 0000000..a2794bd --- /dev/null +++ b/examples/consumer-metrics/src/consumer/index.ts @@ -0,0 +1,13 @@ +import { logger } from "../logger.js" + +const main = async () => { + logger.info("main(): Starting consumer...") +} + +main() + .catch(e => { + logger.error("Message: %s", e.message) + if (e.cause) logger.error(e.cause) + if (e.stack) logger.error("Stack:\n%s", e.stack) + process.exit(1) + }) \ No newline at end of file diff --git a/examples/consumer-metrics/src/logger.ts b/examples/consumer-metrics/src/logger.ts new file mode 100644 index 0000000..701d137 --- /dev/null +++ b/examples/consumer-metrics/src/logger.ts @@ -0,0 +1,18 @@ +import { createLogger, format, transports } from "winston" + +const logger = createLogger({ + level: "info", + format: format.combine( + format.colorize(), + format.timestamp(), + format.align(), + format.splat(), + format.printf(({ timestamp, level, message }) => `${ timestamp } - ${ level }: ${ message }`) + ), + transports: [ + new transports.Console() + ], +}) + +export const LOG_SEPARATOR_LINE = "* * * * * * * * * * * * *" +export { logger } \ No newline at end of file diff --git a/examples/consumer-metrics/src/publisher/index.ts b/examples/consumer-metrics/src/publisher/index.ts new file mode 100644 index 0000000..f9685c8 --- /dev/null +++ b/examples/consumer-metrics/src/publisher/index.ts @@ -0,0 +1,13 @@ +import { logger } from "../logger.js" + +const main = async () => { + logger.info("main(): Starting producer...") +} + +main() + .catch(e => { + logger.error("Message: %s", e.message) + if (e.cause) logger.error(e.cause) + if (e.stack) logger.error("Stack:\n%s", e.stack) + process.exit(1) + }) \ No newline at end of file From 540a50c93c8d51377de7f9c4d0e3344c01061134 Mon Sep 17 00:00:00 2001 From: Marek Fedorovic Date: Fri, 3 May 2024 15:51:38 +1000 Subject: [PATCH 2/5] Test session and test code --- examples/consumer-metrics/README.md | 45 ++++++ examples/consumer-metrics/docs/metrics.png | Bin 0 -> 77210 bytes examples/consumer-metrics/package-lock.json | 128 ++++++++++++++++++ examples/consumer-metrics/package.json | 6 +- .../consumer-metrics/src/consumer/index.ts | 35 +++++ examples/consumer-metrics/src/core.ts | 16 +++ .../consumer-metrics/src/publisher/index.ts | 75 ++++++++++ examples/consumer-metrics/tsconfig.json | 18 +++ .../load-generator/src/index-pond-ordered.ts | 39 ++++++ 9 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 examples/consumer-metrics/README.md create mode 100644 examples/consumer-metrics/docs/metrics.png create mode 100644 examples/consumer-metrics/src/core.ts create mode 100644 examples/consumer-metrics/tsconfig.json create mode 100644 examples/load-generator/src/index-pond-ordered.ts diff --git a/examples/consumer-metrics/README.md b/examples/consumer-metrics/README.md new file mode 100644 index 0000000..406bc88 --- /dev/null +++ b/examples/consumer-metrics/README.md @@ -0,0 +1,45 @@ +# Consumer Metrics + +This example demonstrates two methods of measuring metrics of kafka consumer. + +**Method 1** - consumer is responsible for measuring how quickly it consumes messages, by reading current server time at each message arrive. + +**Method 2** - publisher or external process is responsible for measuring how the offset of consumer group moves forward. +This method makes the following assumptions: + - Consumer is not auto committing offsets (or it is auto-committing offsets frequently) + - Publisher can predict what will be the last offset + - The topic has a single partition. + +## How to run + +- We need to terminal windows +- We need running kafka with (auto-create topics feature) or with pre-created topic named "local_pit_consumer_metrics" +- Kafka is listening on 127.1.1:9092 and can take plaintext connection with `scram-sha-512` and credentials: `admin/admin`. + See `pit-toolkit/examples/consumer-metrics/src/core.ts` + +In terminal one run the consumer: +``` +cd pit-toolkit/examples/consumer-metrics +npm i +npm run start.consumer +``` +It will connect to kafka and listen for messages from `local_pit_consumer_metrics` topic. When messages start coming it will print current consuming metrics every few seconds. When messages stop coming and keep silence for 30 seconds, the current metrics will be reset to zero. Once messages start coming again, the new metric will start printing. + +In the second terminal run the producer: +``` +cd pit-toolkit/examples/consumer-metrics +npm i +npm run start.publisher +``` +It will start two activities concurrently: +- it will start publishing messages one by one (not in batches). +- it will connect to kafka as admin and start fetching the offset from `local_pit_consumer_metrics` topic and consumer group used by consumer from terminal one. +- once it detects that consumer's offset has moved forward to its last value, the metric will be printed and program will terminate. + +## Test run results + +Below is the list the table of metrics comparing 9 test sessions. First 5 sessions each used 100k messages. The remaining 4 sessiones used 1mln messages each. All tests and kafka were running on Macbook. + +![](./docs/metrics.png) + +The column "Offset minus Consumer (%)" shows the discrepancy in reported metrics in per cents. In other words, it shows how much slower the Method 2 is comparing to Method 1. \ No newline at end of file diff --git a/examples/consumer-metrics/docs/metrics.png b/examples/consumer-metrics/docs/metrics.png new file mode 100644 index 0000000000000000000000000000000000000000..1f95069d32c55f79948a9aed42172a001559fd4f GIT binary patch literal 77210 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
^1SpR9p#D+k$MbmA2p`KF73fV5 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�orOiy5{m@;VoO#J@|{AFQnz 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 literal 0 HcmV?d00001 diff --git a/examples/consumer-metrics/package-lock.json b/examples/consumer-metrics/package-lock.json index 05ec28a..b1825b1 100644 --- a/examples/consumer-metrics/package-lock.json +++ b/examples/consumer-metrics/package-lock.json @@ -9,6 +9,9 @@ "version": "1.1.8-dev", "license": "MIT", "dependencies": { + "hdr-histogram-js": "^3.0.0", + "kafkajs": "^2.2.4", + "lz4": "0.6.5", "uuid": "^9.0.1", "winston": "^3.11.0" }, @@ -18,6 +21,11 @@ "typescript": "^5.2.2" } }, + "node_modules/@assemblyscript/loader": { + "version": "0.19.23", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.19.23.tgz", + "integrity": "sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw==" + }, "node_modules/@colors/colors": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", @@ -61,6 +69,48 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -101,6 +151,11 @@ "text-hex": "1.0.x" } }, + "node_modules/cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==" + }, "node_modules/enabled": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", @@ -116,6 +171,38 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, + "node_modules/hdr-histogram-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-3.0.0.tgz", + "integrity": "sha512-/EpvQI2/Z98mNFYEnlqJ8Ogful8OpArLG/6Tf2bPnkutBVLIeMVNHjk1ZDfshF2BUweipzbk+dB1hgSB7SIakw==", + "dependencies": { + "@assemblyscript/loader": "^0.19.21", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -137,6 +224,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/kafkajs": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz", + "integrity": "sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -158,11 +253,31 @@ "node": ">= 12.0.0" } }, + "node_modules/lz4": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/lz4/-/lz4-0.6.5.tgz", + "integrity": "sha512-KSZcJU49QZOlJSItaeIU3p8WoAvkTmD9fJqeahQXNu1iQ/kR0/mQLdbrK8JY9MY8f6AhJoMrihp1nu1xDbscSQ==", + "hasInstallScript": true, + "dependencies": { + "buffer": "^5.2.1", + "cuint": "^0.2.2", + "nan": "^2.13.2", + "xxhashjs": "^0.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/nan": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==" + }, "node_modules/one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", @@ -171,6 +286,11 @@ "fn.name": "1.x.x" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -317,6 +437,14 @@ "engines": { "node": ">= 12.0.0" } + }, + "node_modules/xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "dependencies": { + "cuint": "^0.2.2" + } } } } diff --git a/examples/consumer-metrics/package.json b/examples/consumer-metrics/package.json index c2f31b0..3f40ec0 100644 --- a/examples/consumer-metrics/package.json +++ b/examples/consumer-metrics/package.json @@ -16,9 +16,13 @@ "typescript": "^5.2.2" }, "scripts": { - "test": "" + "clean": "rm -rf dist", + "build": "npm run clean && npx tsc", + "start.consumer": "npm run build && node dist/consumer/index.js", + "start.publisher": "npm run build && node dist/publisher/index.js" }, "dependencies": { + "hdr-histogram-js": "^3.0.0", "kafkajs": "^2.2.4", "lz4": "0.6.5", "uuid": "^9.0.1", diff --git a/examples/consumer-metrics/src/consumer/index.ts b/examples/consumer-metrics/src/consumer/index.ts index a2794bd..b3eda09 100644 --- a/examples/consumer-metrics/src/consumer/index.ts +++ b/examples/consumer-metrics/src/consumer/index.ts @@ -1,7 +1,42 @@ +import { CONSUMER_GROUP, PRINT_PROGRESS_EVERY, TOPIC, kafkaClient } from "../core.js" import { logger } from "../logger.js" const main = async () => { logger.info("main(): Starting consumer...") + + const consumer = kafkaClient.consumer({ + groupId: CONSUMER_GROUP, + readUncommitted: false + }) + + await consumer.subscribe({ topics: [ TOPIC ] }) + + let firstMessageTime = 0 + let recentMessageTime = 0 + let count = 0 + await consumer.run({ + autoCommit: false, + eachMessage: async ({ message }) => { + if (firstMessageTime == 0) firstMessageTime = Date.now() + recentMessageTime = Date.now() + count++ + if (count % PRINT_PROGRESS_EVERY == 0) logger.info("consuming: %s", message.value?.toString()) + await consumer.commitOffsets([ { topic: TOPIC, partition: 0, offset: `${ parseInt(message.offset) + 1 }` } ]) + } + }) + + // start metrics calculator + setInterval(() => { + if (Date.now() - recentMessageTime >= 30_000) { + logger.info("auto resetting metrics...") + firstMessageTime = 0 + recentMessageTime = 0 + count = 0 + } + if (recentMessageTime) { + logger.info("consumer rate: %s TPS", (count / ((recentMessageTime - firstMessageTime) / 1_000)).toFixed(2)) + } + }, 5_000) } main() diff --git a/examples/consumer-metrics/src/core.ts b/examples/consumer-metrics/src/core.ts new file mode 100644 index 0000000..88ac036 --- /dev/null +++ b/examples/consumer-metrics/src/core.ts @@ -0,0 +1,16 @@ +import { Kafka } from "kafkajs" +import { v4 as uuid } from 'uuid' + +export const TOPIC = "local_pit_consumer_metrics" +export const CONSUMER_GROUP = "local_pit_group" +export const PRINT_PROGRESS_EVERY = 50_000 + +export const kafkaClient = new Kafka( { + brokers: [ "127.0.0.1:9092" ], + clientId: uuid(), + sasl: { + mechanism: "scram-sha-512", + username: "admin", + password: "admin", + } +}) \ No newline at end of file diff --git a/examples/consumer-metrics/src/publisher/index.ts b/examples/consumer-metrics/src/publisher/index.ts index f9685c8..e0f2e7c 100644 --- a/examples/consumer-metrics/src/publisher/index.ts +++ b/examples/consumer-metrics/src/publisher/index.ts @@ -1,7 +1,82 @@ +import { Admin, Producer } from "kafkajs" +import * as hdr from "hdr-histogram-js" + +import { CONSUMER_GROUP, PRINT_PROGRESS_EVERY, TOPIC, kafkaClient } from "../core.js" import { logger } from "../logger.js" +const OFFSET_MONITOR_FREQUENCY_SECONDS = 15 +const MESSAGES_COUNT = 1_000_000 +const consumerMetrics = hdr.build() + const main = async () => { logger.info("main(): Starting producer...") + + const producer = kafkaClient.producer() + await producer.connect() + + const admin = kafkaClient.admin() + await admin.connect() + + const initialOffset = await getOffsetValue(admin) + const expectedOffset = Math.max(0, initialOffset) + MESSAGES_COUNT + + let sentMessages = 0 + logger.info("Initial offset: %s, expected offset: %s", initialOffset, expectedOffset) + setTimeout(async() => { + await startTest(producer, sent => { sentMessages = sent }) + await producer.disconnect() + }, 0) + + setTimeout(async() => { + const timer = await startMonitor(admin, async recentlyFetchedOffset => { + if (recentlyFetchedOffset < expectedOffset) return + if (recentlyFetchedOffset > expectedOffset && sentMessages < MESSAGES_COUNT) { + logger.warn("Incorrect value of fetched offset: %s. Current messages: %s. Approx expected offset is: %s", recentlyFetchedOffset, sentMessages, sentMessages + initialOffset) + return + } + consumerMetrics.recordValue(Date.now()) + if (timer) { + logger.info("stopping offset monitor....") + clearInterval(timer) + } + await admin.disconnect() + printMetrics() + }) + }, 0) +} + +const getOffsetValue = async(admin: Admin) => { + const offsets = await admin.fetchOffsets({ groupId: CONSUMER_GROUP, topics: [ TOPIC ] }) + const offsetsArray = offsets.flatMap(({ partitions }) => { + return partitions.map(({ offset }) => offset) + }) + return offsetsArray.length > 0 ? parseInt(offsetsArray[0]) : 0 +} + +const startMonitor = async (admin: Admin, offsetCallback: (value: number) => Promise) => { + return setInterval(async () => { + const offset = await getOffsetValue(admin) + logger.info("fetched offset: %s", offset) + await offsetCallback(offset) + }, OFFSET_MONITOR_FREQUENCY_SECONDS * 1_000) +} + +const startTest = async (producer: Producer, publishCallback: (sent: number) => void) => { + consumerMetrics.recordValue(Date.now()) + for (let i = 1; i <= MESSAGES_COUNT; i++) { + await producer.send({ + topic: TOPIC, + messages: [ { value: `Sample message: ${i}` } ], + }) + publishCallback(i) + if (i % PRINT_PROGRESS_EVERY == 0) logger.info("sent: %s", i) + } + logger.info("all messages have been published") +} + +const printMetrics = () => { + logger.info("Count : %s", MESSAGES_COUNT) + logger.info("Throughput: %s (tps)", (MESSAGES_COUNT / ((consumerMetrics.maxValue - consumerMetrics.minNonZeroValue) / 1_000)).toFixed(2)) } main() diff --git a/examples/consumer-metrics/tsconfig.json b/examples/consumer-metrics/tsconfig.json new file mode 100644 index 0000000..aaed676 --- /dev/null +++ b/examples/consumer-metrics/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "node16", + "esModuleInterop": true, + "allowArbitraryExtensions": true, + "declaration": true, + "target": "ES2022", + "moduleResolution": "node16", + "sourceMap": true, + "outDir": "dist" + }, + "include": ["src/**/**/*.ts", "tests/**/**/*.ts"], + "exclude": [ + "build", + "dist", + "node_modules", + ] +} \ No newline at end of file diff --git a/examples/load-generator/src/index-pond-ordered.ts b/examples/load-generator/src/index-pond-ordered.ts new file mode 100644 index 0000000..e049143 --- /dev/null +++ b/examples/load-generator/src/index-pond-ordered.ts @@ -0,0 +1,39 @@ +import * as hdr from "hdr-histogram-js" + +import { logger } from "./utls/logger.js" +import { Pond } from "./utls/pond.js" + +const CONCURRENCY = 400 + +const main = async () => { + const pool = new Pond(CONCURRENCY) + + const list = new Array() + const ITEMS_COUNT = 100_000 + for (let i = 1; i <= ITEMS_COUNT; i++) { + list.push(i) + } + list.reverse() + + const processedItems = new Array() + for (let i = 1; i <= ITEMS_COUNT; i++) { + pool.submit(async () => { + const item = list.pop() + logger.info("%s - %s", i, item) + processedItems.push(item) + }) + } + + for (let i = 0; i < processedItems.length; i += 2) { + const a = processedItems[i] + const b = processedItems[i + 1] + if (b - a !== 1) throw Error(`out of order items: $a and $b`) + } +} + +main() + .catch(e => { + logger.error("Message: %s", e.message) + if (e.cause) logger.error(e.cause) + if (e.stack) logger.error("Stack:\n%s", e.stack) + }) \ No newline at end of file From a0cdaeccf0337c59b86d0548aba58d8a3536b067 Mon Sep 17 00:00:00 2001 From: Marek Fedorovic Date: Fri, 3 May 2024 16:07:26 +1000 Subject: [PATCH 3/5] chore: typo --- examples/consumer-metrics/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/consumer-metrics/README.md b/examples/consumer-metrics/README.md index 406bc88..eb76d6f 100644 --- a/examples/consumer-metrics/README.md +++ b/examples/consumer-metrics/README.md @@ -12,7 +12,7 @@ This method makes the following assumptions: ## How to run -- We need to terminal windows +- We need two terminal windows - We need running kafka with (auto-create topics feature) or with pre-created topic named "local_pit_consumer_metrics" - Kafka is listening on 127.1.1:9092 and can take plaintext connection with `scram-sha-512` and credentials: `admin/admin`. See `pit-toolkit/examples/consumer-metrics/src/core.ts` From 49e56aba3e8d9aa85bf6c26c5277a3184d6ad41c Mon Sep 17 00:00:00 2001 From: Marek Fedorovic Date: Fri, 3 May 2024 16:08:44 +1000 Subject: [PATCH 4/5] chore: typo --- examples/consumer-metrics/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/consumer-metrics/README.md b/examples/consumer-metrics/README.md index eb76d6f..37f373a 100644 --- a/examples/consumer-metrics/README.md +++ b/examples/consumer-metrics/README.md @@ -38,7 +38,7 @@ It will start two activities concurrently: ## Test run results -Below is the list the table of metrics comparing 9 test sessions. First 5 sessions each used 100k messages. The remaining 4 sessiones used 1mln messages each. All tests and kafka were running on Macbook. +Below is the table of metrics comparing 9 test sessions. First 5 sessions each used 100k messages. The remaining 4 sessiones used 1mln messages each. All tests and kafka were running on Macbook. ![](./docs/metrics.png) From 6173b6c5e5104260fd0dd05fc2046185da780b2c Mon Sep 17 00:00:00 2001 From: Marek Fedorovic Date: Fri, 3 May 2024 16:09:15 +1000 Subject: [PATCH 5/5] chore: typo --- examples/consumer-metrics/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/consumer-metrics/README.md b/examples/consumer-metrics/README.md index 37f373a..c98c95e 100644 --- a/examples/consumer-metrics/README.md +++ b/examples/consumer-metrics/README.md @@ -38,7 +38,7 @@ It will start two activities concurrently: ## Test run results -Below is the table of metrics comparing 9 test sessions. First 5 sessions each used 100k messages. The remaining 4 sessiones used 1mln messages each. All tests and kafka were running on Macbook. +Below is the table of metrics comparing 9 test sessions. First 5 sessions each used 100k messages. The remaining 4 sessions used 1mln messages each. All tests and kafka were running on Macbook. ![](./docs/metrics.png)