diff --git a/src/lib/metrics.test.ts b/src/lib/metrics.test.ts index b3b519f89a..a8688873d7 100644 --- a/src/lib/metrics.test.ts +++ b/src/lib/metrics.test.ts @@ -2,7 +2,11 @@ import { register } from 'prom-client'; import EventEmitter from 'events'; import { createTestConfig } from '../test/config/test-config'; import { REQUEST_TIME, DB_TIME } from './metric-events'; -import { CLIENT_METRICS, FEATURE_UPDATED } from './types/events'; +import { + CLIENT_METRICS, + CLIENT_REGISTER, + FEATURE_UPDATED, +} from './types/events'; import { createMetricsMonitor } from './metrics'; import createStores from '../test/fixtures/store'; @@ -32,7 +36,6 @@ beforeAll(() => { // @ts-ignore - We don't want a full knex implementation for our tests, it's enough that it actually yields the numbers we want. monitor.startMonitoring(config, stores, '4.0.0', eventBus, db); }); - afterAll(() => { monitor.stopMonitoring(); }); @@ -108,3 +111,37 @@ test('Should collect metrics for database', async () => { expect(metrics).toMatch(/db_pool_pending_creates/); expect(metrics).toMatch(/db_pool_pending_acquires/); }); + +test('Should collect metrics for client sdk versions', async () => { + eventBus.emit(CLIENT_REGISTER, { sdkVersion: 'unleash-client-node:3.2.5' }); + eventBus.emit(CLIENT_REGISTER, { sdkVersion: 'unleash-client-node:3.2.5' }); + eventBus.emit(CLIENT_REGISTER, { sdkVersion: 'unleash-client-node:3.2.5' }); + eventBus.emit(CLIENT_REGISTER, { sdkVersion: 'unleash-client-java:5.0.0' }); + eventBus.emit(CLIENT_REGISTER, { sdkVersion: 'unleash-client-java:5.0.0' }); + eventBus.emit(CLIENT_REGISTER, { sdkVersion: 'unleash-client-java:5.0.0' }); + const metrics = await prometheusRegister.getSingleMetricAsString( + 'client_sdk_versions', + ); + expect(metrics).toMatch( + /client_sdk_versions\{sdk_name="unleash-client-node",sdk_version="3\.2\.5"} 3/, + ); + expect(metrics).toMatch( + /client_sdk_versions\{sdk_name="unleash-client-java",sdk_version="5\.0\.0"} 3/, + ); + eventBus.emit(CLIENT_REGISTER, { sdkVersion: 'unleash-client-node:3.2.5' }); + const newmetrics = await prometheusRegister.getSingleMetricAsString( + 'client_sdk_versions', + ); + expect(newmetrics).toMatch( + /client_sdk_versions\{sdk_name="unleash-client-node",sdk_version="3\.2\.5"} 4/, + ); +}); + +test('Should not collect client sdk version if sdkVersion is of wrong format or non-existent', async () => { + eventBus.emit(CLIENT_REGISTER, { sdkVersion: 'unleash-client-rust' }); + eventBus.emit(CLIENT_REGISTER, {}); + const metrics = await prometheusRegister.getSingleMetricAsString( + 'client_sdk_versions', + ); + expect(metrics).not.toMatch(/unleash-client-rust/); +}); diff --git a/src/lib/metrics.ts b/src/lib/metrics.ts index 72d0a790e6..3360c6e9b8 100644 --- a/src/lib/metrics.ts +++ b/src/lib/metrics.ts @@ -12,6 +12,7 @@ import { FEATURE_STRATEGY_UPDATE, FEATURE_UPDATED, CLIENT_METRICS, + CLIENT_REGISTER, } from './types/events'; import { IUnleashConfig } from './types/option'; import { IUnleashStores } from './types/stores'; @@ -80,6 +81,12 @@ export default class MetricsMonitor { help: 'Number of projects', }); + const clientSdkVersionUsage = new client.Counter({ + name: 'client_sdk_versions', + help: 'Which sdk versions are being used', + labelNames: ['sdk_name', 'sdk_version'], + }); + async function collectStaticCounters() { let togglesCount: number = 0; let usersCount: number; @@ -157,6 +164,12 @@ export default class MetricsMonitor { .inc(entry[1].no); } }); + eventBus.on(CLIENT_REGISTER, (m) => { + if (m.sdkVersion && m.sdkVersion.indexOf(':') > -1) { + const [sdkName, sdkVersion] = m.sdkVersion.split(':'); + clientSdkVersionUsage.labels(sdkName, sdkVersion).inc(); + } + }); this.configureDbMetrics(db, eventBus); } diff --git a/src/lib/services/client-metrics/instance-service.test.ts b/src/lib/services/client-metrics/instance-service.test.ts index 8e09e30e01..ff6bbf2a64 100644 --- a/src/lib/services/client-metrics/instance-service.test.ts +++ b/src/lib/services/client-metrics/instance-service.test.ts @@ -2,6 +2,7 @@ import ClientInstanceService from './instance-service'; import getLogger from '../../../test/fixtures/no-logger'; import { IClientApp } from '../../types/model'; import { secondsToMilliseconds } from 'date-fns'; +import FakeEventStore from '../../../test/fixtures/fake-event-store'; /** * A utility to wait for any pending promises in the test subject code. @@ -54,7 +55,7 @@ test('Multiple registrations of same appname and instanceid within same time per featureToggleStore: null, clientApplicationsStore, clientInstanceStore, - eventStore: null, + eventStore: new FakeEventStore(), }, { getLogger }, ); @@ -104,7 +105,7 @@ test('Multiple unique clients causes multiple registrations', async () => { featureToggleStore: null, clientApplicationsStore, clientInstanceStore, - eventStore: null, + eventStore: new FakeEventStore(), }, { getLogger }, ); @@ -157,7 +158,7 @@ test('Same client registered outside of dedup interval will be registered twice' featureToggleStore: null, clientApplicationsStore, clientInstanceStore, - eventStore: null, + eventStore: new FakeEventStore(), }, { getLogger }, bulkInterval, @@ -210,7 +211,7 @@ test('No registrations during a time period will not call stores', async () => { featureToggleStore: null, clientApplicationsStore, clientInstanceStore, - eventStore: null, + eventStore: new FakeEventStore(), }, { getLogger }, ); diff --git a/src/lib/services/client-metrics/instance-service.ts b/src/lib/services/client-metrics/instance-service.ts index 3a5d684ddb..b973763891 100644 --- a/src/lib/services/client-metrics/instance-service.ts +++ b/src/lib/services/client-metrics/instance-service.ts @@ -1,5 +1,5 @@ import { applicationSchema } from './schema'; -import { APPLICATION_CREATED } from '../../types/events'; +import { APPLICATION_CREATED, CLIENT_REGISTER } from '../../types/events'; import { IApplication } from './models'; import { IUnleashStores } from '../../types/stores'; import { IUnleashConfig } from '../../types/option'; @@ -111,6 +111,7 @@ export default class ClientInstanceService { value.clientIp = clientIp; value.createdBy = clientIp; this.seenClients[this.clientKey(value)] = value; + this.eventStore.emit(CLIENT_REGISTER, value); } async announceUnannounced(): Promise { diff --git a/src/lib/types/events.ts b/src/lib/types/events.ts index 86fb02fbf7..fd8891ce8b 100644 --- a/src/lib/types/events.ts +++ b/src/lib/types/events.ts @@ -74,6 +74,7 @@ export const SETTING_UPDATED = 'setting-updated'; export const SETTING_DELETED = 'setting-deleted'; export const CLIENT_METRICS = 'client-metrics'; +export const CLIENT_REGISTER = 'client-register'; export interface IBaseEvent { type: string;