From bf08be8a8465233c895b95e8ac0204383fd7066a Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 3 Jul 2024 19:03:23 +1000 Subject: [PATCH 01/39] Revert temp fix for failed fetch requests --- src/clients/victoriaMetrics/promAgent.ts | 58 +++++++++++------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/src/clients/victoriaMetrics/promAgent.ts b/src/clients/victoriaMetrics/promAgent.ts index 8ad2d4a6e..b4028130e 100644 --- a/src/clients/victoriaMetrics/promAgent.ts +++ b/src/clients/victoriaMetrics/promAgent.ts @@ -40,41 +40,35 @@ export class PromAgent { process.exit(1); } - try { - const response = await fetch(process.env.VM_IMPORT_PROMETHEUS_URL, { - method: 'POST', - body: metrics, - headers: { - 'Content-Type': 'text/plain', - }, - }); - - if (!response.ok) { - const errMsg = `Failed to push metrics: status=${response.status} text=${response.statusText}`; - console.error(errMsg); - - // Alert Discord while errors are less than MAX_PUSH_METRICS_ERRORS - const now = Date.now(); - this.pushMetricsErrorsTimestamps.push(now); - - while (this.pushMetricsErrorsTimestamps.length > 0) { - const timeDiff = now - this.pushMetricsErrorsTimestamps[0]; - - if (timeDiff > FORGET_ERROR_TIME_THRESHOLD) { - this.pushMetricsErrorsTimestamps.unshift(); - } else { - break; - } + const response = await fetch(process.env.VM_IMPORT_PROMETHEUS_URL, { + method: 'POST', + body: metrics, + headers: { + 'Content-Type': 'text/plain', + }, + }); + + if (!response.ok) { + const errMsg = `Failed to push metrics: status=${response.status} text=${response.statusText}`; + console.error(errMsg); + + // Alert Discord while errors are less than MAX_PUSH_METRICS_ERRORS + const now = Date.now(); + this.pushMetricsErrorsTimestamps.push(now); + + while (this.pushMetricsErrorsTimestamps.length > 0) { + const timeDiff = now - this.pushMetricsErrorsTimestamps[0]; + + if (timeDiff > FORGET_ERROR_TIME_THRESHOLD) { + this.pushMetricsErrorsTimestamps.unshift(); + } else { + break; } + } - if ( - this.pushMetricsErrorsTimestamps.length <= MAX_PUSH_METRICS_ERRORS - ) { - sendToDiscordAdmins(errMsg); - } + if (this.pushMetricsErrorsTimestamps.length <= MAX_PUSH_METRICS_ERRORS) { + sendToDiscordAdmins(errMsg); } - } catch (e) { - return; } } } From 8b10620f71bea6cee76d0c81d21c4fcb48c55296 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 4 Jul 2024 15:57:28 +1000 Subject: [PATCH 02/39] Added labelOptions checks. Also on counter config, increment all combinations with 0 --- .../victoriaMetrics/promMetricCounter.ts | 55 +++++++++++++++++++ src/metrics/gameMetrics.ts | 3 + 2 files changed, 58 insertions(+) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index 7a7ff0b08..222817dcb 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -5,17 +5,72 @@ interface CounterConfig { name: string; help: string; labelNames?: string[]; + labelOptions?: Record; } export class PromMetricCounter { private counter: Counter; private labelNames: string[]; + private labelOptions: Record; constructor(counterConfig: CounterConfig) { promAgent.registerMetric(counterConfig.name); this.counter = new promClient.Counter(counterConfig); this.labelNames = counterConfig.labelNames; + this.labelOptions = counterConfig.labelOptions; + + // Check either both or neither labelNames and labelOptions are declared + if ( + (counterConfig.labelNames && !counterConfig.labelOptions) || + (!counterConfig.labelNames && counterConfig.labelOptions) + ) { + throw new Error( + `Error: Counter Metric "${counterConfig.name}" must have both labelNames and labelOptions configured if either is declared.`, + ); + } + + // Validate labelNames and labelOptions if both provided + if (counterConfig.labelNames && counterConfig.labelOptions) { + const labelNamesFromOptions = Object.keys(counterConfig.labelOptions); + const invalidLabelNamesFromOptions = labelNamesFromOptions.filter( + (labelName) => !counterConfig.labelNames.includes(labelName), + ); + + if (invalidLabelNamesFromOptions.length > 0) { + throw new Error( + `Error: labelNames cannot be used if undeclared: "${invalidLabelNamesFromOptions}".`, + ); + } + + const invalidConfigLabelNames = counterConfig.labelNames.filter( + (labelName) => !labelNamesFromOptions.includes(labelName), + ); + if (invalidConfigLabelNames.length > 0) { + throw new Error( + `Error: labelOptions are not configured for labelNames="${invalidConfigLabelNames}".`, + ); + } + + let labelCombinations = [{}]; + + labelNamesFromOptions.forEach((labelName) => { + // Get current label options + const options = this.labelOptions[labelName]; + + // Update combinations with new options + labelCombinations = labelCombinations.flatMap((combination) => + options.map((option) => ({ + ...combination, + [labelName]: option, + })), + ); + }); + + labelCombinations.forEach((combination) => { + this.counter.inc(combination, 0); + }); + } } public inc(num: number, labels?: Record) { diff --git a/src/metrics/gameMetrics.ts b/src/metrics/gameMetrics.ts index 5640aefa3..f302a7969 100644 --- a/src/metrics/gameMetrics.ts +++ b/src/metrics/gameMetrics.ts @@ -4,4 +4,7 @@ export const gamesPlayedMetric = new PromMetricCounter({ name: 'games_played_total', help: 'test', labelNames: ['status'], + labelOptions: { + status: ['finished', 'voided'], + }, }); From 899f447a2b7c535da64bc48e1cfa27be768f32ab Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 4 Jul 2024 16:48:34 +1000 Subject: [PATCH 03/39] Added labelCombination validations on counter config and inc methods --- .../victoriaMetrics/promMetricCounter.ts | 118 +++++++++++------- 1 file changed, 71 insertions(+), 47 deletions(-) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index 222817dcb..0cd8d9ad5 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -1,7 +1,7 @@ import promClient, { Counter } from 'prom-client'; import { promAgent } from './promAgent'; -interface CounterConfig { +export interface CounterConfig { name: string; help: string; labelNames?: string[]; @@ -9,17 +9,12 @@ interface CounterConfig { } export class PromMetricCounter { + private readonly labelCombinations: Record[]; private counter: Counter; - private labelNames: string[]; - private labelOptions: Record; constructor(counterConfig: CounterConfig) { promAgent.registerMetric(counterConfig.name); - this.counter = new promClient.Counter(counterConfig); - this.labelNames = counterConfig.labelNames; - this.labelOptions = counterConfig.labelOptions; - // Check either both or neither labelNames and labelOptions are declared if ( (counterConfig.labelNames && !counterConfig.labelOptions) || @@ -32,63 +27,92 @@ export class PromMetricCounter { // Validate labelNames and labelOptions if both provided if (counterConfig.labelNames && counterConfig.labelOptions) { - const labelNamesFromOptions = Object.keys(counterConfig.labelOptions); - const invalidLabelNamesFromOptions = labelNamesFromOptions.filter( - (labelName) => !counterConfig.labelNames.includes(labelName), + this.validateLabelNamesAndOptions( + counterConfig.labelNames, + counterConfig.labelOptions, ); - - if (invalidLabelNamesFromOptions.length > 0) { - throw new Error( - `Error: labelNames cannot be used if undeclared: "${invalidLabelNamesFromOptions}".`, - ); - } - - const invalidConfigLabelNames = counterConfig.labelNames.filter( - (labelName) => !labelNamesFromOptions.includes(labelName), + this.labelCombinations = this.generateLabelCombinations( + counterConfig.labelOptions, ); - if (invalidConfigLabelNames.length > 0) { - throw new Error( - `Error: labelOptions are not configured for labelNames="${invalidConfigLabelNames}".`, - ); - } - - let labelCombinations = [{}]; - - labelNamesFromOptions.forEach((labelName) => { - // Get current label options - const options = this.labelOptions[labelName]; - - // Update combinations with new options - labelCombinations = labelCombinations.flatMap((combination) => - options.map((option) => ({ - ...combination, - [labelName]: option, - })), - ); - }); + } + + this.counter = new promClient.Counter(counterConfig); - labelCombinations.forEach((combination) => { + if (this.labelCombinations) { + this.labelCombinations.forEach((combination) => { this.counter.inc(combination, 0); }); } } + private validateLabelNamesAndOptions( + labelNames: string[], + labelOptions: Record, + ) { + const labelNamesFromOptions = Object.keys(labelOptions); + const invalidLabelNamesFromOptions = labelNamesFromOptions.filter( + (labelName) => !labelNames.includes(labelName), + ); + + if (invalidLabelNamesFromOptions.length > 0) { + throw new Error( + `Error: labelNames cannot be used if undeclared: "${invalidLabelNamesFromOptions}".`, + ); + } + + const invalidLabelNames = labelNames.filter( + (labelName) => !labelNamesFromOptions.includes(labelName), + ); + if (invalidLabelNames.length > 0) { + throw new Error( + `Error: labelOptions are not configured for labelNames="${invalidLabelNames}".`, + ); + } + } + + private generateLabelCombinations( + labelOptions?: Record, + ): Record[] { + let labelCombinations = [{}]; + + Object.keys(labelOptions).forEach((labelName) => { + // Get current label options + const options = labelOptions[labelName]; + + // Update combinations with new options + labelCombinations = labelCombinations.flatMap((combination) => + options.map((option) => ({ + ...combination, + [labelName]: option, + })), + ); + }); + + return labelCombinations; + } + public inc(num: number, labels?: Record) { if (labels) { - this.validateLabels(labels); + this.validateLabelCombination(labels); this.counter.inc(labels, num); } else { this.counter.inc(num); } } - private validateLabels(labels: Record) { - const invalidLabels = Object.keys(labels).filter( - (label) => !this.labelNames.includes(label), - ); + private validateLabelCombination(labelCombination: Record) { + const result = this.labelCombinations.some((item) => { + return Object.keys(labelCombination).every( + (key) => item[key] === labelCombination[key], + ); + }); - if (invalidLabels.length > 0) { - throw new Error(`Invalid labels provided: ${invalidLabels.join(', ')}.`); + if (!result) { + throw new Error( + `Invalid labelCombination provided: "${JSON.stringify( + labelCombination, + )}".`, + ); } } } From 58c4d669cae3cff88078400661eb14846e8bfa6c Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 4 Jul 2024 16:50:22 +1000 Subject: [PATCH 04/39] Updated custom avatar metric labelOptions --- src/metrics/miscellaneousMetrics.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/metrics/miscellaneousMetrics.ts b/src/metrics/miscellaneousMetrics.ts index 9efcfd2c8..6dd0c6d00 100644 --- a/src/metrics/miscellaneousMetrics.ts +++ b/src/metrics/miscellaneousMetrics.ts @@ -19,6 +19,9 @@ export const avatarSubmissionsMetric = new PromMetricCounter({ name: `custom_avatar_submissions_total`, help: `Total number of custom avatars submitted/rejected/approved.`, labelNames: ['status'], + labelOptions: { + status: ['approved', 'rejected', 'submitted'], + }, }); export const passwordResetRequestsMetric = new PromMetricCounter({ From 296b700b155635e774d6606ad7c85128d4b99551 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 4 Jul 2024 17:28:08 +1000 Subject: [PATCH 05/39] Minor update to error messages --- src/clients/victoriaMetrics/promMetricCounter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index 0cd8d9ad5..7e3f5bd7d 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -56,7 +56,7 @@ export class PromMetricCounter { if (invalidLabelNamesFromOptions.length > 0) { throw new Error( - `Error: labelNames cannot be used if undeclared: "${invalidLabelNamesFromOptions}".`, + `Error: Undeclared labelNames: "${invalidLabelNamesFromOptions}".`, ); } From 8500dcd8f07a33412a71db68813982aee2619d25 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 4 Jul 2024 17:37:27 +1000 Subject: [PATCH 06/39] Updated gauge to also validate labels --- .../victoriaMetrics/metricFunctions.ts | 45 ++++++++++++++++ .../victoriaMetrics/promMetricCounter.ts | 54 +++---------------- .../victoriaMetrics/promMetricGauge.ts | 27 ++++++++++ 3 files changed, 78 insertions(+), 48 deletions(-) create mode 100644 src/clients/victoriaMetrics/metricFunctions.ts diff --git a/src/clients/victoriaMetrics/metricFunctions.ts b/src/clients/victoriaMetrics/metricFunctions.ts new file mode 100644 index 000000000..aae73a056 --- /dev/null +++ b/src/clients/victoriaMetrics/metricFunctions.ts @@ -0,0 +1,45 @@ +export function validateLabelNamesAndOptions( + labelNames: string[], + labelOptions: Record, +) { + const labelNamesFromOptions = Object.keys(labelOptions); + const invalidLabelNamesFromOptions = labelNamesFromOptions.filter( + (labelName) => !labelNames.includes(labelName), + ); + + if (invalidLabelNamesFromOptions.length > 0) { + throw new Error( + `Error: Undeclared labelNames: "${invalidLabelNamesFromOptions}".`, + ); + } + + const invalidLabelNames = labelNames.filter( + (labelName) => !labelNamesFromOptions.includes(labelName), + ); + if (invalidLabelNames.length > 0) { + throw new Error( + `Error: labelOptions are not configured for labelNames="${invalidLabelNames}".`, + ); + } +} + +export function generateLabelCombinations( + labelOptions?: Record, +): Record[] { + let labelCombinations = [{}]; + + Object.keys(labelOptions).forEach((labelName) => { + // Get current label options + const options = labelOptions[labelName]; + + // Update combinations with new options + labelCombinations = labelCombinations.flatMap((combination) => + options.map((option) => ({ + ...combination, + [labelName]: option, + })), + ); + }); + + return labelCombinations; +} diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index 7e3f5bd7d..bd476e9b0 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -1,5 +1,9 @@ import promClient, { Counter } from 'prom-client'; import { promAgent } from './promAgent'; +import { + generateLabelCombinations, + validateLabelNamesAndOptions, +} from './metricFunctions'; export interface CounterConfig { name: string; @@ -27,11 +31,11 @@ export class PromMetricCounter { // Validate labelNames and labelOptions if both provided if (counterConfig.labelNames && counterConfig.labelOptions) { - this.validateLabelNamesAndOptions( + validateLabelNamesAndOptions( counterConfig.labelNames, counterConfig.labelOptions, ); - this.labelCombinations = this.generateLabelCombinations( + this.labelCombinations = generateLabelCombinations( counterConfig.labelOptions, ); } @@ -45,52 +49,6 @@ export class PromMetricCounter { } } - private validateLabelNamesAndOptions( - labelNames: string[], - labelOptions: Record, - ) { - const labelNamesFromOptions = Object.keys(labelOptions); - const invalidLabelNamesFromOptions = labelNamesFromOptions.filter( - (labelName) => !labelNames.includes(labelName), - ); - - if (invalidLabelNamesFromOptions.length > 0) { - throw new Error( - `Error: Undeclared labelNames: "${invalidLabelNamesFromOptions}".`, - ); - } - - const invalidLabelNames = labelNames.filter( - (labelName) => !labelNamesFromOptions.includes(labelName), - ); - if (invalidLabelNames.length > 0) { - throw new Error( - `Error: labelOptions are not configured for labelNames="${invalidLabelNames}".`, - ); - } - } - - private generateLabelCombinations( - labelOptions?: Record, - ): Record[] { - let labelCombinations = [{}]; - - Object.keys(labelOptions).forEach((labelName) => { - // Get current label options - const options = labelOptions[labelName]; - - // Update combinations with new options - labelCombinations = labelCombinations.flatMap((combination) => - options.map((option) => ({ - ...combination, - [labelName]: option, - })), - ); - }); - - return labelCombinations; - } - public inc(num: number, labels?: Record) { if (labels) { this.validateLabelCombination(labels); diff --git a/src/clients/victoriaMetrics/promMetricGauge.ts b/src/clients/victoriaMetrics/promMetricGauge.ts index 99ecfabc6..89549d218 100644 --- a/src/clients/victoriaMetrics/promMetricGauge.ts +++ b/src/clients/victoriaMetrics/promMetricGauge.ts @@ -1,19 +1,46 @@ import promClient, { Gauge } from 'prom-client'; import { promAgent } from './promAgent'; +import { + generateLabelCombinations, + validateLabelNamesAndOptions, +} from './metricFunctions'; interface GaugeConfig { name: string; help: string; labelNames?: string[]; + labelOptions?: Record; collect?: () => void; // Refer to prom-client docs on how this should be used. } export class PromMetricGauge { + private readonly labelCombinations: Record[]; private gauge: Gauge; constructor(gaugeConfig: GaugeConfig) { promAgent.registerMetric(gaugeConfig.name); + // Check either both or neither labelNames and labelOptions are declared + if ( + (gaugeConfig.labelNames && !gaugeConfig.labelOptions) || + (!gaugeConfig.labelNames && gaugeConfig.labelOptions) + ) { + throw new Error( + `Error: Counter Metric "${gaugeConfig.name}" must have both labelNames and labelOptions configured if either is declared.`, + ); + } + + // Validate labelNames and labelOptions if both provided + if (gaugeConfig.labelNames && gaugeConfig.labelOptions) { + validateLabelNamesAndOptions( + gaugeConfig.labelNames, + gaugeConfig.labelOptions, + ); + this.labelCombinations = generateLabelCombinations( + gaugeConfig.labelOptions, + ); + } + this.gauge = new promClient.Gauge(gaugeConfig); } } From 47e8623a494ff6f402e8229997b86cd21f3d3790 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 5 Jul 2024 15:25:17 +1000 Subject: [PATCH 07/39] Removed labelNames from counterConfig in favor of labelOptions --- .../victoriaMetrics/promMetricCounter.ts | 37 +++++++------------ src/metrics/gameMetrics.ts | 3 +- src/metrics/miscellaneousMetrics.ts | 1 - 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index bd476e9b0..495fa46ab 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -1,17 +1,18 @@ import promClient, { Counter } from 'prom-client'; import { promAgent } from './promAgent'; -import { - generateLabelCombinations, - validateLabelNamesAndOptions, -} from './metricFunctions'; export interface CounterConfig { name: string; help: string; - labelNames?: string[]; labelOptions?: Record; } +export interface PromClientCounterConfig { + name: string; + help: string; + labelNames?: string[]; +} + export class PromMetricCounter { private readonly labelCombinations: Record[]; private counter: Counter; @@ -19,27 +20,17 @@ export class PromMetricCounter { constructor(counterConfig: CounterConfig) { promAgent.registerMetric(counterConfig.name); - // Check either both or neither labelNames and labelOptions are declared - if ( - (counterConfig.labelNames && !counterConfig.labelOptions) || - (!counterConfig.labelNames && counterConfig.labelOptions) - ) { - throw new Error( - `Error: Counter Metric "${counterConfig.name}" must have both labelNames and labelOptions configured if either is declared.`, - ); - } + const config: PromClientCounterConfig = { + name: counterConfig.name, + help: counterConfig.help, + }; - // Validate labelNames and labelOptions if both provided - if (counterConfig.labelNames && counterConfig.labelOptions) { - validateLabelNamesAndOptions( - counterConfig.labelNames, - counterConfig.labelOptions, - ); - this.labelCombinations = generateLabelCombinations( - counterConfig.labelOptions, - ); + if (counterConfig.labelOptions) { + config.labelNames = Object.keys(counterConfig.labelOptions); } + console.log(config); + this.counter = new promClient.Counter(counterConfig); if (this.labelCombinations) { diff --git a/src/metrics/gameMetrics.ts b/src/metrics/gameMetrics.ts index f302a7969..5555cf7aa 100644 --- a/src/metrics/gameMetrics.ts +++ b/src/metrics/gameMetrics.ts @@ -2,8 +2,7 @@ import { PromMetricCounter } from '../clients/victoriaMetrics/promMetricCounter' export const gamesPlayedMetric = new PromMetricCounter({ name: 'games_played_total', - help: 'test', - labelNames: ['status'], + help: 'Total number of games played.', labelOptions: { status: ['finished', 'voided'], }, diff --git a/src/metrics/miscellaneousMetrics.ts b/src/metrics/miscellaneousMetrics.ts index 6dd0c6d00..370f199da 100644 --- a/src/metrics/miscellaneousMetrics.ts +++ b/src/metrics/miscellaneousMetrics.ts @@ -18,7 +18,6 @@ export const uniqueLoginsMetric = new PromMetricCounter({ export const avatarSubmissionsMetric = new PromMetricCounter({ name: `custom_avatar_submissions_total`, help: `Total number of custom avatars submitted/rejected/approved.`, - labelNames: ['status'], labelOptions: { status: ['approved', 'rejected', 'submitted'], }, From a48f3787431a91d1cd6918a7909cf7afdbd237a8 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 5 Jul 2024 15:55:12 +1000 Subject: [PATCH 08/39] Added checks for invalid configurations --- src/clients/victoriaMetrics/promMetricCounter.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index 495fa46ab..b0169355d 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -26,6 +26,17 @@ export class PromMetricCounter { }; if (counterConfig.labelOptions) { + const labelNames = Object.keys(counterConfig.labelOptions); + const invalidLabelNames = labelNames.filter((labelName) => { + return counterConfig.labelOptions[labelName].length === 0; + }); + + if (invalidLabelNames.length > 0) { + throw new Error( + `LabelOptions undefined for labelNames: "${invalidLabelNames}".`, + ); + } + config.labelNames = Object.keys(counterConfig.labelOptions); } From 2740fa2813d2a92eff6920df5ee82cdfc4def441 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 5 Jul 2024 16:15:51 +1000 Subject: [PATCH 09/39] Minor update --- .../victoriaMetrics/promMetricCounter.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index b0169355d..bbfd98fda 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -1,5 +1,6 @@ import promClient, { Counter } from 'prom-client'; import { promAgent } from './promAgent'; +import { generateLabelCombinations } from './metricFunctions'; export interface CounterConfig { name: string; @@ -20,7 +21,7 @@ export class PromMetricCounter { constructor(counterConfig: CounterConfig) { promAgent.registerMetric(counterConfig.name); - const config: PromClientCounterConfig = { + const promClientCounterConfig: PromClientCounterConfig = { name: counterConfig.name, help: counterConfig.help, }; @@ -37,12 +38,18 @@ export class PromMetricCounter { ); } - config.labelNames = Object.keys(counterConfig.labelOptions); + promClientCounterConfig.labelNames = Object.keys( + counterConfig.labelOptions, + ); } - console.log(config); + if (counterConfig.labelOptions) { + this.labelCombinations = generateLabelCombinations( + counterConfig.labelOptions, + ); + } - this.counter = new promClient.Counter(counterConfig); + this.counter = new promClient.Counter(promClientCounterConfig); if (this.labelCombinations) { this.labelCombinations.forEach((combination) => { @@ -53,7 +60,7 @@ export class PromMetricCounter { public inc(num: number, labels?: Record) { if (labels) { - this.validateLabelCombination(labels); + // this.validateLabelCombination(labels); this.counter.inc(labels, num); } else { this.counter.inc(num); From 71a0562a4af9b8d713e5f49cdb47d5b938e32d8b Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 5 Jul 2024 16:25:27 +1000 Subject: [PATCH 10/39] Updating labelOptions to be a Set --- src/clients/victoriaMetrics/promMetricCounter.ts | 14 +++++++------- src/metrics/gameMetrics.ts | 2 +- src/metrics/miscellaneousMetrics.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index bbfd98fda..3c38d7439 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -1,11 +1,10 @@ import promClient, { Counter } from 'prom-client'; import { promAgent } from './promAgent'; -import { generateLabelCombinations } from './metricFunctions'; export interface CounterConfig { name: string; help: string; - labelOptions?: Record; + labelOptions?: Record>; } export interface PromClientCounterConfig { @@ -29,7 +28,7 @@ export class PromMetricCounter { if (counterConfig.labelOptions) { const labelNames = Object.keys(counterConfig.labelOptions); const invalidLabelNames = labelNames.filter((labelName) => { - return counterConfig.labelOptions[labelName].length === 0; + return counterConfig.labelOptions[labelName].size === 0; }); if (invalidLabelNames.length > 0) { @@ -44,9 +43,10 @@ export class PromMetricCounter { } if (counterConfig.labelOptions) { - this.labelCombinations = generateLabelCombinations( - counterConfig.labelOptions, - ); + // TODO-kev: Needs to be fixed + // this.labelCombinations = generateLabelCombinations( + // counterConfig.labelOptions, + // ); } this.counter = new promClient.Counter(promClientCounterConfig); @@ -60,7 +60,7 @@ export class PromMetricCounter { public inc(num: number, labels?: Record) { if (labels) { - // this.validateLabelCombination(labels); + this.validateLabelCombination(labels); this.counter.inc(labels, num); } else { this.counter.inc(num); diff --git a/src/metrics/gameMetrics.ts b/src/metrics/gameMetrics.ts index 5555cf7aa..52df20202 100644 --- a/src/metrics/gameMetrics.ts +++ b/src/metrics/gameMetrics.ts @@ -4,6 +4,6 @@ export const gamesPlayedMetric = new PromMetricCounter({ name: 'games_played_total', help: 'Total number of games played.', labelOptions: { - status: ['finished', 'voided'], + status: new Set(['finished', 'voided', 'finished']), }, }); diff --git a/src/metrics/miscellaneousMetrics.ts b/src/metrics/miscellaneousMetrics.ts index 370f199da..edd85f4e9 100644 --- a/src/metrics/miscellaneousMetrics.ts +++ b/src/metrics/miscellaneousMetrics.ts @@ -19,7 +19,7 @@ export const avatarSubmissionsMetric = new PromMetricCounter({ name: `custom_avatar_submissions_total`, help: `Total number of custom avatars submitted/rejected/approved.`, labelOptions: { - status: ['approved', 'rejected', 'submitted'], + status: new Set(['approved', 'rejected', 'submitted']), }, }); From 16e902da4e9a1a33aac0880de04c8ec6ea42ad03 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 5 Jul 2024 16:31:20 +1000 Subject: [PATCH 11/39] Updated generateMetricCombinations --- .../victoriaMetrics/metricFunctions.ts | 6 +++--- .../victoriaMetrics/promMetricCounter.ts | 8 ++++---- .../victoriaMetrics/promMetricGauge.ts | 19 ++++++++++--------- src/metrics/gameMetrics.ts | 2 +- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/clients/victoriaMetrics/metricFunctions.ts b/src/clients/victoriaMetrics/metricFunctions.ts index aae73a056..f487fe28e 100644 --- a/src/clients/victoriaMetrics/metricFunctions.ts +++ b/src/clients/victoriaMetrics/metricFunctions.ts @@ -24,13 +24,13 @@ export function validateLabelNamesAndOptions( } export function generateLabelCombinations( - labelOptions?: Record, + labelOptions?: Record>, ): Record[] { - let labelCombinations = [{}]; + let labelCombinations: Record[] = [{}]; Object.keys(labelOptions).forEach((labelName) => { // Get current label options - const options = labelOptions[labelName]; + const options = Array.from(labelOptions[labelName]); // Update combinations with new options labelCombinations = labelCombinations.flatMap((combination) => diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index 3c38d7439..219bfc42b 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -1,5 +1,6 @@ import promClient, { Counter } from 'prom-client'; import { promAgent } from './promAgent'; +import { generateLabelCombinations } from './metricFunctions'; export interface CounterConfig { name: string; @@ -43,10 +44,9 @@ export class PromMetricCounter { } if (counterConfig.labelOptions) { - // TODO-kev: Needs to be fixed - // this.labelCombinations = generateLabelCombinations( - // counterConfig.labelOptions, - // ); + this.labelCombinations = generateLabelCombinations( + counterConfig.labelOptions, + ); } this.counter = new promClient.Counter(promClientCounterConfig); diff --git a/src/clients/victoriaMetrics/promMetricGauge.ts b/src/clients/victoriaMetrics/promMetricGauge.ts index 89549d218..986c5a96f 100644 --- a/src/clients/victoriaMetrics/promMetricGauge.ts +++ b/src/clients/victoriaMetrics/promMetricGauge.ts @@ -31,15 +31,16 @@ export class PromMetricGauge { } // Validate labelNames and labelOptions if both provided - if (gaugeConfig.labelNames && gaugeConfig.labelOptions) { - validateLabelNamesAndOptions( - gaugeConfig.labelNames, - gaugeConfig.labelOptions, - ); - this.labelCombinations = generateLabelCombinations( - gaugeConfig.labelOptions, - ); - } + // TODO-kev: Needs to be fixed + // if (gaugeConfig.labelNames && gaugeConfig.labelOptions) { + // validateLabelNamesAndOptions( + // gaugeConfig.labelNames, + // gaugeConfig.labelOptions, + // ); + // this.labelCombinations = generateLabelCombinations( + // gaugeConfig.labelOptions, + // ); + // } this.gauge = new promClient.Gauge(gaugeConfig); } diff --git a/src/metrics/gameMetrics.ts b/src/metrics/gameMetrics.ts index 52df20202..0beada4c6 100644 --- a/src/metrics/gameMetrics.ts +++ b/src/metrics/gameMetrics.ts @@ -4,6 +4,6 @@ export const gamesPlayedMetric = new PromMetricCounter({ name: 'games_played_total', help: 'Total number of games played.', labelOptions: { - status: new Set(['finished', 'voided', 'finished']), + status: new Set(['finished', 'voided']), }, }); From aaff0dc09edab52d42a8146a2ae865bea9e8c4a6 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 5 Jul 2024 16:47:30 +1000 Subject: [PATCH 12/39] Updated valid label combination check for counter inc method --- .../victoriaMetrics/promMetricCounter.ts | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index 219bfc42b..3ee04fbf3 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -16,6 +16,7 @@ export interface PromClientCounterConfig { export class PromMetricCounter { private readonly labelCombinations: Record[]; + private readonly labelOptions: Record>; private counter: Counter; constructor(counterConfig: CounterConfig) { @@ -49,6 +50,7 @@ export class PromMetricCounter { ); } + this.labelOptions = counterConfig.labelOptions; this.counter = new promClient.Counter(promClientCounterConfig); if (this.labelCombinations) { @@ -60,26 +62,38 @@ export class PromMetricCounter { public inc(num: number, labels?: Record) { if (labels) { - this.validateLabelCombination(labels); + if (!this.isValidLabelCombination(labels)) { + throw new Error(`Invalid labels provided: ${JSON.stringify(labels)}`); + } this.counter.inc(labels, num); } else { this.counter.inc(num); } } - private validateLabelCombination(labelCombination: Record) { - const result = this.labelCombinations.some((item) => { - return Object.keys(labelCombination).every( - (key) => item[key] === labelCombination[key], - ); - }); + private isValidLabelCombination( + labelCombination: Record, + ): boolean { + // Key size does not match + if ( + Object.keys(labelCombination).length !== + Object.keys(this.labelOptions).length + ) { + return false; + } - if (!result) { - throw new Error( - `Invalid labelCombination provided: "${JSON.stringify( - labelCombination, - )}".`, - ); + for (const labelName in labelCombination) { + // label name is not in config + if (!this.labelOptions[labelName]) { + return false; + } + + // Option not present under the label name + if (!this.labelOptions[labelName].has(labelCombination[labelName])) { + return false; + } } + + return true; } } From 03ac4101f0cfb14f30fb01d140b26c8d69f474f3 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 5 Jul 2024 16:50:05 +1000 Subject: [PATCH 13/39] Removed labelCombinations attribute from PromMetricCounter class --- src/clients/victoriaMetrics/promMetricCounter.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index 3ee04fbf3..f4b681aae 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -15,7 +15,6 @@ export interface PromClientCounterConfig { } export class PromMetricCounter { - private readonly labelCombinations: Record[]; private readonly labelOptions: Record>; private counter: Counter; @@ -44,17 +43,16 @@ export class PromMetricCounter { ); } + this.labelOptions = counterConfig.labelOptions; + this.counter = new promClient.Counter(promClientCounterConfig); + + // Increment each labelCombination by 0 to initiate metric if (counterConfig.labelOptions) { - this.labelCombinations = generateLabelCombinations( + const labelCombinations = generateLabelCombinations( counterConfig.labelOptions, ); - } - - this.labelOptions = counterConfig.labelOptions; - this.counter = new promClient.Counter(promClientCounterConfig); - if (this.labelCombinations) { - this.labelCombinations.forEach((combination) => { + labelCombinations.forEach((combination) => { this.counter.inc(combination, 0); }); } From 2c1d2e61e6b4d9043df026ee1b483e56adb062f4 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 5 Jul 2024 16:55:39 +1000 Subject: [PATCH 14/39] Removed unused function --- .../victoriaMetrics/metricFunctions.ts | 25 ------------------- .../victoriaMetrics/promMetricGauge.ts | 5 +--- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/src/clients/victoriaMetrics/metricFunctions.ts b/src/clients/victoriaMetrics/metricFunctions.ts index f487fe28e..08c25d973 100644 --- a/src/clients/victoriaMetrics/metricFunctions.ts +++ b/src/clients/victoriaMetrics/metricFunctions.ts @@ -1,28 +1,3 @@ -export function validateLabelNamesAndOptions( - labelNames: string[], - labelOptions: Record, -) { - const labelNamesFromOptions = Object.keys(labelOptions); - const invalidLabelNamesFromOptions = labelNamesFromOptions.filter( - (labelName) => !labelNames.includes(labelName), - ); - - if (invalidLabelNamesFromOptions.length > 0) { - throw new Error( - `Error: Undeclared labelNames: "${invalidLabelNamesFromOptions}".`, - ); - } - - const invalidLabelNames = labelNames.filter( - (labelName) => !labelNamesFromOptions.includes(labelName), - ); - if (invalidLabelNames.length > 0) { - throw new Error( - `Error: labelOptions are not configured for labelNames="${invalidLabelNames}".`, - ); - } -} - export function generateLabelCombinations( labelOptions?: Record>, ): Record[] { diff --git a/src/clients/victoriaMetrics/promMetricGauge.ts b/src/clients/victoriaMetrics/promMetricGauge.ts index 986c5a96f..ae99cee32 100644 --- a/src/clients/victoriaMetrics/promMetricGauge.ts +++ b/src/clients/victoriaMetrics/promMetricGauge.ts @@ -1,9 +1,6 @@ import promClient, { Gauge } from 'prom-client'; import { promAgent } from './promAgent'; -import { - generateLabelCombinations, - validateLabelNamesAndOptions, -} from './metricFunctions'; +import { generateLabelCombinations } from './metricFunctions'; interface GaugeConfig { name: string; From e5fe498ea6b5df60098aaeffda77e7ced30b1978 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 5 Jul 2024 17:04:25 +1000 Subject: [PATCH 15/39] Minor updaet --- src/clients/victoriaMetrics/promMetricCounter.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index f4b681aae..445c5aa73 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -27,15 +27,12 @@ export class PromMetricCounter { }; if (counterConfig.labelOptions) { - const labelNames = Object.keys(counterConfig.labelOptions); - const invalidLabelNames = labelNames.filter((labelName) => { - return counterConfig.labelOptions[labelName].size === 0; - }); - - if (invalidLabelNames.length > 0) { - throw new Error( - `LabelOptions undefined for labelNames: "${invalidLabelNames}".`, - ); + for (const labelName in counterConfig.labelOptions) { + if (counterConfig.labelOptions[labelName].size === 0) { + throw new Error( + `LabelOptions undefined for labelName: "${labelName}".`, + ); + } } promClientCounterConfig.labelNames = Object.keys( From cf1ab190d0ecfa45b2ddf1128d623132db02bf28 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 6 Jul 2024 17:54:31 +1000 Subject: [PATCH 16/39] Added test cases for generateLabelCombinations. Updated function for recursive solution --- .../victoriaMetrics/metricFunctions.ts | 49 ++++++++++++----- .../tests/metricFunctions.test.ts | 54 +++++++++++++++++++ 2 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 src/clients/victoriaMetrics/tests/metricFunctions.test.ts diff --git a/src/clients/victoriaMetrics/metricFunctions.ts b/src/clients/victoriaMetrics/metricFunctions.ts index 08c25d973..66d43372c 100644 --- a/src/clients/victoriaMetrics/metricFunctions.ts +++ b/src/clients/victoriaMetrics/metricFunctions.ts @@ -1,20 +1,43 @@ export function generateLabelCombinations( - labelOptions?: Record>, + labelOptions: Record>, ): Record[] { - let labelCombinations: Record[] = [{}]; + const labelNames = Object.keys(labelOptions); + const labelCombinations: Record[] = []; - Object.keys(labelOptions).forEach((labelName) => { - // Get current label options - const options = Array.from(labelOptions[labelName]); + function buildCombinations( + currentCombination: Record, + index: number, + ) { + if (index === labelNames.length) { + labelCombinations.push(currentCombination); + return; + } - // Update combinations with new options - labelCombinations = labelCombinations.flatMap((combination) => - options.map((option) => ({ - ...combination, - [labelName]: option, - })), - ); - }); + const key = labelNames[index]; + const options = Array.from(labelOptions[key]); + + options.forEach((option) => { + buildCombinations({ ...currentCombination, [key]: option }, index + 1); + }); + } + + buildCombinations({}, 0); return labelCombinations; + + // TODO-kev: Use above recursive solution or below compact one + // Object.keys(labelOptions).forEach((labelName) => { + // // Get current label options + // const options = Array.from(labelOptions[labelName]); + // + // // Update combinations with new options + // labelCombinations = labelCombinations.flatMap((combination) => + // options.map((option) => ({ + // ...combination, + // [labelName]: option, + // })), + // ); + // }); + // + // return labelCombinations; } diff --git a/src/clients/victoriaMetrics/tests/metricFunctions.test.ts b/src/clients/victoriaMetrics/tests/metricFunctions.test.ts new file mode 100644 index 000000000..daad43e14 --- /dev/null +++ b/src/clients/victoriaMetrics/tests/metricFunctions.test.ts @@ -0,0 +1,54 @@ +import { generateLabelCombinations } from '../metricFunctions'; + +describe('MetricFunctions', () => { + describe('Generate label combinations', () => { + it('returns an empty array for empty labelOptions.', () => { + const labelOptions = {}; + expect(generateLabelCombinations(labelOptions)).toEqual([{}]); + }); + + it('generates label combinations for a single label.', () => { + const labelOptions = { + label1: new Set(['a', 'b', 'c']), + }; + + const expectedResult = [ + { label1: 'a' }, + { label1: 'b' }, + { label1: 'c' }, + ]; + + expect(generateLabelCombinations(labelOptions)).toEqual(expectedResult); + }); + + it('generates label combinations for multiple labels with a single option.', () => { + const labelOptions = { + label1: new Set(['a']), + label2: new Set(['b']), + label3: new Set(['c']), + }; + + const expectedResult = [{ label1: 'a', label2: 'b', label3: 'c' }]; + + expect(generateLabelCombinations(labelOptions)).toEqual(expectedResult); + }); + + it('generates label combinations for multiple labels with multiple options.', () => { + const labelOptions = { + label1: new Set(['a', 'b', 'c']), + label2: new Set(['d', 'e']), + }; + + const expectedResult = [ + { label1: 'a', label2: 'd' }, + { label1: 'a', label2: 'e' }, + { label1: 'b', label2: 'd' }, + { label1: 'b', label2: 'e' }, + { label1: 'c', label2: 'd' }, + { label1: 'c', label2: 'e' }, + ]; + + expect(generateLabelCombinations(labelOptions)).toEqual(expectedResult); + }); + }); +}); From e289292fd145524394f9fd2d653f85d42110682f Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 6 Jul 2024 17:58:15 +1000 Subject: [PATCH 17/39] Added check for empty labelOptions --- src/clients/victoriaMetrics/promMetricCounter.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index 445c5aa73..c742e5422 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -27,6 +27,10 @@ export class PromMetricCounter { }; if (counterConfig.labelOptions) { + if (Object.keys(counterConfig.labelOptions).length === 0) { + throw new Error('LabelOptions are declared but undefined.'); + } + for (const labelName in counterConfig.labelOptions) { if (counterConfig.labelOptions[labelName].size === 0) { throw new Error( From bd4a15c29aae02a80f3f2df029f56e50b07e8f3f Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 6 Jul 2024 18:03:04 +1000 Subject: [PATCH 18/39] Updated constructor for promClientCounter --- src/clients/victoriaMetrics/promMetricCounter.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index c742e5422..d4371e5eb 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -26,7 +26,9 @@ export class PromMetricCounter { help: counterConfig.help, }; - if (counterConfig.labelOptions) { + if (!counterConfig.labelOptions) { + this.counter = new promClient.Counter(promClientCounterConfig); + } else { if (Object.keys(counterConfig.labelOptions).length === 0) { throw new Error('LabelOptions are declared but undefined.'); } @@ -34,7 +36,7 @@ export class PromMetricCounter { for (const labelName in counterConfig.labelOptions) { if (counterConfig.labelOptions[labelName].size === 0) { throw new Error( - `LabelOptions undefined for labelName: "${labelName}".`, + `LabelOptions are undefined for labelName: "${labelName}".`, ); } } @@ -42,13 +44,12 @@ export class PromMetricCounter { promClientCounterConfig.labelNames = Object.keys( counterConfig.labelOptions, ); - } - this.labelOptions = counterConfig.labelOptions; - this.counter = new promClient.Counter(promClientCounterConfig); + // Initialise counter metric + this.labelOptions = counterConfig.labelOptions; + this.counter = new promClient.Counter(promClientCounterConfig); - // Increment each labelCombination by 0 to initiate metric - if (counterConfig.labelOptions) { + // Increment each labelCombination by 0 to initiate metric const labelCombinations = generateLabelCombinations( counterConfig.labelOptions, ); From 18606603df3cab93da599e259bc01f8d4ca8037a Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 6 Jul 2024 19:14:33 +1000 Subject: [PATCH 19/39] Added test cases for promMetricCounter partially --- .../tests/promMetricCounter.test.ts | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/clients/victoriaMetrics/tests/promMetricCounter.test.ts diff --git a/src/clients/victoriaMetrics/tests/promMetricCounter.test.ts b/src/clients/victoriaMetrics/tests/promMetricCounter.test.ts new file mode 100644 index 000000000..9e92d427a --- /dev/null +++ b/src/clients/victoriaMetrics/tests/promMetricCounter.test.ts @@ -0,0 +1,115 @@ +import { promAgent } from '../promAgent'; +import { + CounterConfig, + PromClientCounterConfig, + PromMetricCounter, +} from '../promMetricCounter'; + +// Create mocks for promAgent and promClient.Counter +promAgent.registerMetric = jest.fn(); + +jest.mock('prom-client', () => ({ + Counter: jest.fn().mockImplementation(() => ({ + inc: jest.fn(), // Mock the inc method + })), +})); + +import { Counter } from 'prom-client'; + +describe('PromMetricCounter', () => { + describe('Constructor', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should register the metric name with promAgent and initialise the metric.', () => { + new PromMetricCounter({ + name: 'test_counter1', + help: 'A test counter.', + }); + + const expectedConfig1: PromClientCounterConfig = { + name: 'test_counter1', + help: 'A test counter.', + }; + + expect(promAgent.registerMetric).toHaveBeenCalledWith('test_counter1'); + expect(Counter).toHaveBeenCalledWith(expectedConfig1); + + new PromMetricCounter({ + name: 'test_counter2', + help: 'A test counter.', + labelOptions: { + status: new Set(['finished', 'voided']), + colour: new Set(['black', 'white']), + }, + }); + + const expectedConfig2: PromClientCounterConfig = { + name: 'test_counter2', + help: 'A test counter.', + labelNames: ['status', 'colour'], + }; + + expect(promAgent.registerMetric).toHaveBeenCalledWith('test_counter2'); + expect(Counter).toHaveBeenCalledWith(expectedConfig1); + }); + + it('should throw an error for empty labelOptions.', () => { + const counterConfig: CounterConfig = { + name: 'test_counter', + help: 'A test counter.', + labelOptions: {}, + }; + + expect(() => new PromMetricCounter(counterConfig)).toThrow(); + }); + + it('should throw an error for missing labelOptions.', () => { + const counterConfig: CounterConfig = { + name: 'test_counter', + help: 'A test counter.', + labelOptions: { status: new Set(['yes', 'no']), empty: new Set() }, + }; + + expect(() => new PromMetricCounter(counterConfig)).toThrow(); + }); + + // it('initialise all metric combinations where labelOptions are present.', () => { + // new PromMetricCounter({ + // name: 'test_counter', + // help: 'A test counter.', + // labelOptions: { + // status: new Set(['finished', 'voided']), + // colour: new Set(['black', 'white']), + // }, + // }); + // + // const expectedConfig: PromClientCounterConfig = { + // name: 'test_counter', + // help: 'A test counter.', + // labelNames: ['status', 'colour'], + // }; + // + // expect(promAgent.registerMetric).toHaveBeenCalledWith('test_counter'); + // expect(Counter).toHaveBeenCalledWith(expectedConfig); + // + // const labelCombinations = [ + // { status: 'finished', colour: 'black' }, + // { status: 'finished', colour: 'white' }, + // { status: 'voided', colour: 'black' }, + // { status: 'voided', colour: 'white' }, + // ]; + // + // console.log('test'); + // console.log(Counter.inc); + // + // // labelCombinations.forEach((combination) => { + // // expect(MockCounter.mock.instances[0].inc).toHaveBeenCalledWith( + // // combination, + // // 0, + // // ); + // // }); + // }); + }); +}); From f75553a4d7e1921f9abbdf9d1633d52796207ede Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 6 Jul 2024 20:13:00 +1000 Subject: [PATCH 20/39] Fixed inc mock --- .../tests/promMetricCounter.test.ts | 75 +++++++++---------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/src/clients/victoriaMetrics/tests/promMetricCounter.test.ts b/src/clients/victoriaMetrics/tests/promMetricCounter.test.ts index 9e92d427a..69809fc8f 100644 --- a/src/clients/victoriaMetrics/tests/promMetricCounter.test.ts +++ b/src/clients/victoriaMetrics/tests/promMetricCounter.test.ts @@ -1,3 +1,4 @@ +import { Counter } from 'prom-client'; import { promAgent } from '../promAgent'; import { CounterConfig, @@ -8,14 +9,14 @@ import { // Create mocks for promAgent and promClient.Counter promAgent.registerMetric = jest.fn(); +const incMock = jest.fn(); + jest.mock('prom-client', () => ({ Counter: jest.fn().mockImplementation(() => ({ - inc: jest.fn(), // Mock the inc method + inc: incMock, // Mock the inc method })), })); -import { Counter } from 'prom-client'; - describe('PromMetricCounter', () => { describe('Constructor', () => { beforeEach(() => { @@ -75,41 +76,37 @@ describe('PromMetricCounter', () => { expect(() => new PromMetricCounter(counterConfig)).toThrow(); }); - // it('initialise all metric combinations where labelOptions are present.', () => { - // new PromMetricCounter({ - // name: 'test_counter', - // help: 'A test counter.', - // labelOptions: { - // status: new Set(['finished', 'voided']), - // colour: new Set(['black', 'white']), - // }, - // }); - // - // const expectedConfig: PromClientCounterConfig = { - // name: 'test_counter', - // help: 'A test counter.', - // labelNames: ['status', 'colour'], - // }; - // - // expect(promAgent.registerMetric).toHaveBeenCalledWith('test_counter'); - // expect(Counter).toHaveBeenCalledWith(expectedConfig); - // - // const labelCombinations = [ - // { status: 'finished', colour: 'black' }, - // { status: 'finished', colour: 'white' }, - // { status: 'voided', colour: 'black' }, - // { status: 'voided', colour: 'white' }, - // ]; - // - // console.log('test'); - // console.log(Counter.inc); - // - // // labelCombinations.forEach((combination) => { - // // expect(MockCounter.mock.instances[0].inc).toHaveBeenCalledWith( - // // combination, - // // 0, - // // ); - // // }); - // }); + it('initialise all metric combinations where labelOptions are present.', () => { + new PromMetricCounter({ + name: 'test_counter', + help: 'A test counter.', + labelOptions: { + status: new Set(['finished', 'voided']), + colour: new Set(['black', 'white']), + }, + }); + + const expectedConfig: PromClientCounterConfig = { + name: 'test_counter', + help: 'A test counter.', + labelNames: ['status', 'colour'], + }; + + expect(promAgent.registerMetric).toHaveBeenCalledWith('test_counter'); + expect(Counter).toHaveBeenCalledWith(expectedConfig); + + const labelCombinations = [ + { status: 'finished', colour: 'black' }, + { status: 'finished', colour: 'white' }, + { status: 'voided', colour: 'black' }, + { status: 'voided', colour: 'white' }, + ]; + + expect(incMock).toHaveBeenCalledTimes(4); + + labelCombinations.forEach((combination) => { + expect(incMock).toHaveBeenCalledWith(combination, 0); + }); + }); }); }); From 85a71f580cb699f0fcc22910c1d060eae5bff619 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 6 Jul 2024 21:30:05 +1000 Subject: [PATCH 21/39] Added test functions for increment method for counter --- .../victoriaMetrics/promMetricCounter.ts | 4 ++ .../tests/promMetricCounter.test.ts | 54 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index d4371e5eb..88b3355c0 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -61,6 +61,10 @@ export class PromMetricCounter { } public inc(num: number, labels?: Record) { + if (this.labelOptions && !labels) { + throw new Error('Labels were not provided.'); + } + if (labels) { if (!this.isValidLabelCombination(labels)) { throw new Error(`Invalid labels provided: ${JSON.stringify(labels)}`); diff --git a/src/clients/victoriaMetrics/tests/promMetricCounter.test.ts b/src/clients/victoriaMetrics/tests/promMetricCounter.test.ts index 69809fc8f..75bae6e10 100644 --- a/src/clients/victoriaMetrics/tests/promMetricCounter.test.ts +++ b/src/clients/victoriaMetrics/tests/promMetricCounter.test.ts @@ -109,4 +109,58 @@ describe('PromMetricCounter', () => { }); }); }); + + describe('Increment', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should increment metrics.', () => { + const testMetric = new PromMetricCounter({ + name: 'test_counter', + help: 'A test counter.', + }); + + testMetric.inc(2); + + expect(incMock).toHaveBeenCalledWith(2); + + const testMetric2 = new PromMetricCounter({ + name: 'test_counter2', + help: 'A test counter.', + labelOptions: { + status: new Set(['yes', 'no']), + }, + }); + + testMetric2.inc(2, { status: 'yes' }); + testMetric2.inc(2, { status: 'no' }); + + expect(incMock).toHaveBeenCalledWith({ status: 'yes' }, 2); + expect(incMock).toHaveBeenCalledWith({ status: 'no' }, 2); + }); + + it('should not increment invalid metrics.', () => { + const testMetric = new PromMetricCounter({ + name: 'test_counter', + help: 'A test counter.', + labelOptions: { + status: new Set(['yes', 'no']), + color: new Set(['red', 'white']), + }, + }); + + // Labels are not used yet they were declared + expect(() => testMetric.inc(2)).toThrow(); + + // Not all label names are used + expect(() => testMetric.inc(2, { status: 'yes' })).toThrow(); + + // Label name not declared + expect(() => testMetric.inc(2, { fake_label: 'yes' })).toThrow(); + + // Incorrect label option used + expect(() => testMetric.inc(2, { status: 'fake_status' })).toThrow(); + }); + }); }); From d0dae22f8fbcae6fda82bec48caf1cad0e5acc85 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 6 Jul 2024 22:03:07 +1000 Subject: [PATCH 22/39] Changed from recursive back to map --- .../victoriaMetrics/metricFunctions.ts | 46 +++++-------------- 1 file changed, 11 insertions(+), 35 deletions(-) diff --git a/src/clients/victoriaMetrics/metricFunctions.ts b/src/clients/victoriaMetrics/metricFunctions.ts index 66d43372c..957824ba0 100644 --- a/src/clients/victoriaMetrics/metricFunctions.ts +++ b/src/clients/victoriaMetrics/metricFunctions.ts @@ -1,43 +1,19 @@ export function generateLabelCombinations( labelOptions: Record>, ): Record[] { - const labelNames = Object.keys(labelOptions); - const labelCombinations: Record[] = []; + let labelCombinations: Record[] = [{}]; - function buildCombinations( - currentCombination: Record, - index: number, - ) { - if (index === labelNames.length) { - labelCombinations.push(currentCombination); - return; - } + Object.keys(labelOptions).forEach((labelName) => { + // Get current label options + const options = Array.from(labelOptions[labelName]); - const key = labelNames[index]; - const options = Array.from(labelOptions[key]); - - options.forEach((option) => { - buildCombinations({ ...currentCombination, [key]: option }, index + 1); - }); - } - - buildCombinations({}, 0); + labelCombinations = labelCombinations.flatMap((combination) => + options.map((option) => ({ + ...combination, + [labelName]: option, + })), + ); + }); return labelCombinations; - - // TODO-kev: Use above recursive solution or below compact one - // Object.keys(labelOptions).forEach((labelName) => { - // // Get current label options - // const options = Array.from(labelOptions[labelName]); - // - // // Update combinations with new options - // labelCombinations = labelCombinations.flatMap((combination) => - // options.map((option) => ({ - // ...combination, - // [labelName]: option, - // })), - // ); - // }); - // - // return labelCombinations; } From 81bd6c018673f8af019e75618f30884bf6ed2108 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 6 Jul 2024 22:12:47 +1000 Subject: [PATCH 23/39] Minor update --- .../victoriaMetrics/promMetricCounter.ts | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index 88b3355c0..eac616ce5 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -21,13 +21,11 @@ export class PromMetricCounter { constructor(counterConfig: CounterConfig) { promAgent.registerMetric(counterConfig.name); - const promClientCounterConfig: PromClientCounterConfig = { - name: counterConfig.name, - help: counterConfig.help, - }; - if (!counterConfig.labelOptions) { - this.counter = new promClient.Counter(promClientCounterConfig); + this.counter = new promClient.Counter({ + name: counterConfig.name, + help: counterConfig.help, + }); } else { if (Object.keys(counterConfig.labelOptions).length === 0) { throw new Error('LabelOptions are declared but undefined.'); @@ -41,22 +39,20 @@ export class PromMetricCounter { } } - promClientCounterConfig.labelNames = Object.keys( - counterConfig.labelOptions, - ); - // Initialise counter metric this.labelOptions = counterConfig.labelOptions; - this.counter = new promClient.Counter(promClientCounterConfig); + this.counter = new promClient.Counter({ + name: counterConfig.name, + help: counterConfig.help, + labelNames: Object.keys(counterConfig.labelOptions), + }); // Increment each labelCombination by 0 to initiate metric - const labelCombinations = generateLabelCombinations( - counterConfig.labelOptions, + generateLabelCombinations(counterConfig.labelOptions).forEach( + (combination) => { + this.counter.inc(combination, 0); + }, ); - - labelCombinations.forEach((combination) => { - this.counter.inc(combination, 0); - }); } } From f0d96b140ce6c613ea450796e8dfe33d126fccde Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 6 Jul 2024 22:16:47 +1000 Subject: [PATCH 24/39] Minor update --- src/clients/victoriaMetrics/promMetricCounter.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index eac616ce5..4e38bcfaa 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -71,6 +71,7 @@ export class PromMetricCounter { } } + // Valid label combinations have a corresponding value for each label name private isValidLabelCombination( labelCombination: Record, ): boolean { @@ -84,7 +85,7 @@ export class PromMetricCounter { for (const labelName in labelCombination) { // label name is not in config - if (!this.labelOptions[labelName]) { + if (!(labelName in this.labelOptions)) { return false; } From fea929b6dccd1fcc1b5af610f81804e7cb19c70b Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 6 Jul 2024 22:54:03 +1000 Subject: [PATCH 25/39] Updated gauge --- .../victoriaMetrics/promMetricGauge.ts | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/clients/victoriaMetrics/promMetricGauge.ts b/src/clients/victoriaMetrics/promMetricGauge.ts index ae99cee32..e0bae71b8 100644 --- a/src/clients/victoriaMetrics/promMetricGauge.ts +++ b/src/clients/victoriaMetrics/promMetricGauge.ts @@ -1,43 +1,55 @@ import promClient, { Gauge } from 'prom-client'; import { promAgent } from './promAgent'; -import { generateLabelCombinations } from './metricFunctions'; interface GaugeConfig { name: string; help: string; - labelNames?: string[]; - labelOptions?: Record; + labelOptions?: Record>; collect?: () => void; // Refer to prom-client docs on how this should be used. } +interface PromClientGaugeConfig { + name: string; + help: string; + labelNames?: string[]; + collect?: () => void; +} + export class PromMetricGauge { - private readonly labelCombinations: Record[]; + private readonly labelOptions: Record>; private gauge: Gauge; constructor(gaugeConfig: GaugeConfig) { promAgent.registerMetric(gaugeConfig.name); - // Check either both or neither labelNames and labelOptions are declared - if ( - (gaugeConfig.labelNames && !gaugeConfig.labelOptions) || - (!gaugeConfig.labelNames && gaugeConfig.labelOptions) - ) { - throw new Error( - `Error: Counter Metric "${gaugeConfig.name}" must have both labelNames and labelOptions configured if either is declared.`, - ); - } + if (!gaugeConfig.labelOptions) { + this.gauge = new promClient.Gauge({ + name: gaugeConfig.name, + help: gaugeConfig.help, + collect: gaugeConfig.collect, + }); + } else { + if (Object.keys(gaugeConfig.labelOptions).length === 0) { + throw new Error('LabelOptions are declared but undefined.'); + } - // Validate labelNames and labelOptions if both provided - // TODO-kev: Needs to be fixed - // if (gaugeConfig.labelNames && gaugeConfig.labelOptions) { - // validateLabelNamesAndOptions( - // gaugeConfig.labelNames, - // gaugeConfig.labelOptions, - // ); - // this.labelCombinations = generateLabelCombinations( - // gaugeConfig.labelOptions, - // ); - // } + for (const labelName in gaugeConfig.labelOptions) { + if (gaugeConfig.labelOptions[labelName].size === 0) { + throw new Error( + `LabelOptions are undefined for labelName: "${labelName}".`, + ); + } + } + + // Initialise gauge metric + this.labelOptions = gaugeConfig.labelOptions; + this.gauge = new promClient.Gauge({ + name: gaugeConfig.name, + help: gaugeConfig.help, + labelNames: Object.keys(gaugeConfig.labelOptions), + collect: gaugeConfig.collect, + }); + } this.gauge = new promClient.Gauge(gaugeConfig); } From c401d16ff3cf2d48b939c80d2ae93a21c9503f00 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 6 Jul 2024 23:23:48 +1000 Subject: [PATCH 26/39] Added test cases for gauge --- .../victoriaMetrics/promMetricGauge.ts | 4 +- .../tests/promMetricGauge.test.ts | 83 +++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/clients/victoriaMetrics/tests/promMetricGauge.test.ts diff --git a/src/clients/victoriaMetrics/promMetricGauge.ts b/src/clients/victoriaMetrics/promMetricGauge.ts index e0bae71b8..a48044e7b 100644 --- a/src/clients/victoriaMetrics/promMetricGauge.ts +++ b/src/clients/victoriaMetrics/promMetricGauge.ts @@ -1,14 +1,14 @@ import promClient, { Gauge } from 'prom-client'; import { promAgent } from './promAgent'; -interface GaugeConfig { +export interface GaugeConfig { name: string; help: string; labelOptions?: Record>; collect?: () => void; // Refer to prom-client docs on how this should be used. } -interface PromClientGaugeConfig { +export interface PromClientGaugeConfig { name: string; help: string; labelNames?: string[]; diff --git a/src/clients/victoriaMetrics/tests/promMetricGauge.test.ts b/src/clients/victoriaMetrics/tests/promMetricGauge.test.ts new file mode 100644 index 000000000..258a28f7f --- /dev/null +++ b/src/clients/victoriaMetrics/tests/promMetricGauge.test.ts @@ -0,0 +1,83 @@ +import { Gauge } from 'prom-client'; +import { promAgent } from '../promAgent'; +import { + GaugeConfig, + PromClientGaugeConfig, + PromMetricGauge, +} from '../promMetricGauge'; + +// Create mocks for promAgent and promClient.Counter +promAgent.registerMetric = jest.fn(); + +const incMock = jest.fn(); + +jest.mock('prom-client', () => ({ + Gauge: jest.fn().mockImplementation(), +})); + +describe('PromMetric Gauge', () => { + describe('Constructor', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should register the metric name with promAgent and initialise the metric.', () => { + new PromMetricGauge({ + name: 'test_gauge1', + help: 'A test gauge.', + }); + + const expectedConfig1: PromClientGaugeConfig = { + name: 'test_gauge1', + help: 'A test gauge.', + }; + + expect(promAgent.registerMetric).toHaveBeenCalledWith('test_gauge1'); + expect(Gauge).toHaveBeenCalledWith(expectedConfig1); + + new PromMetricGauge({ + name: 'test_gauge2', + help: 'A test gauge.', + labelOptions: { + status: new Set(['finished', 'voided']), + colour: new Set(['black', 'white']), + }, + collect() { + this.set(1); + }, + }); + + const expectedConfig2: PromClientGaugeConfig = { + name: 'test_gauge2', + help: 'A test gauge.', + labelNames: ['status', 'colour'], + collect() { + this.set(1); + }, + }; + + expect(promAgent.registerMetric).toHaveBeenCalledWith('test_gauge2'); + expect(Gauge).toHaveBeenCalledWith(expectedConfig1); + }); + + it('should throw an error for empty labelOptions.', () => { + const gaugeConfig: GaugeConfig = { + name: 'test_gauge', + help: 'A test gauge.', + labelOptions: {}, + }; + + expect(() => new PromMetricGauge(gaugeConfig)).toThrow(); + }); + + it('should throw an error for missing labelOptions.', () => { + const gaugeConfig: GaugeConfig = { + name: 'test_gauge', + help: 'A test gauge.', + labelOptions: { status: new Set(['yes', 'no']), empty: new Set() }, + }; + + expect(() => new PromMetricGauge(gaugeConfig)).toThrow(); + }); + }); +}); From c35434c42eb4c25defe0b9ccefe555acbe4c6f94 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 6 Jul 2024 23:36:20 +1000 Subject: [PATCH 27/39] Minor updates --- src/clients/victoriaMetrics/promMetricGauge.ts | 4 ++-- src/clients/victoriaMetrics/tests/promMetricGauge.test.ts | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/clients/victoriaMetrics/promMetricGauge.ts b/src/clients/victoriaMetrics/promMetricGauge.ts index a48044e7b..0e1e2155c 100644 --- a/src/clients/victoriaMetrics/promMetricGauge.ts +++ b/src/clients/victoriaMetrics/promMetricGauge.ts @@ -49,8 +49,8 @@ export class PromMetricGauge { labelNames: Object.keys(gaugeConfig.labelOptions), collect: gaugeConfig.collect, }); - } - this.gauge = new promClient.Gauge(gaugeConfig); + this.gauge = new promClient.Gauge(gaugeConfig); + } } } diff --git a/src/clients/victoriaMetrics/tests/promMetricGauge.test.ts b/src/clients/victoriaMetrics/tests/promMetricGauge.test.ts index 258a28f7f..538304f03 100644 --- a/src/clients/victoriaMetrics/tests/promMetricGauge.test.ts +++ b/src/clients/victoriaMetrics/tests/promMetricGauge.test.ts @@ -9,8 +9,6 @@ import { // Create mocks for promAgent and promClient.Counter promAgent.registerMetric = jest.fn(); -const incMock = jest.fn(); - jest.mock('prom-client', () => ({ Gauge: jest.fn().mockImplementation(), })); From c68e0fa24d4bd908e1bb2d16bbe4619d04eb44e0 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sun, 7 Jul 2024 17:49:15 +1000 Subject: [PATCH 28/39] Added metric to distinguish between custom and matchmade games --- src/gameplay/game.ts | 16 +++++++++++++--- src/gameplay/roomTypes.ts | 11 +++++++++++ src/metrics/gameMetrics.ts | 1 + 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/gameplay/game.ts b/src/gameplay/game.ts index 65c42b9ee..ddc92ae16 100644 --- a/src/gameplay/game.ts +++ b/src/gameplay/game.ts @@ -11,7 +11,10 @@ import { isTO } from '../modsadmins/tournamentOrganizers'; import { isDev } from '../modsadmins/developers'; import { modOrTOString } from '../modsadmins/modOrTO'; -import { RoomCreationType } from './roomTypes'; +import { + RoomCreationType, + roomCreationTypeStrToMetricLabel, +} from './roomTypes'; import { Phase } from './phases/types'; import { Alliance, @@ -1255,7 +1258,11 @@ class Game extends Room { } // From this point on, no more game moves can be made. Game is finished. - gamesPlayedMetric.inc(1, { status: 'finished' }); + console.log(this.roomCreationType); + gamesPlayedMetric.inc(1, { + status: 'finished', + type: roomCreationTypeStrToMetricLabel(this.roomCreationType), + }); // Clean up from here. for (let i = 0; i < this.allSockets.length; i++) { @@ -2077,7 +2084,10 @@ class Game extends Room { } if (this.voidGameTracker.playerVoted(socket.request.user.username)) { - gamesPlayedMetric.inc(1, { status: 'voided' }); + gamesPlayedMetric.inc(1, { + status: 'voided', + type: roomCreationTypeStrToMetricLabel(this.roomCreationType), + }); this.changePhase(Phase.Voided); this.sendText(`Game has been voided.`, 'server-text'); diff --git a/src/gameplay/roomTypes.ts b/src/gameplay/roomTypes.ts index ba4ea4f80..908797994 100644 --- a/src/gameplay/roomTypes.ts +++ b/src/gameplay/roomTypes.ts @@ -3,6 +3,17 @@ export enum RoomCreationType { CUSTOM_ROOM = 'CUSTOM_ROOM', } +export function roomCreationTypeStrToMetricLabel(str: string) { + switch (str) { + case RoomCreationType.QUEUE: + return 'matchmaking'; + case RoomCreationType.CUSTOM_ROOM: + return 'custom'; + default: + throw new Error(`Unknown RoomCreationType: ${str}`); + } +} + export function strToRoomCreationType(typeString: string): RoomCreationType { switch (typeString) { case RoomCreationType.QUEUE: diff --git a/src/metrics/gameMetrics.ts b/src/metrics/gameMetrics.ts index 0beada4c6..f469619ea 100644 --- a/src/metrics/gameMetrics.ts +++ b/src/metrics/gameMetrics.ts @@ -5,5 +5,6 @@ export const gamesPlayedMetric = new PromMetricCounter({ help: 'Total number of games played.', labelOptions: { status: new Set(['finished', 'voided']), + type: new Set(['custom', 'matchmaking', 'private']), }, }); From 91b6ae41059a542f6ab798cf19a5a48d2e524419 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sun, 7 Jul 2024 17:49:50 +1000 Subject: [PATCH 29/39] Removed unused function --- src/gameplay/roomTypes.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/gameplay/roomTypes.ts b/src/gameplay/roomTypes.ts index 908797994..eb99770eb 100644 --- a/src/gameplay/roomTypes.ts +++ b/src/gameplay/roomTypes.ts @@ -13,15 +13,3 @@ export function roomCreationTypeStrToMetricLabel(str: string) { throw new Error(`Unknown RoomCreationType: ${str}`); } } - -export function strToRoomCreationType(typeString: string): RoomCreationType { - switch (typeString) { - case RoomCreationType.QUEUE: - return RoomCreationType.QUEUE; - case RoomCreationType.CUSTOM_ROOM: - return RoomCreationType.CUSTOM_ROOM; - default: - console.warn(`Invalid roomCreationType string. Got ${typeString}`); - return RoomCreationType.CUSTOM_ROOM; - } -} From 68810e8e7233f48f44bd6d931b10d023e9be6d66 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sun, 7 Jul 2024 18:00:14 +1000 Subject: [PATCH 30/39] Added private game label --- src/gameplay/roomTypes.ts | 3 +++ src/sockets/sockets.ts | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gameplay/roomTypes.ts b/src/gameplay/roomTypes.ts index eb99770eb..f67daee33 100644 --- a/src/gameplay/roomTypes.ts +++ b/src/gameplay/roomTypes.ts @@ -1,6 +1,7 @@ export enum RoomCreationType { QUEUE = 'QUEUE', CUSTOM_ROOM = 'CUSTOM_ROOM', + PRIVATE = 'PRIVATE_ROOM', } export function roomCreationTypeStrToMetricLabel(str: string) { @@ -9,6 +10,8 @@ export function roomCreationTypeStrToMetricLabel(str: string) { return 'matchmaking'; case RoomCreationType.CUSTOM_ROOM: return 'custom'; + case RoomCreationType.PRIVATE: + return 'private'; default: throw new Error(`Unknown RoomCreationType: ${str}`); } diff --git a/src/sockets/sockets.ts b/src/sockets/sockets.ts index 85686ee9b..c824b651a 100644 --- a/src/sockets/sockets.ts +++ b/src/sockets/sockets.ts @@ -42,7 +42,6 @@ import { Role } from '../gameplay/roles/types'; import { Phase } from '../gameplay/phases/types'; import { Card } from '../gameplay/cards/types'; import { TOCommandsImported } from './commands/tournamentOrganisers'; -import userAdapter from '../databaseAdapters/user'; import { uniqueLoginsMetric } from '../metrics/miscellaneousMetrics'; const ONE_DAY_MILLIS = 24 * 60 * 60 * 1000; // 1 day @@ -1444,7 +1443,7 @@ function newRoom(dataObj) { roomConfig, dataObj.muteSpectators, dataObj.disableVoteHistory, - RoomCreationType.CUSTOM_ROOM, + privateRoom ? RoomCreationType.PRIVATE : RoomCreationType.CUSTOM_ROOM, () => new Date(), ); From c0a1d943f38ec2a2f3effd31b79fb85e0441aaf5 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sun, 7 Jul 2024 18:02:37 +1000 Subject: [PATCH 31/39] Renamed type to game_type for clarity --- src/gameplay/game.ts | 4 ++-- src/metrics/gameMetrics.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gameplay/game.ts b/src/gameplay/game.ts index ddc92ae16..326408191 100644 --- a/src/gameplay/game.ts +++ b/src/gameplay/game.ts @@ -1261,7 +1261,7 @@ class Game extends Room { console.log(this.roomCreationType); gamesPlayedMetric.inc(1, { status: 'finished', - type: roomCreationTypeStrToMetricLabel(this.roomCreationType), + game_type: roomCreationTypeStrToMetricLabel(this.roomCreationType), }); // Clean up from here. @@ -2086,7 +2086,7 @@ class Game extends Room { if (this.voidGameTracker.playerVoted(socket.request.user.username)) { gamesPlayedMetric.inc(1, { status: 'voided', - type: roomCreationTypeStrToMetricLabel(this.roomCreationType), + game_type: roomCreationTypeStrToMetricLabel(this.roomCreationType), }); this.changePhase(Phase.Voided); diff --git a/src/metrics/gameMetrics.ts b/src/metrics/gameMetrics.ts index f469619ea..3ba7bb849 100644 --- a/src/metrics/gameMetrics.ts +++ b/src/metrics/gameMetrics.ts @@ -5,6 +5,6 @@ export const gamesPlayedMetric = new PromMetricCounter({ help: 'Total number of games played.', labelOptions: { status: new Set(['finished', 'voided']), - type: new Set(['custom', 'matchmaking', 'private']), + game_type: new Set(['custom', 'matchmaking', 'private']), }, }); From 3fa626fcbebb6d262bb0730bb1647532b738101a Mon Sep 17 00:00:00 2001 From: Kevin Date: Sun, 7 Jul 2024 18:10:44 +1000 Subject: [PATCH 32/39] Removed unnecessary console log --- src/gameplay/game.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gameplay/game.ts b/src/gameplay/game.ts index 326408191..f124eb4f0 100644 --- a/src/gameplay/game.ts +++ b/src/gameplay/game.ts @@ -530,7 +530,6 @@ class Game extends Room { this.pickNum = 1; this.missionHistory = []; - console.log(this.ranked); let str = ''; if (this.ranked === true) { str = 'This game is ranked.'; From 9cfa3cd8faa3c54b72fc58483fb9ecff224c29cf Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 22 Jul 2024 17:23:35 +1000 Subject: [PATCH 33/39] Minor update --- src/clients/victoriaMetrics/promMetricCounter.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/clients/victoriaMetrics/promMetricCounter.ts b/src/clients/victoriaMetrics/promMetricCounter.ts index 228e781a9..e91fbc007 100644 --- a/src/clients/victoriaMetrics/promMetricCounter.ts +++ b/src/clients/victoriaMetrics/promMetricCounter.ts @@ -14,7 +14,6 @@ export interface CounterConfig { export class PromMetricCounter { private readonly labelOptions: Record>; private counter: Counter; - private labelNames: string[]; constructor(counterConfig: CounterConfig) { promAgent.registerMetric(counterConfig.name); From e42df33e3155383e14fbbde1513c97c81793a572 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 22 Jul 2024 17:57:27 +1000 Subject: [PATCH 34/39] Added a roomCreationMapping function to map between room type and metric label --- src/gameplay/game.ts | 10 +++------- src/gameplay/roomTypes.ts | 33 +++++++++++++++++++++++---------- src/metrics/gameMetrics.ts | 2 +- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/gameplay/game.ts b/src/gameplay/game.ts index f124eb4f0..8cf212af9 100644 --- a/src/gameplay/game.ts +++ b/src/gameplay/game.ts @@ -11,10 +11,7 @@ import { isTO } from '../modsadmins/tournamentOrganizers'; import { isDev } from '../modsadmins/developers'; import { modOrTOString } from '../modsadmins/modOrTO'; -import { - RoomCreationType, - roomCreationTypeStrToMetricLabel, -} from './roomTypes'; +import { getRoomCreationMetricType, RoomCreationType } from './roomTypes'; import { Phase } from './phases/types'; import { Alliance, @@ -1257,10 +1254,9 @@ class Game extends Room { } // From this point on, no more game moves can be made. Game is finished. - console.log(this.roomCreationType); gamesPlayedMetric.inc(1, { status: 'finished', - game_type: roomCreationTypeStrToMetricLabel(this.roomCreationType), + room_creation_type: getRoomCreationMetricType(this.roomCreationType), }); // Clean up from here. @@ -2085,7 +2081,7 @@ class Game extends Room { if (this.voidGameTracker.playerVoted(socket.request.user.username)) { gamesPlayedMetric.inc(1, { status: 'voided', - game_type: roomCreationTypeStrToMetricLabel(this.roomCreationType), + room_creation_type: getRoomCreationMetricType(this.roomCreationType), }); this.changePhase(Phase.Voided); diff --git a/src/gameplay/roomTypes.ts b/src/gameplay/roomTypes.ts index f67daee33..ebf265d73 100644 --- a/src/gameplay/roomTypes.ts +++ b/src/gameplay/roomTypes.ts @@ -4,15 +4,28 @@ export enum RoomCreationType { PRIVATE = 'PRIVATE_ROOM', } -export function roomCreationTypeStrToMetricLabel(str: string) { - switch (str) { - case RoomCreationType.QUEUE: - return 'matchmaking'; - case RoomCreationType.CUSTOM_ROOM: - return 'custom'; - case RoomCreationType.PRIVATE: - return 'private'; - default: - throw new Error(`Unknown RoomCreationType: ${str}`); +export enum RoomCreationMetricType { + QUEUE = 'matchmaking', + CUSTOM_ROOM = 'custom', + PRIVATE = 'private', +} + +const roomCreationMapping: { + [key in RoomCreationType]: RoomCreationMetricType; +} = { + [RoomCreationType.QUEUE]: RoomCreationMetricType.QUEUE, + [RoomCreationType.CUSTOM_ROOM]: RoomCreationMetricType.CUSTOM_ROOM, + [RoomCreationType.PRIVATE]: RoomCreationMetricType.PRIVATE, +}; + +export function getRoomCreationMetricType( + type: RoomCreationType, +): RoomCreationMetricType { + const metricType = roomCreationMapping[type]; + + if (!metricType) { + throw new Error(`Invalid RoomCreationType: ${type}`); } + + return roomCreationMapping[type]; } diff --git a/src/metrics/gameMetrics.ts b/src/metrics/gameMetrics.ts index 58ca47ff7..2ac0fedbc 100644 --- a/src/metrics/gameMetrics.ts +++ b/src/metrics/gameMetrics.ts @@ -5,6 +5,6 @@ export const gamesPlayedMetric = new PromMetricCounter({ help: 'Total number of games played.', labelOptions: { status: new Set(['finished', 'voided']), - game_type: new Set(['matchmaking', 'public', 'private']) + room_creation_type: new Set(['matchmaking', 'custom', 'private']), }, }); From 11159a349727931867c68c2c1e78bd559bb7226a Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 15 Aug 2024 11:15:46 +1000 Subject: [PATCH 35/39] Reverted private room type changes --- src/gameplay/game.ts | 2 +- src/gameplay/roomTypes.ts | 27 --------------------------- src/metrics/gameMetrics.ts | 2 +- src/sockets/sockets.ts | 1 - 4 files changed, 2 insertions(+), 30 deletions(-) diff --git a/src/gameplay/game.ts b/src/gameplay/game.ts index 8cf212af9..d644f4265 100644 --- a/src/gameplay/game.ts +++ b/src/gameplay/game.ts @@ -11,7 +11,7 @@ import { isTO } from '../modsadmins/tournamentOrganizers'; import { isDev } from '../modsadmins/developers'; import { modOrTOString } from '../modsadmins/modOrTO'; -import { getRoomCreationMetricType, RoomCreationType } from './roomTypes'; +import { RoomCreationType } from './roomTypes'; import { Phase } from './phases/types'; import { Alliance, diff --git a/src/gameplay/roomTypes.ts b/src/gameplay/roomTypes.ts index ebf265d73..31244bd9b 100644 --- a/src/gameplay/roomTypes.ts +++ b/src/gameplay/roomTypes.ts @@ -1,31 +1,4 @@ export enum RoomCreationType { QUEUE = 'QUEUE', CUSTOM_ROOM = 'CUSTOM_ROOM', - PRIVATE = 'PRIVATE_ROOM', -} - -export enum RoomCreationMetricType { - QUEUE = 'matchmaking', - CUSTOM_ROOM = 'custom', - PRIVATE = 'private', -} - -const roomCreationMapping: { - [key in RoomCreationType]: RoomCreationMetricType; -} = { - [RoomCreationType.QUEUE]: RoomCreationMetricType.QUEUE, - [RoomCreationType.CUSTOM_ROOM]: RoomCreationMetricType.CUSTOM_ROOM, - [RoomCreationType.PRIVATE]: RoomCreationMetricType.PRIVATE, -}; - -export function getRoomCreationMetricType( - type: RoomCreationType, -): RoomCreationMetricType { - const metricType = roomCreationMapping[type]; - - if (!metricType) { - throw new Error(`Invalid RoomCreationType: ${type}`); - } - - return roomCreationMapping[type]; } diff --git a/src/metrics/gameMetrics.ts b/src/metrics/gameMetrics.ts index 2ac0fedbc..046bbcd5d 100644 --- a/src/metrics/gameMetrics.ts +++ b/src/metrics/gameMetrics.ts @@ -5,6 +5,6 @@ export const gamesPlayedMetric = new PromMetricCounter({ help: 'Total number of games played.', labelOptions: { status: new Set(['finished', 'voided']), - room_creation_type: new Set(['matchmaking', 'custom', 'private']), + room_creation_type: new Set(['matchmaking', 'public', 'private']), }, }); diff --git a/src/sockets/sockets.ts b/src/sockets/sockets.ts index 14ecbb2f5..6300be2a2 100644 --- a/src/sockets/sockets.ts +++ b/src/sockets/sockets.ts @@ -1443,7 +1443,6 @@ function newRoom(dataObj) { roomConfig, dataObj.muteSpectators, dataObj.disableVoteHistory, - privateRoom ? RoomCreationType.PRIVATE : RoomCreationType.CUSTOM_ROOM, () => new Date(), ); From 7fce7d2fcbc9d656fcc272235fa2382602f0fd51 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 15 Aug 2024 11:35:26 +1000 Subject: [PATCH 36/39] Updated gamesPlayedMetric room type for private and matchmade games --- src/gameplay/game.ts | 8 ++++++-- src/metrics/gameMetrics.ts | 18 +++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/gameplay/game.ts b/src/gameplay/game.ts index d644f4265..1da6fbeb9 100644 --- a/src/gameplay/game.ts +++ b/src/gameplay/game.ts @@ -32,7 +32,11 @@ import { millisToStr } from '../util/time'; import shuffleArray from '../util/shuffleArray'; import { Anonymizer } from './anonymizer'; import { sendReplyToCommand } from '../sockets/sockets'; -import { gamesPlayedMetric } from '../metrics/gameMetrics'; +import { + gamesPlayedMetric, + getGameMetricRoomType, + getGameType, +} from '../metrics/gameMetrics'; export const WAITING = 'Waiting'; export const MIN_PLAYERS = 5; @@ -2081,7 +2085,7 @@ class Game extends Room { if (this.voidGameTracker.playerVoted(socket.request.user.username)) { gamesPlayedMetric.inc(1, { status: 'voided', - room_creation_type: getRoomCreationMetricType(this.roomCreationType), + room_type: getGameMetricRoomType(this.roomCreationType), }); this.changePhase(Phase.Voided); diff --git a/src/metrics/gameMetrics.ts b/src/metrics/gameMetrics.ts index 046bbcd5d..99c89cffe 100644 --- a/src/metrics/gameMetrics.ts +++ b/src/metrics/gameMetrics.ts @@ -1,10 +1,26 @@ import { PromMetricCounter } from '../clients/victoriaMetrics/promMetricCounter'; +import Game from '../gameplay/game'; +import { RoomCreationType } from '../gameplay/roomTypes'; + +enum GameRoomType { + MATCHMAKING = 'matchmaking', + PUBLIC = 'public', + PRIVATE = 'private', +} export const gamesPlayedMetric = new PromMetricCounter({ name: 'games_played_total', help: 'Total number of games played.', labelOptions: { status: new Set(['finished', 'voided']), - room_creation_type: new Set(['matchmaking', 'public', 'private']), + room_type: new Set(Object.values(GameRoomType)), }, }); + +export function getGameMetricRoomType(game: Game) { + if (game.roomCreationType == RoomCreationType.QUEUE) { + return GameRoomType.MATCHMAKING; + } else if (game.joinPassword) { + return GameRoomType.PRIVATE; + } else return GameRoomType.PUBLIC; +} From bb7fe3ceef4c87ef68c995f21d9e4a51b67bf3c2 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 15 Aug 2024 11:37:39 +1000 Subject: [PATCH 37/39] Fixed metrics --- src/gameplay/game.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/gameplay/game.ts b/src/gameplay/game.ts index 1da6fbeb9..9acbf4239 100644 --- a/src/gameplay/game.ts +++ b/src/gameplay/game.ts @@ -35,7 +35,6 @@ import { sendReplyToCommand } from '../sockets/sockets'; import { gamesPlayedMetric, getGameMetricRoomType, - getGameType, } from '../metrics/gameMetrics'; export const WAITING = 'Waiting'; @@ -1260,7 +1259,7 @@ class Game extends Room { // From this point on, no more game moves can be made. Game is finished. gamesPlayedMetric.inc(1, { status: 'finished', - room_creation_type: getRoomCreationMetricType(this.roomCreationType), + room_type: getGameMetricRoomType(this), }); // Clean up from here. @@ -2085,7 +2084,7 @@ class Game extends Room { if (this.voidGameTracker.playerVoted(socket.request.user.username)) { gamesPlayedMetric.inc(1, { status: 'voided', - room_type: getGameMetricRoomType(this.roomCreationType), + room_type: getGameMetricRoomType(this), }); this.changePhase(Phase.Voided); From ac0d3eb5cf13987f1119ceea9b2d0bbee08ebcbd Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 15 Aug 2024 11:38:44 +1000 Subject: [PATCH 38/39] Updated enum name --- src/metrics/gameMetrics.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/metrics/gameMetrics.ts b/src/metrics/gameMetrics.ts index 99c89cffe..5dea3eb99 100644 --- a/src/metrics/gameMetrics.ts +++ b/src/metrics/gameMetrics.ts @@ -2,7 +2,7 @@ import { PromMetricCounter } from '../clients/victoriaMetrics/promMetricCounter' import Game from '../gameplay/game'; import { RoomCreationType } from '../gameplay/roomTypes'; -enum GameRoomType { +enum GameMetricRoomType { MATCHMAKING = 'matchmaking', PUBLIC = 'public', PRIVATE = 'private', @@ -13,14 +13,14 @@ export const gamesPlayedMetric = new PromMetricCounter({ help: 'Total number of games played.', labelOptions: { status: new Set(['finished', 'voided']), - room_type: new Set(Object.values(GameRoomType)), + room_type: new Set(Object.values(GameMetricRoomType)), }, }); -export function getGameMetricRoomType(game: Game) { +export function getGameMetricRoomType(game: Game): GameMetricRoomType { if (game.roomCreationType == RoomCreationType.QUEUE) { - return GameRoomType.MATCHMAKING; + return GameMetricRoomType.MATCHMAKING; } else if (game.joinPassword) { - return GameRoomType.PRIVATE; - } else return GameRoomType.PUBLIC; + return GameMetricRoomType.PRIVATE; + } else return GameMetricRoomType.PUBLIC; } From e1b39baaebf04447f6e9ee9b57200889a950e582 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 15 Aug 2024 11:41:33 +1000 Subject: [PATCH 39/39] Minor update --- src/metrics/gameMetrics.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/metrics/gameMetrics.ts b/src/metrics/gameMetrics.ts index 5dea3eb99..79cf97bfb 100644 --- a/src/metrics/gameMetrics.ts +++ b/src/metrics/gameMetrics.ts @@ -22,5 +22,9 @@ export function getGameMetricRoomType(game: Game): GameMetricRoomType { return GameMetricRoomType.MATCHMAKING; } else if (game.joinPassword) { return GameMetricRoomType.PRIVATE; - } else return GameMetricRoomType.PUBLIC; + } else if (!game.joinPassword) { + return GameMetricRoomType.PUBLIC; + } else { + throw new Error('Undefined game room type.'); + } }