diff --git a/src/lib/metrics.test.ts b/src/lib/metrics.test.ts index a9b7cb195ee9..fea985c99e07 100644 --- a/src/lib/metrics.test.ts +++ b/src/lib/metrics.test.ts @@ -10,11 +10,14 @@ import { } from './types/events'; import { createMetricsMonitor } from './metrics'; import createStores from '../test/fixtures/store'; +import { InstanceStatsService } from './services/instance-stats-service'; +import VersionService from './services/version-service'; const monitor = createMetricsMonitor(); const eventBus = new EventEmitter(); const prometheusRegister = register; let eventStore: IEventStore; +let statsService: InstanceStatsService; let stores; beforeAll(() => { const config = createTestConfig({ @@ -24,6 +27,8 @@ beforeAll(() => { }); stores = createStores(); eventStore = stores.eventStore; + const versionService = new VersionService(stores, config); + statsService = new InstanceStatsService(stores, config, versionService); const db = { client: { pool: { @@ -37,7 +42,14 @@ 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); + monitor.startMonitoring( + config, + stores, + '4.0.0', + eventBus, + statsService, + db, + ); }); afterAll(() => { monitor.stopMonitoring(); @@ -102,6 +114,9 @@ test('should collect metrics for db query timings', async () => { }); test('should collect metrics for feature toggle size', async () => { + await new Promise((done) => { + setTimeout(done, 10); + }); const metrics = await prometheusRegister.metrics(); expect(metrics).toMatch(/feature_toggles_total{version="(.*)"} 0/); }); diff --git a/src/lib/metrics.ts b/src/lib/metrics.ts index 1ff13dc4f79c..2529c494a1fc 100644 --- a/src/lib/metrics.ts +++ b/src/lib/metrics.ts @@ -22,6 +22,7 @@ import { IUnleashConfig } from './types/option'; import { IUnleashStores } from './types/stores'; import { hoursToMilliseconds, minutesToMilliseconds } from 'date-fns'; import Timer = NodeJS.Timer; +import { InstanceStatsService } from './services/instance-stats-service'; export default class MetricsMonitor { timer?: Timer; @@ -38,19 +39,14 @@ export default class MetricsMonitor { stores: IUnleashStores, version: string, eventBus: EventEmitter, + instanceStatsService: InstanceStatsService, db: Knex, ): Promise { if (!config.server.serverMetrics) { return; } - const { - eventStore, - featureToggleStore, - userStore, - projectStore, - environmentStore, - } = stores; + const { eventStore } = stores; client.collectDefaultMetrics(); @@ -97,6 +93,40 @@ export default class MetricsMonitor { name: 'environments_total', help: 'Number of environments', }); + const groupsTotal = new client.Gauge({ + name: 'groups_total', + help: 'Number of groups', + }); + + const rolesTotal = new client.Gauge({ + name: 'roles_total', + help: 'Number of roles', + }); + + const segmentsTotal = new client.Gauge({ + name: 'segments_total', + help: 'Number of segments', + }); + + const contextTotal = new client.Gauge({ + name: 'context_total', + help: 'Number of context', + }); + + const strategiesTotal = new client.Gauge({ + name: 'strategies_total', + help: 'Number of strategies', + }); + + const samlEnabled = new client.Gauge({ + name: 'saml_enabled', + help: 'Whether SAML is enabled', + }); + + const oidcEnabled = new client.Gauge({ + name: 'oidc_enabled', + help: 'Whether OIDC is enabled', + }); const clientSdkVersionUsage = new client.Counter({ name: 'client_sdk_versions', @@ -105,41 +135,51 @@ export default class MetricsMonitor { }); async function collectStaticCounters() { - let togglesCount: number = 0; - let usersCount: number; - let projectsCount: number; - let environmentsCount: number; try { - togglesCount = await featureToggleStore.count({ - archived: false, - }); - usersCount = await userStore.count(); - projectsCount = await projectStore.count(); - environmentsCount = await environmentStore.count(); - // eslint-disable-next-line no-empty - } catch (e) {} + const stats = await instanceStatsService.getStats(); + + featureTogglesTotal.reset(); + featureTogglesTotal.labels(version).set(stats.featureToggles); - featureTogglesTotal.reset(); - featureTogglesTotal.labels(version).set(togglesCount); - if (usersCount) { usersTotal.reset(); - usersTotal.set(usersCount); - } - if (projectsCount) { + usersTotal.set(stats.users); + projectsTotal.reset(); - projectsTotal.set(projectsCount); - } - if (environmentsCount) { + projectsTotal.set(stats.projects); + environmentsTotal.reset(); - environmentsTotal.set(environmentsCount); - } + environmentsTotal.set(stats.environments); + + groupsTotal.reset(); + groupsTotal.set(stats.groups); + + rolesTotal.reset(); + rolesTotal.set(stats.roles); + + segmentsTotal.reset(); + segmentsTotal.set(stats.segments); + + contextTotal.reset(); + contextTotal.set(stats.contextFields); + + strategiesTotal.reset(); + strategiesTotal.set(stats.strategies); + + samlEnabled.reset(); + samlEnabled.set(stats.SAMLenabled ? 1 : 0); + + oidcEnabled.reset(); + oidcEnabled.set(stats.OIDCenabled ? 1 : 0); + } catch (e) {} } - collectStaticCounters(); - this.timer = setInterval( - () => collectStaticCounters(), - hoursToMilliseconds(2), - ).unref(); + process.nextTick(() => { + collectStaticCounters(); + this.timer = setInterval( + () => collectStaticCounters(), + hoursToMilliseconds(2), + ).unref(); + }); eventBus.on( events.REQUEST_TIME, diff --git a/src/lib/server-impl.ts b/src/lib/server-impl.ts index 379389346cf8..514898c430c7 100644 --- a/src/lib/server-impl.ts +++ b/src/lib/server-impl.ts @@ -67,6 +67,7 @@ async function createApp( stores, serverVersion, config.eventBus, + services.instanceStatsService, db, ); const unleash: Omit = { diff --git a/src/test/fixtures/fake-context-field-store.ts b/src/test/fixtures/fake-context-field-store.ts index 43fa7cd1accd..74314417e7c7 100644 --- a/src/test/fixtures/fake-context-field-store.ts +++ b/src/test/fixtures/fake-context-field-store.ts @@ -7,7 +7,7 @@ import NotFoundError from '../../lib/error/notfound-error'; export default class FakeContextFieldStore implements IContextFieldStore { count(): Promise { - throw new Error('Method not implemented.'); + return Promise.resolve(0); } defaultContextFields: IContextField[] = [ diff --git a/src/test/fixtures/fake-group-store.ts b/src/test/fixtures/fake-group-store.ts index acef0ed5cf51..47473187ebd8 100644 --- a/src/test/fixtures/fake-group-store.ts +++ b/src/test/fixtures/fake-group-store.ts @@ -10,7 +10,7 @@ import Group, { /* eslint-disable @typescript-eslint/no-unused-vars */ export default class FakeGroupStore implements IGroupStore { count(): Promise { - throw new Error('Method not implemented.'); + return Promise.resolve(0); } data: IGroup[]; diff --git a/src/test/fixtures/fake-role-store.ts b/src/test/fixtures/fake-role-store.ts index 1c0e33f06953..7e870bb48bfc 100644 --- a/src/test/fixtures/fake-role-store.ts +++ b/src/test/fixtures/fake-role-store.ts @@ -9,7 +9,7 @@ import { export default class FakeRoleStore implements IRoleStore { count(): Promise { - throw new Error('Method not implemented.'); + return Promise.resolve(0); } getProjectRolesCount(): Promise { diff --git a/src/test/fixtures/fake-segment-store.ts b/src/test/fixtures/fake-segment-store.ts index 196fbb3b6226..620c767ca3af 100644 --- a/src/test/fixtures/fake-segment-store.ts +++ b/src/test/fixtures/fake-segment-store.ts @@ -3,7 +3,7 @@ import { IFeatureStrategySegment, ISegment } from '../../lib/types/model'; export default class FakeSegmentStore implements ISegmentStore { count(): Promise { - throw new Error('Method not implemented.'); + return Promise.resolve(0); } create(): Promise { diff --git a/src/test/fixtures/fake-strategies-store.ts b/src/test/fixtures/fake-strategies-store.ts index 3d2bb9005bc0..307a4f206fe2 100644 --- a/src/test/fixtures/fake-strategies-store.ts +++ b/src/test/fixtures/fake-strategies-store.ts @@ -8,7 +8,7 @@ import NotFoundError from '../../lib/error/notfound-error'; export default class FakeStrategiesStore implements IStrategyStore { count(): Promise { - throw new Error('Method not implemented.'); + return Promise.resolve(0); } defaultStrategy: IStrategy = {