From b20588a0f44c49013a3e04f0f974d649f550c397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Pichon?= Date: Tue, 27 Jul 2021 18:10:54 +0200 Subject: [PATCH] new retry metrics, better eslint (#2) * new retry metrics, better eslint * create common getMetricName method --- .eslintrc | 6 +- .github/workflows/pipeline.yml | 4 +- docs/content/module/retry.md | 13 ++- docs/package-lock.json | 6 +- docs/package.json | 2 +- jest.config.js | 1 + package-lock.json | 42 ++++----- package.json | 5 +- src/circuit.ts | 2 +- src/common.ts | 25 +++--- src/index.ts | 6 +- src/metrics/counter.ts | 4 +- src/metrics/gauge.ts | 6 +- src/module/breaker/index.ts | 2 +- src/module/bulkhead.ts | 2 +- src/module/cache.ts | 2 +- src/module/fallback.ts | 2 +- src/module/ratelimit.ts | 2 +- src/module/retry.ts | 72 ++++++++++++++-- src/module/timeout.ts | 2 +- test/module/retry.spec.ts | 150 +++++++++++++++++++++++++++++++++ 21 files changed, 291 insertions(+), 65 deletions(-) create mode 100644 test/module/retry.spec.ts diff --git a/.eslintrc b/.eslintrc index 065e48a..bc4ff3f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -21,7 +21,11 @@ "max-len": [2, { "code": 160 }], "no-unused-expressions": [2, { "allowShortCircuit": true, "allowTernary": true }], "no-return-await": [2], + "consistent-return": [2], + "default-case-last": [2], "@typescript-eslint/await-thenable": [2], - "@typescript-eslint/no-unused-vars": [2] + "@typescript-eslint/no-unused-vars": [2], + "semi": "off", + "@typescript-eslint/semi": [2] } } diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 5c81496..c804de2 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -75,11 +75,11 @@ jobs: publish_dir: ./docs/dist - name: Release if: github.event_name == 'push' && steps.env.outputs.SHOULD_RELEASE == 'true' - run: npx release-it --ci --verbose --disable-metrics --increment=${{steps.env.outputs.VERSION}} + run: npx release-it@14.6.2 --ci --verbose --disable-metrics --increment=${{steps.env.outputs.VERSION}} env: GIT_AUTHOR_NAME: Github Action GIT_AUTHOR_EMAIL: action@github.com GIT_COMMITTER_NAME: Github Action GIT_COMMITTER_EMAIL: action@github.com GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/docs/content/module/retry.md b/docs/content/module/retry.md index a4306f2..328c8b4 100644 --- a/docs/content/module/retry.md +++ b/docs/content/module/retry.md @@ -5,11 +5,20 @@ title: Mollitia Prometheus - Module - Retry ## Common Metrics -| Name | Description | Type | -|:-------------------|:-----------------------------------------------|:----------| +| Name | Description | Type | +|:-------------------|:----------------------------------------------|:----------| | `total_executions` | The amount of times the module has been used. | `Counter` | | `total_success` | The amount of times the module suceeded. | `Counter` | | `total_failures` | The amount of times the module failed. | `Counter` | | `duration_max` | The maximum duration of the module execution. | `Gauge` | | `duration_ave` | The average duration of the module execution. | `Gauge` | | `duration_min` | The minimum duration of the module execution. | `Gauge` | + +## Retry Metrics + +| Name | Description | Type | +|:---------------------------|:--------------------------------------------------------|:----------| +| `success_without_retries` | The amount of executions that succeed without retrying. | `Counter` | +| `success_with_retries` | The amount of executions that succeed after retrying. | `Counter` | +| `failures_without_retries` | The amount of executions that failed without retrying. | `Counter` | +| `failures_with_retries` | The amount of executions that failed after retrying. | `Gauge` | diff --git a/docs/package-lock.json b/docs/package-lock.json index 0ee76c5..7364dc3 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -11989,9 +11989,9 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "mollitia": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/mollitia/-/mollitia-0.0.4.tgz", - "integrity": "sha512-C7dZWwWH6MGfrg/+tmg0y21lmb7Iiorc/WA1+kStrTLBm07CqlfbOnHxaZD3XhM10wALVq50tV8fdsxxwfraBA==" + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/mollitia/-/mollitia-0.0.6.tgz", + "integrity": "sha512-SXTBD9DPwtpIalCz3ziQrHl/VL7MNeUOS5P+Dzc8x0k9ZCDP3T4st+RB6WdphWrjZLIyKwDWlsPHdf8q95c9EQ==" }, "move-concurrently": { "version": "1.0.1", diff --git a/docs/package.json b/docs/package.json index 4f14c63..39efb82 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,7 +14,7 @@ "@nuxt/typescript-runtime": "2.1.0", "@nuxtjs/pwa": "3.3.5", "core-js": "3.10.1", - "mollitia": "0.0.4", + "mollitia": "0.0.6", "nuxt": "2.15.4" }, "devDependencies": { diff --git a/jest.config.js b/jest.config.js index 59e773c..91670dd 100644 --- a/jest.config.js +++ b/jest.config.js @@ -15,6 +15,7 @@ module.exports = { setupFilesAfterEnv: [ "./test/_setup.ts" ], + forceExit: true, collectCoverage: true, collectCoverageFrom: [ "src/**/*.ts", diff --git a/package-lock.json b/package-lock.json index b2f4fef..82177d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@mollitia/prometheus", - "version": "0.0.7", + "version": "0.0.8", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1391,14 +1391,14 @@ "dev": true }, "browserslist": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz", - "integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==", + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001208", + "caniuse-lite": "^1.0.30001219", "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.712", + "electron-to-chromium": "^1.3.723", "escalade": "^3.1.1", "node-releases": "^1.1.71" } @@ -1473,9 +1473,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001208", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz", - "integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==", + "version": "1.0.30001246", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001246.tgz", + "integrity": "sha512-Tc+ff0Co/nFNbLOrziBXmMVtpt9S2c2Y+Z9Nk9Khj09J+0zR9ejvIW5qkZAErCbOrVODCx/MN+GpB5FNBs5GFA==", "dev": true }, "capture-exit": { @@ -1865,9 +1865,9 @@ } }, "electron-to-chromium": { - "version": "1.3.713", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.713.tgz", - "integrity": "sha512-HWgkyX4xTHmxcWWlvv7a87RHSINEcpKYZmDMxkUlHcY+CJcfx7xEfBHuXVsO1rzyYs1WQJ7EgDp2CoErakBIow==", + "version": "1.3.785", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.785.tgz", + "integrity": "sha512-WmCgAeURsMFiyoJ646eUaJQ7GNfvMRLXo+GamUyKVNEM4MqTAsXyC0f38JEB4N3BtbD0tlAKozGP5E2T9K3YGg==", "dev": true }, "emittery": { @@ -4109,9 +4109,9 @@ "dev": true }, "mollitia": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/mollitia/-/mollitia-0.0.4.tgz", - "integrity": "sha512-C7dZWwWH6MGfrg/+tmg0y21lmb7Iiorc/WA1+kStrTLBm07CqlfbOnHxaZD3XhM10wALVq50tV8fdsxxwfraBA==", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/mollitia/-/mollitia-0.0.6.tgz", + "integrity": "sha512-SXTBD9DPwtpIalCz3ziQrHl/VL7MNeUOS5P+Dzc8x0k9ZCDP3T4st+RB6WdphWrjZLIyKwDWlsPHdf8q95c9EQ==", "dev": true }, "ms": { @@ -4179,9 +4179,9 @@ } }, "node-releases": { - "version": "1.1.71", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", - "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", + "version": "1.1.73", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", + "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", "dev": true }, "normalize-package-data": { @@ -6228,9 +6228,9 @@ } }, "ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", + "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", "dev": true }, "xml-name-validator": { diff --git a/package.json b/package.json index c5a5afc..5c5c64f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mollitia/prometheus", - "version": "0.0.7", + "version": "0.0.8", "description": "Prometheus Mollitia Addon", "main": "dist/mollitia-prometheus.umd.js", "module": "dist/mollitia-prometheus.es5.js", @@ -34,6 +34,7 @@ "graphing" ], "scripts": { + "install:all": "npm i && cd ./docs && npm i", "dev": "run-p dev:lib dev:docs", "dev:lib": "npm run build:lib:tsc && run-p dev:lib:tsc dev:lib:rollup", "dev:lib:tsc": "tsc --watch --module commonjs", @@ -65,7 +66,7 @@ "@typescript-eslint/parser": "4.16.1", "eslint": "7.24.0", "jest": "26.6.3", - "mollitia": "0.0.4", + "mollitia": "0.0.6", "npm-run-all": "4.1.5", "rimraf": "3.0.2", "rollup": "2.45.2", diff --git a/src/circuit.ts b/src/circuit.ts index c5fa2e7..29b240d 100644 --- a/src/circuit.ts +++ b/src/circuit.ts @@ -43,4 +43,4 @@ export interface PrometheusCircuitData extends PrometheusCircuitOptions { export const attachMetrics = (circuit: Mollitia.Circuit, options: Mollitia.CircuitOptions): PrometheusCircuitMetrics => { const metrics = commonMetrics(circuit, options); return metrics; -} +}; diff --git a/src/common.ts b/src/common.ts index 38b9246..f162bda 100644 --- a/src/common.ts +++ b/src/common.ts @@ -5,7 +5,8 @@ import { PrometheusGauge } from './metrics/gauge'; type durationType = { [key: string]: number; -} +}; + export interface PrometheusCommonMetrics { [key: string]: PrometheusMetric; /** @@ -38,6 +39,15 @@ export interface PrometheusCommonMetrics { duration_count: PrometheusGauge; } +export const getMetricName = (executor: Mollitia.Circuit|Mollitia.Module) => { + if (executor.constructor.name === Mollitia.Circuit.name) { + const circuit = executor as Mollitia.Circuit; + return circuit.prometheus.perMethod ? `${circuit.prometheus.name}_${circuit.prometheus.funcName}` : executor.prometheus.name; + } else { + return executor.prometheus.name; + } +}; + export const commonMetrics = (executor: Mollitia.Circuit|Mollitia.Module, options: Mollitia.CircuitOptions|Mollitia.ModuleOptions): PrometheusCommonMetrics => { let labels = options.prometheus?.labels; if (executor.constructor.name !== Mollitia.Circuit.name) { @@ -97,17 +107,10 @@ export const commonMetrics = (executor: Mollitia.Circuit|Mollitia.Module, option } ); + // Handlers // eslint-disable-next-line @typescript-eslint/no-explicit-any executor.on('execute', (executor: Mollitia.Circuit|Mollitia.Module, promise: Promise) => { - let metricName = ''; - if (executor.constructor.name === Mollitia.Circuit.name) { - metricName = - (executor as Mollitia.Circuit).prometheus.perMethod ? - `${executor.prometheus.name}_${(executor as Mollitia.Circuit).prometheus.funcName}` : - executor.prometheus.name; - } else { - metricName = executor.prometheus.name; - } + const metricName = getMetricName(executor); totalDuration[metricName] = totalDuration[metricName] || 0; const start = Date.now(); @@ -143,4 +146,4 @@ export const commonMetrics = (executor: Mollitia.Circuit|Mollitia.Module, option duration_min, duration_count }; -} +}; diff --git a/src/index.ts b/src/index.ts index bb0f8fc..5ebf167 100644 --- a/src/index.ts +++ b/src/index.ts @@ -199,7 +199,7 @@ export const scrap = (): string => { _metrics[metric] = { help: circuit.prometheus.metrics[metric].scrapHelp(), value: circuit.prometheus.metrics[metric].scrapValues() - } + }; } } } @@ -210,8 +210,8 @@ export const scrap = (): string => { } else { _metrics[metric] = { help: module.prometheus.metrics[metric].scrapHelp(), - value: module.prometheus.metrics[metric].scrapValues() - } + value: module.prometheus.metrics[metric].scrapValues() + }; } } } diff --git a/src/metrics/counter.ts b/src/metrics/counter.ts index 3e688f6..01eb517 100644 --- a/src/metrics/counter.ts +++ b/src/metrics/counter.ts @@ -1,4 +1,4 @@ -import { PrometheusLabels, PrometheusMetric, PrometheusMetricLabelValues, PrometheusMetricType } from './index' +import { PrometheusLabels, PrometheusMetric, PrometheusMetricLabelValues, PrometheusMetricType } from './index'; interface PrometheusCounterOptions { description: string; @@ -19,7 +19,7 @@ export class PrometheusCounter implements PrometheusMetric { this.type = PrometheusMetricType.COUNTER; this.labels = options?.labels || {}; this.values = {}; - if (options?.description) { this.description = options?.description } + if (options?.description) { this.description = options?.description; } } // Public Methods public inc (value = 1, circuitName: string): number { diff --git a/src/metrics/gauge.ts b/src/metrics/gauge.ts index 9fd0eac..7ec01da 100644 --- a/src/metrics/gauge.ts +++ b/src/metrics/gauge.ts @@ -1,4 +1,4 @@ -import { PrometheusLabels, PrometheusMetric, PrometheusMetricLabelValues, PrometheusMetricType } from './index' +import { PrometheusLabels, PrometheusMetric, PrometheusMetricLabelValues, PrometheusMetricType } from './index'; interface PrometheusGaugeOptions { description: string; @@ -19,7 +19,7 @@ export class PrometheusGauge implements PrometheusMetric { this.type = PrometheusMetricType.GAUGE; this.labels = options?.labels || {}; this.values = {}; - if (options?.description) { this.description = options?.description } + if (options?.description) { this.description = options?.description; } } // Public Methods public inc (value = 1, circuitName: string): number { @@ -62,4 +62,4 @@ export class PrometheusGauge implements PrometheusMetric { this.values = {}; return res; } -} \ No newline at end of file +} diff --git a/src/module/breaker/index.ts b/src/module/breaker/index.ts index fa2a7aa..098cef6 100644 --- a/src/module/breaker/index.ts +++ b/src/module/breaker/index.ts @@ -20,4 +20,4 @@ export const attachMetrics = (module: Mollitia.Module, options: Mollitia.ModuleO // TODO total_failures_open // TODO total_failures_slow return metrics; -} +}; diff --git a/src/module/bulkhead.ts b/src/module/bulkhead.ts index e35036f..8be879b 100644 --- a/src/module/bulkhead.ts +++ b/src/module/bulkhead.ts @@ -18,4 +18,4 @@ export const attachMetrics = (module: Mollitia.Module, options: Mollitia.ModuleO // TODO max_queued // TODO total_failures_max_queue_wait return metrics; -} +}; diff --git a/src/module/cache.ts b/src/module/cache.ts index bbe3129..40d395d 100644 --- a/src/module/cache.ts +++ b/src/module/cache.ts @@ -17,4 +17,4 @@ export const attachMetrics = (module: Mollitia.Module, options: Mollitia.ModuleO // TODO total_cache_hit // TODO total_cache_hit_old return metrics; -} +}; diff --git a/src/module/fallback.ts b/src/module/fallback.ts index d2745ea..ae39263 100644 --- a/src/module/fallback.ts +++ b/src/module/fallback.ts @@ -15,4 +15,4 @@ export interface PrometheusFallbackData extends PrometheusModuleOptions { export const attachMetrics = (module: Mollitia.Module, options: Mollitia.ModuleOptions): PrometheusFallbackMetrics => { const metrics = commonMetrics(module, options); return metrics; -} +}; diff --git a/src/module/ratelimit.ts b/src/module/ratelimit.ts index 4d9f6b1..a27aac3 100644 --- a/src/module/ratelimit.ts +++ b/src/module/ratelimit.ts @@ -16,4 +16,4 @@ export const attachMetrics = (module: Mollitia.Module, options: Mollitia.ModuleO const metrics = commonMetrics(module, options); // TODO total_failures_ratelimit return metrics; -} +}; diff --git a/src/module/retry.ts b/src/module/retry.ts index 2f1ef2e..6e155b0 100644 --- a/src/module/retry.ts +++ b/src/module/retry.ts @@ -1,7 +1,8 @@ import * as Mollitia from 'mollitia'; import { PrometheusModuleOptions } from './index'; -import { commonMetrics, PrometheusCommonMetrics } from '../common'; +import { commonMetrics, getMetricName, PrometheusCommonMetrics } from '../common'; import { PrometheusMetric } from '../metrics'; +import { PrometheusCounter } from '../metrics/counter'; interface PrometheusRetryMetrics extends PrometheusCommonMetrics { [key: string]: PrometheusMetric; @@ -14,9 +15,66 @@ export interface PrometheusRetryData extends PrometheusModuleOptions { export const attachMetrics = (module: Mollitia.Module, options: Mollitia.ModuleOptions): PrometheusRetryMetrics => { const metrics = commonMetrics(module, options); - // TODO success_without_retry - // TODO success_with_retry - // TODO failures_without_retry - // TODO failures_with_retry - return metrics; -} + const labels = { + ...options.prometheus?.labels, + module: options.prometheus?.name || '' + }; + // Success Without Retries + const success_without_retries = new PrometheusCounter( + `${options.prometheus?.prefix ? `${options.prometheus?.prefix}_` : ''}success_without_retries`, + { + description: 'Success Without Retries', + labels + } + ); + // Success Without Retries + const success_with_retries = new PrometheusCounter( + `${options.prometheus?.prefix ? `${options.prometheus?.prefix}_` : ''}success_with_retries`, + { + description: 'Success With Retries', + labels + } + ); + // Success Without Retries + const failures_without_retries = new PrometheusCounter( + `${options.prometheus?.prefix ? `${options.prometheus?.prefix}_` : ''}failures_without_retries`, + { + description: 'Failures Without Retries', + labels + } + ); + // Success Without Retries + const failures_with_retries = new PrometheusCounter( + `${options.prometheus?.prefix ? `${options.prometheus?.prefix}_` : ''}failures_with_retries`, + { + description: 'Failures With Retries', + labels + } + ); + + // Handlers + module.on('success-without-retry', (circuit: Mollitia.Circuit) => { + const metricName = getMetricName(circuit); + success_without_retries.inc(1, metricName); + }); + module.on('success-with-retry', (circuit: Mollitia.Circuit) => { + const metricName = getMetricName(circuit); + success_with_retries.inc(1, metricName); + }); + module.on('failure-without-retry', (circuit: Mollitia.Circuit) => { + const metricName = getMetricName(circuit); + failures_without_retries.inc(1, metricName); + }); + module.on('failure-with-retry', (circuit: Mollitia.Circuit) => { + const metricName = getMetricName(circuit); + failures_with_retries.inc(1, metricName); + }); + + return { + ...metrics, + success_without_retries, + success_with_retries, + failures_without_retries, + failures_with_retries + }; +}; diff --git a/src/module/timeout.ts b/src/module/timeout.ts index 9f313ca..49668c0 100644 --- a/src/module/timeout.ts +++ b/src/module/timeout.ts @@ -32,4 +32,4 @@ export const attachMetrics = (module: Mollitia.Module, options: Mollitia.ModuleO ...metrics, total_failures_timeout }; -} +}; diff --git a/test/module/retry.spec.ts b/test/module/retry.spec.ts new file mode 100644 index 0000000..d2abb78 --- /dev/null +++ b/test/module/retry.spec.ts @@ -0,0 +1,150 @@ +import '../jest.d'; +import * as Mollitia from 'mollitia'; +import * as MollitiaPrometheus from '../../src/index'; + +Mollitia.use(new MollitiaPrometheus.PrometheusAddon()); + +const successAsync = jest.fn().mockImplementation((res: unknown, delay = 1) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(res); + }, delay); + }); +}); + +const failureAsync = jest.fn().mockImplementation((res: unknown, delay = 1) => { + return new Promise((resolve, reject) => { + setTimeout(() => { + reject(res); + }, delay); + }); +}); + +describe('retry.ts', () => { + afterEach(() => { + successAsync.mockClear(); + failureAsync.mockClear(); + }); + it('should support success_without_retries metric', async () => { + const retry = new Mollitia.Retry({ + attempts: 1, + prometheus: { + name: 'retry' + } + }); + const circuit = new Mollitia.Circuit({ + options: { + prometheus: { + name: 'circuit' + }, + modules: [retry] + } + }); + expect(retry.prometheus.metrics.success_without_retries).toBeDefined(); + // Classic Circuit + await circuit.fn(successAsync).execute('dummy', 250); + await circuit.fn(successAsync).execute('dummy', 100); + expect(retry.prometheus.metrics.success_without_retries.values['circuit']).toEqual(2); + // Per Method Circuit + circuit.prometheus.perMethod = true; + await circuit.fn(successAsync, 'successAsync').execute('dummy', 250); + await circuit.fn(successAsync, 'successAsync').execute('dummy', 100); + expect(retry.prometheus.metrics.success_without_retries.values['circuit_successAsync']).toEqual(2); + }); + it('should support success_with_retries metric', async () => { + let currentAttempts = 0; + const successAsyncAfterNth = jest.fn().mockImplementation((attempts, res: unknown = 'default', delay = 1) => { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (currentAttempts !== attempts) { + currentAttempts++; + reject(res); + } else { + currentAttempts = 0; + resolve(res); + } + }, delay); + }); + }); + const retry = new Mollitia.Retry({ + attempts: 1, + prometheus: { + name: 'retry' + } + }); + const circuit = new Mollitia.Circuit({ + options: { + prometheus: { + name: 'circuit' + }, + modules: [retry] + } + }); + expect(retry.prometheus.metrics.success_with_retries).toBeDefined(); + // Classic Circuit + await circuit.fn(successAsyncAfterNth).execute(1, 'dummy', 100); + await circuit.fn(successAsyncAfterNth).execute(1, 'dummy', 100); + expect(retry.prometheus.metrics.success_with_retries.values['circuit']).toEqual(2); + // Per Method Circuit + circuit.prometheus.perMethod = true; + await circuit.fn(successAsyncAfterNth, 'successAsyncAfterNth').execute(1, 'dummy', 100); + await circuit.fn(successAsyncAfterNth, 'successAsyncAfterNth').execute(1, 'dummy', 100); + expect(retry.prometheus.metrics.success_with_retries.values['circuit_successAsyncAfterNth']).toEqual(2); + }); + it('should support failures_without_retries metric', async () => { + const retry = new Mollitia.Retry({ + attempts: 0, + prometheus: { + name: 'retry' + } + }); + const circuit = new Mollitia.Circuit({ + options: { + prometheus: { + name: 'circuit' + }, + modules: [retry] + } + }); + expect(retry.prometheus.metrics.failures_without_retries).toBeDefined(); + // Classic Circuit + await expect(circuit.fn(failureAsync).execute('dummy', 100)).rejects.toEqual('dummy'); + await expect(circuit.fn(failureAsync).execute('dummy', 100)).rejects.toEqual('dummy'); + expect(retry.prometheus.metrics.failures_without_retries.values['circuit']).toEqual(2); + // Per Method Circuit + circuit.prometheus.perMethod = true; + await expect(circuit.fn(failureAsync, 'failureAsync').execute('dummy', 100)).rejects.toEqual('dummy'); + await expect(circuit.fn(failureAsync, 'failureAsync').execute('dummy', 100)).rejects.toEqual('dummy'); + expect(retry.prometheus.metrics.failures_without_retries.values['circuit_failureAsync']).toEqual(2); + }); + it('should support failures_with_retries metric', async () => { + const retry = new Mollitia.Retry({ + attempts: 2, + onRejection: (err, attempt) => { + // Only cancel rejection on second attempt + return (attempt === 1) ? false : true; + }, + prometheus: { + name: 'retry' + } + }); + const circuit = new Mollitia.Circuit({ + options: { + prometheus: { + name: 'circuit' + }, + modules: [retry] + } + }); + expect(retry.prometheus.metrics.failures_with_retries).toBeDefined(); + // Classic Circuit + await expect(circuit.fn(failureAsync).execute('dummy', 100)).rejects.toEqual('dummy'); + await expect(circuit.fn(failureAsync).execute('dummy', 100)).rejects.toEqual('dummy'); + expect(retry.prometheus.metrics.failures_with_retries.values['circuit']).toEqual(2); + // Per Method Circuit + circuit.prometheus.perMethod = true; + await expect(circuit.fn(failureAsync, 'failureAsync').execute('dummy', 100)).rejects.toEqual('dummy'); + await expect(circuit.fn(failureAsync, 'failureAsync').execute('dummy', 100)).rejects.toEqual('dummy'); + expect(retry.prometheus.metrics.failures_with_retries.values['circuit_failureAsync']).toEqual(2); + }); +});