diff --git a/docs/src/pages/guides/cli-options.md b/docs/src/pages/guides/cli-options.md index f4f0532f0..868104d3c 100644 --- a/docs/src/pages/guides/cli-options.md +++ b/docs/src/pages/guides/cli-options.md @@ -48,6 +48,14 @@ For example, assuming you have a file named `only-notif.yml` whose content `{"no monika -c foo-monitoring.yml only-notif.yml ``` +## Compact probes + +Given multiple probes with the identical payload request / socket / db connection configuration, Monika can optionally compact them for you. This aims to do the probing for said identical probes once. With retained alerts configuration. + +```bash +monika -c config-with-identical-probes.yml --compact-probes +``` + ## Auto-update Monika supports automatic update with `--auto-update major|minor|patch`. Where `major|minor|patch` refers to [semantic versioning (semver) specification](https://semver.org/). By default, the updater will check for a new Monika version every 24 hours. diff --git a/package-lock.json b/package-lock.json index 8fe1b4e4c..2e5b3438c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1718,6 +1718,8 @@ "node_modules/@gar/promisify": { "version": "1.1.3", "license": "MIT", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "optional": true }, "node_modules/@gmrchk/cli-testing-library": { @@ -2147,6 +2149,8 @@ "node_modules/@npmcli/fs": { "version": "1.1.1", "license": "ISC", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", "optional": true, "dependencies": { "@gar/promisify": "^1.0.1", @@ -2156,6 +2160,9 @@ "node_modules/@npmcli/move-file": { "version": "1.1.2", "license": "MIT", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", "optional": true, "dependencies": { "mkdirp": "^1.0.4", @@ -4325,6 +4332,8 @@ "node_modules/@tootallnate/once": { "version": "1.1.2", "license": "MIT", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "optional": true, "engines": { "node": ">= 6" @@ -4950,6 +4959,8 @@ "node_modules/agentkeepalive": { "version": "4.5.0", "license": "MIT", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", "optional": true, "dependencies": { "humanize-ms": "^1.2.1" @@ -4960,8 +4971,10 @@ }, "node_modules/aggregate-error": { "version": "3.1.0", - "devOptional": true, "license": "MIT", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "devOptional": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -4972,8 +4985,10 @@ }, "node_modules/aggregate-error/node_modules/clean-stack": { "version": "2.2.0", - "devOptional": true, "license": "MIT", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "devOptional": true, "engines": { "node": ">=6" } @@ -5078,8 +5093,10 @@ }, "node_modules/archy": { "version": "1.0.0", - "dev": true, - "license": "MIT" + "license": "MIT", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true }, "node_modules/are-we-there-yet": { "version": "2.0.0", @@ -5609,6 +5626,8 @@ "node_modules/cacache": { "version": "15.3.0", "license": "ISC", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", "optional": true, "dependencies": { "@npmcli/fs": "^1.0.0", @@ -5637,6 +5656,8 @@ "node_modules/cacache/node_modules/lru-cache": { "version": "6.0.0", "license": "ISC", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "optional": true, "dependencies": { "yallist": "^4.0.0" @@ -5955,6 +5976,8 @@ }, "node_modules/ci-info": { "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, "funding": [ { @@ -6848,6 +6871,8 @@ "node_modules/env-paths": { "version": "2.2.1", "license": "MIT", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "optional": true, "engines": { "node": ">=6" @@ -6856,6 +6881,8 @@ "node_modules/err-code": { "version": "2.0.3", "license": "MIT", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "optional": true }, "node_modules/error-ex": { @@ -7865,8 +7892,10 @@ }, "node_modules/fastest-levenshtein": { "version": "1.0.16", - "dev": true, "license": "MIT", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, "engines": { "node": ">= 4.9.1" } @@ -8883,8 +8912,10 @@ }, "node_modules/http-cache-semantics": { "version": "4.1.1", - "devOptional": true, - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "devOptional": true }, "node_modules/http-call": { "version": "5.3.0", @@ -8931,6 +8962,8 @@ "node_modules/http-proxy-agent": { "version": "4.0.1", "license": "MIT", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "optional": true, "dependencies": { "@tootallnate/once": "1", @@ -8974,6 +9007,8 @@ "node_modules/humanize-ms": { "version": "1.2.1", "license": "MIT", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "optional": true, "dependencies": { "ms": "^2.0.0" @@ -9082,8 +9117,10 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "devOptional": true, "license": "MIT", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "devOptional": true, "engines": { "node": ">=0.8.19" } @@ -9098,6 +9135,8 @@ "node_modules/infer-owner": { "version": "1.0.4", "license": "ISC", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", "optional": true }, "node_modules/inflight": { @@ -9338,6 +9377,8 @@ "node_modules/is-lambda": { "version": "1.0.1", "license": "MIT", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "optional": true }, "node_modules/is-negative-zero": { @@ -9534,8 +9575,10 @@ }, "node_modules/is-typedarray": { "version": "1.0.0", - "dev": true, - "license": "MIT" + "license": "MIT", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true }, "node_modules/is-unicode-supported": { "version": "0.1.0", @@ -9832,8 +9875,10 @@ }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", - "dev": true, - "license": "MIT" + "license": "MIT", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true }, "node_modules/json-schema-traverse": { "version": "1.0.0", @@ -10191,6 +10236,8 @@ "node_modules/make-fetch-happen": { "version": "9.1.0", "license": "ISC", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", "optional": true, "dependencies": { "agentkeepalive": "^4.1.3", @@ -10217,6 +10264,8 @@ "node_modules/make-fetch-happen/node_modules/lru-cache": { "version": "6.0.0", "license": "ISC", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "optional": true, "dependencies": { "yallist": "^4.0.0" @@ -10550,6 +10599,8 @@ "node_modules/minipass-collect": { "version": "1.0.2", "license": "ISC", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", "optional": true, "dependencies": { "minipass": "^3.0.0" @@ -10561,6 +10612,8 @@ "node_modules/minipass-fetch": { "version": "1.4.1", "license": "MIT", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", "optional": true, "dependencies": { "minipass": "^3.1.0", @@ -10577,6 +10630,8 @@ "node_modules/minipass-flush": { "version": "1.0.5", "license": "ISC", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "optional": true, "dependencies": { "minipass": "^3.0.0" @@ -10588,6 +10643,8 @@ "node_modules/minipass-pipeline": { "version": "1.2.4", "license": "ISC", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "optional": true, "dependencies": { "minipass": "^3.0.0" @@ -10599,6 +10656,8 @@ "node_modules/minipass-sized": { "version": "1.0.3", "license": "ISC", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "optional": true, "dependencies": { "minipass": "^3.0.0" @@ -11119,6 +11178,8 @@ "node_modules/node-gyp": { "version": "8.4.1", "license": "MIT", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", "optional": true, "dependencies": { "env-paths": "^2.2.0", @@ -11152,6 +11213,9 @@ "node_modules/node-gyp/node_modules/are-we-there-yet": { "version": "3.0.1", "license": "ISC", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", "optional": true, "dependencies": { "delegates": "^1.0.0", @@ -11164,6 +11228,9 @@ "node_modules/node-gyp/node_modules/gauge": { "version": "4.0.4", "license": "ISC", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", "optional": true, "dependencies": { "aproba": "^1.0.3 || ^2.0.0", @@ -11182,11 +11249,16 @@ "node_modules/node-gyp/node_modules/isexe": { "version": "2.0.0", "license": "ISC", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "optional": true }, "node_modules/node-gyp/node_modules/npmlog": { "version": "6.0.2", "license": "ISC", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", "optional": true, "dependencies": { "are-we-there-yet": "^3.0.0", @@ -11201,6 +11273,8 @@ "node_modules/node-gyp/node_modules/which": { "version": "2.0.2", "license": "ISC", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "optional": true, "dependencies": { "isexe": "^2.0.0" @@ -11250,8 +11324,10 @@ }, "node_modules/normalize-package-data": { "version": "6.0.2", - "dev": true, "license": "BSD-2-Clause", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", @@ -14209,8 +14285,10 @@ }, "node_modules/p-map": { "version": "4.0.0", - "devOptional": true, "license": "MIT", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "devOptional": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -14974,11 +15052,15 @@ "node_modules/promise-inflight": { "version": "1.0.1", "license": "ISC", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "optional": true }, "node_modules/promise-retry": { "version": "2.0.1", "license": "MIT", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "optional": true, "dependencies": { "err-code": "^2.0.2", @@ -14991,6 +15073,8 @@ "node_modules/promise-retry/node_modules/retry": { "version": "0.12.0", "license": "MIT", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "optional": true, "engines": { "node": ">= 4" @@ -15980,6 +16064,8 @@ "node_modules/socks-proxy-agent": { "version": "6.2.1", "license": "MIT", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", "optional": true, "dependencies": { "agent-base": "^6.0.2", @@ -16178,6 +16264,8 @@ "node_modules/ssri": { "version": "8.0.1", "license": "ISC", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", "optional": true, "dependencies": { "minipass": "^3.1.1" @@ -16565,8 +16653,10 @@ }, "node_modules/text-table": { "version": "0.2.0", - "dev": true, - "license": "MIT" + "license": "MIT", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "node_modules/thread-stream": { "version": "2.7.0", @@ -16852,8 +16942,10 @@ }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", - "dev": true, "license": "MIT", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, "dependencies": { "is-typedarray": "^1.0.0" } @@ -16912,6 +17004,8 @@ "node_modules/unique-filename": { "version": "1.1.1", "license": "ISC", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", "optional": true, "dependencies": { "unique-slug": "^2.0.0" @@ -16920,6 +17014,8 @@ "node_modules/unique-slug": { "version": "2.0.2", "license": "ISC", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", "optional": true, "dependencies": { "imurmurhash": "^0.1.4" @@ -17296,8 +17392,10 @@ }, "node_modules/write-file-atomic": { "version": "3.0.3", - "dev": true, "license": "ISC", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", diff --git a/src/components/config/compact-config.test.ts b/src/components/config/compact-config.test.ts new file mode 100644 index 000000000..463b2b2fd --- /dev/null +++ b/src/components/config/compact-config.test.ts @@ -0,0 +1,350 @@ +/********************************************************************************** + * MIT License * + * * + * Copyright (c) 2021 Hyperjump Technology * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **********************************************************************************/ + +import { expect } from 'chai' +import { compactProbes, getDecompactedProbesById } from './compact-config' +import type { Probe } from '../../interfaces/probe' + +describe('Probe Compaction', () => { + describe('compactProbes', () => { + it('should return original probes if no identical probes found', () => { + const probes: Probe[] = [ + { + id: '1', + name: 'Test 1', + interval: 30, + requests: [ + { url: 'http://test1.com', followRedirects: 5, timeout: 10_000 }, + ], + alerts: [], + }, + { + id: '2', + name: 'Test 2', + interval: 60, + requests: [ + { url: 'http://test2.com', followRedirects: 5, timeout: 10_000 }, + ], + alerts: [], + }, + ] + + const result = compactProbes(probes) + expect(result).to.deep.equal(probes) + }) + + it('should merge identical HTTP probes', () => { + const probes: Probe[] = [ + { + id: '1', + name: 'Test 2', + alerts: [], + interval: 30, + requests: [ + { + url: 'http://test.com', + followRedirects: 5, + timeout: 10_000, + alerts: [ + { + id: 'alert1', + assertion: 'response_time < 200', + message: 'Response too slow', + }, + ], + }, + ], + }, + { + id: '2', + name: 'Test 2', + alerts: [], + interval: 30, + requests: [ + { + url: 'http://test.com', + followRedirects: 5, + timeout: 10_000, + alerts: [ + { + id: 'alert2', + assertion: 'response_time < 200', + message: 'Response too slow', + }, + ], + }, + ], + }, + ] + + const result = compactProbes(probes) + expect(result).to.have.lengthOf(1) + expect(result[0].requests?.[0].alerts).to.have.lengthOf(1) + }) + + it('should merge identical socket probes', () => { + const probes: Probe[] = [ + { + id: '1', + interval: 30, + name: 'Test 1', + alerts: [], + socket: { + host: 'test.com', + port: 80, + data: 'test', + alerts: [ + { + id: 'alert1', + assertion: 'response_time < 200', + message: 'Response too slow', + }, + ], + }, + }, + { + id: '2', + interval: 30, + name: 'Test 2', + alerts: [], + socket: { + host: 'test.com', + port: 80, + data: 'test', + alerts: [ + { + id: 'alert2', + assertion: 'response_time < 200', + message: 'Response too slow', + }, + ], + }, + }, + ] + + const result = compactProbes(probes) + expect(result).to.have.lengthOf(1) + expect(result[0].socket?.alerts).to.have.lengthOf(1) + }) + + it('should not merge probes with multiple probe types', () => { + const probes: Probe[] = [ + { + id: '1', + interval: 30, + name: 'Test 1', + alerts: [], + requests: [ + { url: 'http://test.com', followRedirects: 5, timeout: 10_000 }, + ], + socket: { + host: 'test.com', + port: 80, + data: 'test', + alerts: [ + { + id: 'alert1', + assertion: 'response_time < 200', + message: 'Response too slow', + }, + ], + }, + }, + { + id: '2', + interval: 30, + name: 'Test 2', + alerts: [], + requests: [ + { url: 'http://test.com', followRedirects: 5, timeout: 10_000 }, + ], + socket: { host: 'test.com', port: 80, data: 'test' }, + }, + ] + + const result = compactProbes(probes) + expect(result).to.deep.equal(probes) + }) + + it('should not merge probes with request chaining', () => { + const probes: Probe[] = [ + { + id: '1', + interval: 30, + name: 'Test 1', + alerts: [], + requests: [ + { url: 'http://test1.com', followRedirects: 5, timeout: 10_000 }, + { url: 'http://test2.com', followRedirects: 5, timeout: 10_000 }, + ], + }, + { + id: '2', + interval: 30, + name: 'Test 2', + alerts: [], + requests: [ + { url: 'http://test1.com', followRedirects: 5, timeout: 10_000 }, + { url: 'http://test2.com', followRedirects: 5, timeout: 10_000 }, + ], + }, + ] + + const result = compactProbes(probes) + expect(result).to.deep.equal(probes) + }) + }) + + describe('getDecompactedProbesById', () => { + it('should return original probe by id', () => { + const probes: Probe[] = [ + { + id: '1', + name: 'Test 1', + interval: 30, + alerts: [], + requests: [ + { + url: 'http://test.com', + followRedirects: 5, + timeout: 10_000, + }, + ], + }, + ] + + compactProbes(probes) // Sets decompactedProbes + const result = getDecompactedProbesById('1') + expect(result).to.deep.equal(probes[0]) + }) + + it('should return undefined for non-existent probe id', () => { + const probes: Probe[] = [] + compactProbes(probes) + const result = getDecompactedProbesById('nonexistent') + expect(result).to.be.undefined + }) + + it('should deduplicate identical alerts when merging probes', () => { + const probes: Probe[] = [ + { + id: '1', + name: 'Test 1', + interval: 30, + alerts: [], + requests: [ + { + url: 'http://test.com', + followRedirects: 5, + timeout: 10_000, + alerts: [ + { + id: 'alert1', + assertion: 'response_time < 200', + message: 'Response too slow', + }, + { + id: 'alert2', + assertion: 'response_time < 200', + message: 'Response too slow', + }, + ], + }, + ], + }, + ] + + const result = compactProbes(probes) + expect(result[0].requests?.[0].alerts).to.have.lengthOf(1) + expect(result[0].requests?.[0].alerts?.[0]).to.deep.equal({ + id: 'alert1', + assertion: 'response_time < 200', + message: 'Response too slow', + }) + }) + + it('should deduplicate alerts across multiple probes', () => { + const probes: Probe[] = [ + { + id: '1', + name: 'Test 1', + interval: 30, + alerts: [], + socket: { + host: 'test.com', + port: 80, + data: 'test', + alerts: [ + { + id: 'alert1', + assertion: 'response_time < 200', + message: 'Response too slow', + }, + ], + }, + }, + { + id: '2', + name: 'Test 2', + interval: 30, + alerts: [], + socket: { + host: 'test.com', + port: 80, + data: 'test', + alerts: [ + { + id: 'alert2', + assertion: 'response_time < 200', + message: 'Response too slow', + }, + ], + }, + }, + { + id: '3', + name: 'Test 3', + interval: 30, + alerts: [], + socket: { + host: 'test.com', + port: 80, + data: 'test', + alerts: [ + { + id: 'alert1', + assertion: 'response_time < 200', + message: 'Response too slow', + }, + ], + }, + }, + ] + + const result = compactProbes(probes) + expect(result).to.have.lengthOf(1) + expect(result[0].socket?.alerts).to.have.lengthOf(1) + }) + }) +}) diff --git a/src/components/config/compact-config.ts b/src/components/config/compact-config.ts new file mode 100644 index 000000000..eda0f9acc --- /dev/null +++ b/src/components/config/compact-config.ts @@ -0,0 +1,479 @@ +/********************************************************************************** + * MIT License * + * * + * Copyright (c) 2021 Hyperjump Technology * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **********************************************************************************/ + +import lodash from 'lodash' +import type { Probe, ProbeAlert } from '../../interfaces/probe' +import { randomUUID } from 'node:crypto' +import { log } from '../../utils/pino' + +let decompactedProbes: Probe[] = [] + +export function compactProbes(probes: Probe[]): Probe[] { + if (probes.length === 0) { + return [] + } + + decompactedProbes = probes + const identicalProbeIds = identifyIdenticalProbes(probes) + for (const set of identicalProbeIds) { + const identicalProbeIdsArray = [...set] + log.info( + `Found identical probes, following probes IDs will be compacted on runtime: ${identicalProbeIdsArray.join( + ', ' + )}` + ) + } + + if (identicalProbeIds.length === 0) { + return probes.map((probe) => { + const mergedProbeAlerts = mergeProbeAlerts([probe]) + mergedProbeAlerts.id = probe.id + delete mergedProbeAlerts.jointId + return mergedProbeAlerts + }) + } + + const nonIdenticalProbes = probes + .filter((probe) => !identicalProbeIds.some((set) => set.has(probe.id))) + .map((probe) => mergeProbeAlerts([probe])) + + const mergedProbes = identicalProbeIds.map((set) => { + const identicalProbes = probes.filter((probe) => set.has(probe.id)) + return mergeProbeAlerts(identicalProbes) + }) + + return [...nonIdenticalProbes, ...mergedProbes] +} + +export function getDecompactedProbesById(probeId: string): Probe | undefined { + return decompactedProbes.find((probe) => probe.id === probeId) +} + +function mergeProbeAlerts(probes: Probe[]): Probe { + const mergedAlerts = probes.flatMap((probe) => probe.alerts) + let mergedProbe: Probe = { + ...mergeInnerAlerts(probes[0], probes[0]), + alerts: deduplicateAlerts(mergedAlerts), + } + + for (const p of probes.slice(1)) { + mergedProbe = mergeInnerAlerts(p, mergedProbe) + } + + return { + ...mergedProbe, + id: randomUUID(), + jointId: probes.map((probe) => probe.id), + } +} + +// merge corresponding alerts from the probes +function mergeInnerAlerts(newProbe: Probe, destination: Probe): Probe { + destination = mergeHttpAlerts(newProbe, destination) + destination = mergeSocketAlerts(newProbe, destination) + destination = mergeRedisAlerts(newProbe, destination) + destination = mergeMongoAlerts(newProbe, destination) + destination = mergeMariaDbAlerts(newProbe, destination) + destination = mergeMysqlAlerts(newProbe, destination) + destination = mergePostgresAlerts(newProbe, destination) + destination = mergePingAlerts(newProbe, destination) + + return destination +} + +function deduplicateAlerts(alerts: ProbeAlert[]): ProbeAlert[] { + if (alerts.filter((a) => a !== undefined).length === 0) { + return [] + } + + const deduplicatedAlerts: ProbeAlert[] = [alerts[0]] + for (const alert of alerts.slice(1)) { + // Check if the alert is already in the deduplicatedAlerts + const isAlertExist = deduplicatedAlerts.some((a) => { + const firstPayload: Partial = lodash.cloneDeep(a) + const secondPayload: Partial = lodash.cloneDeep(alert) + if (firstPayload?.id) { + delete firstPayload.id + } + + if (secondPayload?.id) { + delete secondPayload.id + } + + return lodash.isEqual(firstPayload, secondPayload) + }) + + if (alert !== undefined && !isAlertExist) { + deduplicatedAlerts.push(alert) + } + } + + return deduplicatedAlerts +} + +function mergeHttpAlerts(newProbe: Probe, destination: Probe): Probe { + if ( + destination.requests && + (newProbe.requests?.every((r) => r.alerts?.length) || + destination.requests.every((r) => r.alerts?.length)) + ) { + const mergedRequestAlerts: ProbeAlert[] = [ + ...(destination.requests?.flatMap((r) => r.alerts) || []), + ...(newProbe?.requests?.flatMap((r) => r.alerts) || []), + ] as ProbeAlert[] + + return { + ...destination, + requests: [ + ...(destination?.requests?.map((r) => ({ + ...r, + alerts: deduplicateAlerts(mergedRequestAlerts), + })) || []), + ], + } + } + + return destination +} + +function mergeSocketAlerts(newProbe: Probe, destination: Probe): Probe { + if ( + destination.socket && + (newProbe.socket?.alerts?.length || destination.socket?.alerts?.length) + ) { + const mergedSocketAlerts: ProbeAlert[] = [ + ...(destination.socket?.alerts || []), + ...(newProbe.socket?.alerts || []), + ] + return { + ...destination, + socket: { + ...destination.socket, + alerts: deduplicateAlerts(mergedSocketAlerts), + }, + } + } + + return destination +} + +function mergeRedisAlerts(newProbe: Probe, destination: Probe): Probe { + if ( + destination.redis && + (newProbe.redis?.every((r) => r.alerts?.length) || + destination.redis.every((r) => r.alerts?.length)) + ) { + const mergedRedisAlerts: ProbeAlert[] = [ + ...(destination.redis?.flatMap((r) => r.alerts) || []), + ...(newProbe.redis?.flatMap((r) => r.alerts) || []), + ] as ProbeAlert[] + return { + ...destination, + redis: [ + ...(destination.redis?.map((r) => ({ + ...r, + alerts: deduplicateAlerts(mergedRedisAlerts), + })) || []), + ], + } + } + + return destination +} + +function mergeMongoAlerts(newProbe: Probe, destination: Probe): Probe { + if ( + destination.mongo && + (destination.mongo.every((r) => r.alerts?.length) || + newProbe.mongo?.every((r) => r.alerts?.length)) + ) { + const mergedMongoAlerts: ProbeAlert[] = [ + ...(destination.mongo?.flatMap((r) => r.alerts) || []), + ...(newProbe.mongo?.flatMap((r) => r.alerts) || []), + ] as ProbeAlert[] + return { + ...destination, + mongo: [ + ...(destination.mongo?.map((r) => ({ + ...r, + alerts: deduplicateAlerts(mergedMongoAlerts), + })) || []), + ], + } + } + + return destination +} + +function mergeMariaDbAlerts(newProbe: Probe, destination: Probe): Probe { + if ( + destination.mariadb && + (destination.mariadb.every((r) => r.alerts?.length) || + newProbe.mariadb?.every((r) => r.alerts?.length)) + ) { + const mergedMariadbAlerts: ProbeAlert[] = [ + ...(destination.mariadb?.flatMap((r) => r.alerts) || []), + ...(newProbe.mariadb?.flatMap((r) => r.alerts) || []), + ] as ProbeAlert[] + return { + ...destination, + mariadb: [ + ...(destination.mariadb?.map((r) => ({ + ...r, + alerts: deduplicateAlerts(mergedMariadbAlerts), + })) || []), + ], + } + } + + return destination +} + +function mergeMysqlAlerts(newProbe: Probe, destination: Probe): Probe { + if ( + destination.mysql && + (destination.mysql.every((r) => r.alerts?.length) || + newProbe.mysql?.every((r) => r.alerts?.length)) + ) { + const mergedMysqlAlerts: ProbeAlert[] = [ + ...(destination.mysql?.flatMap((r) => r.alerts) || []), + ...(newProbe.mysql?.flatMap((r) => r.alerts) || []), + ] as ProbeAlert[] + return { + ...destination, + mysql: [ + ...(destination.mysql?.map((r) => ({ + ...r, + alerts: deduplicateAlerts(mergedMysqlAlerts), + })) || []), + ], + } + } + + return destination +} + +function mergePostgresAlerts(newProbe: Probe, destination: Probe): Probe { + if ( + destination.postgres && + (destination.postgres.every((r) => r.alerts?.length) || + newProbe.postgres?.every((r) => r.alerts?.length)) + ) { + const mergedPostgresAlerts: ProbeAlert[] = [ + ...(destination.postgres?.flatMap((r) => r.alerts) || []), + ...(newProbe.postgres?.flatMap((r) => r.alerts) || []), + ] as ProbeAlert[] + return { + ...destination, + postgres: [ + ...(destination.postgres?.map((r) => ({ + ...r, + alerts: deduplicateAlerts(mergedPostgresAlerts), + })) || []), + ], + } + } + + return destination +} + +function mergePingAlerts(newProbe: Probe, destination: Probe): Probe { + if ( + destination.ping && + (destination.ping.every((r) => r.alerts?.length) || + newProbe.ping?.every((r) => r.alerts?.length)) + ) { + const mergedPingAlerts: ProbeAlert[] = [ + ...(destination.ping?.flatMap((r) => r.alerts) || []), + ...(newProbe.ping?.flatMap((r) => r.alerts) || []), + ] as ProbeAlert[] + return { + ...destination, + ping: [ + ...(destination.ping?.map((r) => ({ + ...r, + alerts: deduplicateAlerts(mergedPingAlerts), + })) || []), + ], + } + } + + return destination +} + +// Function to identify identical probes based on specific fields +function identifyIdenticalProbes(probes: Probe[]): Set[] { + // Define fields that are used to determine if probes are identical + const httpIdentifiers: (keyof Probe)[] = ['interval', 'requests'] + const socketIdentifiers: (keyof Probe)[] = ['interval', 'socket'] + const redisIdentiers: (keyof Probe)[] = ['interval', 'redis'] + const mongoIdentiers: (keyof Probe)[] = ['interval', 'mongo'] + const mariadbIdentiers: (keyof Probe)[] = ['interval', 'mariadb'] + const mysqlIdentiers: (keyof Probe)[] = ['interval', 'mysql'] + const postgresIdentiers: (keyof Probe)[] = ['interval', 'postgres'] + const pingIdentiers: (keyof Probe)[] = ['interval', 'ping'] + + return [ + ...internalIdentification(httpIdentifiers, probes), + ...internalIdentification(socketIdentifiers, probes), + ...internalIdentification(redisIdentiers, probes), + ...internalIdentification(mongoIdentiers, probes), + ...internalIdentification(mariadbIdentiers, probes), + ...internalIdentification(mysqlIdentiers, probes), + ...internalIdentification(postgresIdentiers, probes), + ...internalIdentification(pingIdentiers, probes), + ] +} + +/** + * internalIdentification is a helper function to identify identical probes based on specific fields + * @param fieldIdentifiers identifiers to determine if probes are identical + * @param probes array of probes + * @returns array of sets of identical probe IDs + */ +// suppress double loop complexity +// eslint-disable-next-line complexity +function internalIdentification( + fieldIdentifiers: (keyof Probe)[], + probes: Probe[] +): Set[] { + const identicalProbeIds: Set[] = [] + + for (const outerProbe of probes) { + // skip probes with multiple probe types + const isChainingMultipleProbeTypes = + [ + outerProbe.requests, + outerProbe.socket, + outerProbe.redis, + outerProbe.mongo, + outerProbe.mariadb, + outerProbe.mysql, + outerProbe.postgres, + outerProbe.ping, + ].filter((probe) => probe !== undefined).length > 1 + + if (isChainingMultipleProbeTypes) { + continue + } + + // skip probes with request chaining + const isChainingHttp = outerProbe.requests && outerProbe.requests.length > 1 + const isChainingRedis = outerProbe.redis && outerProbe.redis.length > 1 + const isChainingMongo = outerProbe.mongo && outerProbe.mongo.length > 1 + const isChainingMariaDb = + outerProbe.mariadb && outerProbe.mariadb.length > 1 + const isChainingMysql = outerProbe.mysql && outerProbe.mysql.length > 1 + const isChainingPostgres = + outerProbe.postgres && outerProbe.postgres.length > 1 + const isChainingPing = outerProbe.ping && outerProbe.ping.length > 1 + if ( + isChainingHttp || + isChainingRedis || + isChainingMongo || + isChainingMariaDb || + isChainingMysql || + isChainingPostgres || + isChainingPing + ) { + continue + } + + // skip if probe is already identified as identical + const isIdentified = identicalProbeIds.some((set) => set.has(outerProbe.id)) + if (isIdentified) { + continue + } + + // get the values of the fields to be used for comparison + const currentProbeValues = stripNameAndAlerts(outerProbe, fieldIdentifiers) + + // skip if there are undefined values from identifiers + if (currentProbeValues.includes(undefined)) { + continue + } + + const identicalProbeIdsSet = new Set() + for (const innerProbe of probes) { + // skip if innerProbe has the same ID as the current probe + if (outerProbe.id === innerProbe.id) { + continue + } + + // get the values of the fields to be used for comparison + const innerProbeFieldValues = stripNameAndAlerts( + innerProbe, + fieldIdentifiers + ) + + // compare the values of the fields using deep equality + if (lodash.isEqual(currentProbeValues, innerProbeFieldValues)) { + identicalProbeIdsSet.add(outerProbe.id) + identicalProbeIdsSet.add(innerProbe.id) + } + } + + if (identicalProbeIdsSet.size > 0) { + identicalProbeIds.push(identicalProbeIdsSet) + } + } + + // Return the set of identical probe IDs + return identicalProbeIds +} + +function stripNameAndAlerts(probe: Probe, fieldIdentifiers: (keyof Probe)[]) { + const strippedProbe = fieldIdentifiers.map((key) => { + // clone the value to prevent mutation + let value = lodash.cloneDeep(probe[key]) + if (value !== undefined) { + value = deleteNameFromProbe(value) as never + value = deleteAlertsFromProbe(value) as never + } + + return value + }) + + return strippedProbe +} + +function deleteNameFromProbe(values: unknown): unknown { + if (typeof values === 'object' && values !== null && 'name' in values) { + delete values.name + } + + return values +} + +function deleteAlertsFromProbe(values: unknown): unknown { + if (Array.isArray(values)) { + return values.map((value) => deleteAlertsFromProbe(value)) + } + + if (typeof values === 'object' && values !== null && 'alerts' in values) { + delete values.alerts + } + + return values +} diff --git a/src/components/config/index.ts b/src/components/config/index.ts index 2d935037f..435b86032 100644 --- a/src/components/config/index.ts +++ b/src/components/config/index.ts @@ -35,6 +35,7 @@ import { getRawConfig } from './get' import { getProbes, setProbes } from './probe' import { sanitizeConfig } from './sanitize' import { validateConfig } from './validate' +import { compactProbes } from './compact-config' export async function initConfig() { const { flags } = getContext() @@ -115,8 +116,16 @@ export async function updateConfig(config: Config): Promise { log.info('Config changes. Updating config...') } - setContext({ config: sanitizedConfig }) - setProbes(sanitizedConfig.probes) + setContext({ + config: sanitizedConfig, + }) + + let { probes } = sanitizedConfig + if (getContext().flags['compact-probes']) { + probes = compactProbes(probes) + } + + setProbes(probes) getEventEmitter().emit(events.config.updated) } diff --git a/src/components/logger/history.ts b/src/components/logger/history.ts index 3c8e2e14e..ffd1fedbd 100644 --- a/src/components/logger/history.ts +++ b/src/components/logger/history.ts @@ -31,6 +31,7 @@ import { Probe } from '../../interfaces/probe' import type { Notification } from '@hyperjumptech/monika-notification' import { log } from '../../utils/pino' import { getErrorMessage } from '../../utils/catch-error-handler' +import { getDecompactedProbesById } from '../config/compact-config' const sqlite3 = verboseSQLite() const dbPath = path.resolve(process.cwd(), 'monika-logs.db') @@ -413,6 +414,30 @@ export async function saveProbeRequestLog({ alertQueries?: string[] error?: string }): Promise { + if (probe.jointId?.length) { + const probes = + (probe.jointId + ?.map((id) => getDecompactedProbesById(id)) + ?.filter(Boolean) as Probe[]) || [] + + await Promise.allSettled( + probes.map((originalProbe) => + saveProbeRequestLog({ + probe: originalProbe, + requestIndex, + probeRes, + alertQueries: + (originalProbe.alerts + ?.map((alert) => alert.query) + .filter(Boolean) as string[]) || [], + error: errorResp, + }) + ) + ) + + return + } + const insertProbeRequestSQL = ` INSERT INTO probe_requests ( created_at, diff --git a/src/flag.ts b/src/flag.ts index 8139fea1c..228bd33d3 100644 --- a/src/flag.ts +++ b/src/flag.ts @@ -34,6 +34,7 @@ export enum SYMON_API_VERSION { export type MonikaFlags = { 'auto-update'?: string + 'compact-probes'?: boolean config: string[] 'config-filename': string 'config-interval': number @@ -80,6 +81,7 @@ const DEFAULT_CONFIG_INTERVAL_SECONDS = 900 const DEFAULT_SYMON_REPORT_INTERVAL_MS = 10_000 export const monikaFlagsDefaultValue: MonikaFlags = { + 'compact-probes': false, config: getDefaultConfig(), 'config-filename': 'monika.yml', 'config-interval': DEFAULT_CONFIG_INTERVAL_SECONDS, @@ -126,6 +128,11 @@ export const flags = { description: 'Enable auto-update for Monika. Available options: major, minor, patch. This will make Monika terminate itself on successful update but does not restart', }), + 'compact-probes': Flags.boolean({ + default: monikaFlagsDefaultValue['compact-probes'], + description: + 'Compact probes with the same request configuration. This will not merge probes with request chaining.', + }), config: Flags.string({ char: 'c', default: monikaFlagsDefaultValue.config, diff --git a/src/interfaces/probe.ts b/src/interfaces/probe.ts index bdbb4bc63..7eaf75df6 100644 --- a/src/interfaces/probe.ts +++ b/src/interfaces/probe.ts @@ -88,6 +88,7 @@ export type Ping = { export interface Probe { id: string + jointId?: string[] name: string description?: string interval: number