From 36d1fad122ef4d6b56cedfc13385392cb392fa0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Fri, 15 Nov 2024 19:16:22 +0100 Subject: [PATCH] chore: use memoized stats for version --- .../instance-stats/instance-stats-service.ts | 155 +++++++++++++++--- .../features/scheduler/schedule-services.ts | 9 +- src/lib/services/version-service.test.ts | 16 +- src/lib/services/version-service.ts | 8 +- 4 files changed, 157 insertions(+), 31 deletions(-) diff --git a/src/lib/features/instance-stats/instance-stats-service.ts b/src/lib/features/instance-stats/instance-stats-service.ts index b4d110604266..a0e220c17823 100644 --- a/src/lib/features/instance-stats/instance-stats-service.ts +++ b/src/lib/features/instance-stats/instance-stats-service.ts @@ -6,6 +6,7 @@ import type { IClientMetricsStoreV2, IEventStore, IFeatureStrategiesReadModel, + IFeatureStrategiesStore, ITrafficDataUsageStore, IUnleashStores, } from '../../types/stores'; @@ -33,6 +34,7 @@ import type { GetProductionChanges } from './getProductionChanges'; import { format, minutesToMilliseconds } from 'date-fns'; import memoizee from 'memoizee'; import type { GetLicensedUsers } from './getLicensedUsers'; +import type { IFeatureUsageInfo } from '../../services/version-service'; export type TimeRange = 'allTime' | '30d' | '7d'; @@ -124,6 +126,8 @@ export class InstanceStatsService { private featureStrategiesReadModel: IFeatureStrategiesReadModel; + private featureStrategiesStore: IFeatureStrategiesStore; + private trafficDataUsageStore: ITrafficDataUsageStore; constructor( @@ -186,9 +190,15 @@ export class InstanceStatsService { this.eventStore = eventStore; this.clientInstanceStore = clientInstanceStore; this.logger = getLogger('services/stats-service.js'); - this.getActiveUsers = getActiveUsers; - this.getLicencedUsers = getLicencedUsers; - this.getProductionChanges = getProductionChanges; + this.getActiveUsers = () => + this.memorize('getActiveUsers', getActiveUsers.bind(this)); + this.getLicencedUsers = () => + this.memorize('getLicencedUsers', getLicencedUsers.bind(this)); + this.getProductionChanges = () => + this.memorize( + 'getProductionChanges', + getProductionChanges.bind(this), + ); this.apiTokenStore = apiTokenStore; this.clientMetricsStore = clientMetricsStoreV2; this.flagResolver = flagResolver; @@ -324,8 +334,8 @@ export class InstanceStatsService { this.getRegisteredUsers(), this.countServiceAccounts(), this.countApiTokensByType(), - this.memorize('getActiveUsers', this.getActiveUsers.bind(this)), - this.memorize('getLicencedUsers', this.getLicencedUsers.bind(this)), + this.getActiveUsers(), + this.getLicencedUsers(), this.getProjectModeCount(), this.contextFieldCount(), this.groupCount(), @@ -340,20 +350,9 @@ export class InstanceStatsService { this.hasPasswordAuth(), this.hasSCIM(), this.appCount ? this.appCount : this.getLabeledAppCounts(), - this.memorize('deprecatedFilteredCountFeaturesExported', () => - this.eventStore.deprecatedFilteredCount({ - type: FEATURES_EXPORTED, - }), - ), - this.memorize('deprecatedFilteredCountFeaturesImported', () => - this.eventStore.deprecatedFilteredCount({ - type: FEATURES_IMPORTED, - }), - ), - this.memorize( - 'getProductionChanges', - this.getProductionChanges.bind(this), - ), + this.featuresExported(), + this.featuresImported(), + this.getProductionChanges(), this.countPreviousDayHourlyMetricsBuckets(), this.memorize( 'maxFeatureEnvironmentStrategies', @@ -414,6 +413,124 @@ export class InstanceStatsService { }; } + async getFeatureUsageInfo(): Promise { + const [ + featureToggles, + users, + projectModeCount, + contextFields, + groups, + roles, + customRootRoles, + customRootRolesInUse, + environments, + segments, + strategies, + SAMLenabled, + OIDCenabled, + featureExports, + featureImports, + customStrategies, + customStrategiesInUse, + userActive, + productionChanges, + postgresVersion, + ] = await Promise.all([ + this.getToggleCount(), + this.getRegisteredUsers(), + this.getProjectModeCount(), + this.contextFieldCount(), + this.groupCount(), + this.roleCount(), + this.customRolesCount(), + this.customRolesCountInUse(), + this.environmentCount(), + this.segmentCount(), + this.strategiesCount(), + this.hasSAML(), + this.hasOIDC(), + this.featuresExported(), + this.featuresImported(), + this.customStrategiesCount(), + this.customStrategiesInUseCount(), + this.getActiveUsers(), + this.getProductionChanges(), + this.postgresVersion(), + ]); + const versionInfo = await this.versionService.getVersionInfo(); + + const featureInfo = { + featureToggles, + users, + projects: projectModeCount + .map((p) => p.count) + .reduce((a, b) => a + b, 0), + contextFields, + groups, + roles, + customRootRoles, + customRootRolesInUse, + environments, + segments, + strategies, + SAMLenabled, + OIDCenabled, + featureExports, + featureImports, + customStrategies, + customStrategiesInUse, + instanceId: versionInfo.instanceId, + versionOSS: versionInfo.current.oss, + versionEnterprise: versionInfo.current.enterprise, + activeUsers30: userActive.last30, + activeUsers60: userActive.last60, + activeUsers90: userActive.last90, + productionChanges30: productionChanges.last30, + productionChanges60: productionChanges.last60, + productionChanges90: productionChanges.last90, + postgresVersion, + }; + return featureInfo; + } + + featuresExported(): Promise { + return this.memorize('deprecatedFilteredCountFeaturesExported', () => + this.eventStore.deprecatedFilteredCount({ + type: FEATURES_EXPORTED, + }), + ); + } + + featuresImported(): Promise { + return this.memorize('deprecatedFilteredCountFeaturesImported', () => + this.eventStore.deprecatedFilteredCount({ + type: FEATURES_IMPORTED, + }), + ); + } + + customStrategiesCount(): Promise { + return this.memorize( + 'customStrategiesCount', + async () => + (await this.strategyStore.getEditableStrategies()).length, + ); + } + + customStrategiesInUseCount(): Promise { + return this.memorize( + 'customStrategiesInUseCount', + async () => + await this.featureStrategiesStore.getCustomStrategiesInUseCount(), + ); + } + + postgresVersion(): Promise { + return this.memorize('postgresVersion', () => + this.settingStore.postgresVersion(), + ); + } + groupCount(): Promise { return this.memorize('groupCount', () => this.groupStore.count()); } diff --git a/src/lib/features/scheduler/schedule-services.ts b/src/lib/features/scheduler/schedule-services.ts index 69d3a218fc1e..7910043a2fb6 100644 --- a/src/lib/features/scheduler/schedule-services.ts +++ b/src/lib/features/scheduler/schedule-services.ts @@ -126,7 +126,14 @@ export const scheduleServices = async ( ); schedulerService.schedule( - versionService.checkLatestVersion.bind(versionService), + () => + versionService.checkLatestVersion(() => + config.flagResolver.isEnabled('memorizeStats', { + memoryKey: 'getFeatureUsageInfo', + }) + ? instanceStatsService.getFeatureUsageInfo() + : versionService.getFeatureUsageInfo(), + ), hoursToMilliseconds(48), 'checkLatestVersion', ); diff --git a/src/lib/services/version-service.test.ts b/src/lib/services/version-service.test.ts index 57371ba279b8..960a4c0ea029 100644 --- a/src/lib/services/version-service.test.ts +++ b/src/lib/services/version-service.test.ts @@ -43,7 +43,7 @@ test('yields current versions', async () => { createFakeGetActiveUsers(), createFakeGetProductionChanges(), ); - await service.checkLatestVersion(); + await service.checkLatestVersion(() => service.getFeatureUsageInfo()); const versionInfo = await service.getVersionInfo(); expect(scope.isDone()).toEqual(true); expect(versionInfo.current.oss).toBe(version); @@ -82,7 +82,7 @@ test('supports setting enterprise version as well', async () => { createFakeGetActiveUsers(), createFakeGetProductionChanges(), ); - await service.checkLatestVersion(); + await service.checkLatestVersion(() => service.getFeatureUsageInfo()); const versionInfo = await service.getVersionInfo(); expect(scope.isDone()).toEqual(true); expect(versionInfo.current.oss).toBe(version); @@ -121,7 +121,7 @@ test('if version check is not enabled should not make any calls', async () => { createFakeGetActiveUsers(), createFakeGetProductionChanges(), ); - await service.checkLatestVersion(); + await service.checkLatestVersion(() => service.getFeatureUsageInfo()); const versionInfo = await service.getVersionInfo(); expect(scope.isDone()).toEqual(false); expect(versionInfo.current.oss).toBe(version); @@ -168,7 +168,7 @@ test('sets featureinfo', async () => { createFakeGetActiveUsers(), createFakeGetProductionChanges(), ); - await service.checkLatestVersion(); + await service.checkLatestVersion(() => service.getFeatureUsageInfo()); expect(scope.isDone()).toEqual(true); nock.cleanAll(); }); @@ -225,7 +225,7 @@ test('counts toggles', async () => { createFakeGetActiveUsers(), createFakeGetProductionChanges(), ); - await service.checkLatestVersion(); + await service.checkLatestVersion(() => service.getFeatureUsageInfo()); expect(scope.isDone()).toEqual(true); nock.cleanAll(); }); @@ -297,7 +297,7 @@ test('counts custom strategies', async () => { createFakeGetActiveUsers(), createFakeGetProductionChanges(), ); - await service.checkLatestVersion(); + await service.checkLatestVersion(() => service.getFeatureUsageInfo()); expect(scope.isDone()).toEqual(true); nock.cleanAll(); }); @@ -346,7 +346,7 @@ test('counts active users', async () => { fakeActiveUsers, fakeProductionChanges, ); - await service.checkLatestVersion(); + await service.checkLatestVersion(() => service.getFeatureUsageInfo()); expect(scope.isDone()).toEqual(true); nock.cleanAll(); }); @@ -393,7 +393,7 @@ test('Counts production changes', async () => { fakeActiveUsers, fakeProductionChanges, ); - await service.checkLatestVersion(); + await service.checkLatestVersion(() => service.getFeatureUsageInfo()); expect(scope.isDone()).toEqual(true); nock.cleanAll(); }); diff --git a/src/lib/services/version-service.ts b/src/lib/services/version-service.ts index 4e2648e638e3..42bd2f3146c9 100644 --- a/src/lib/services/version-service.ts +++ b/src/lib/services/version-service.ts @@ -199,7 +199,9 @@ export default class VersionService { return this.instanceId; } - async checkLatestVersion(): Promise { + async checkLatestVersion( + telemetryDataProvider: () => Promise, + ): Promise { const instanceId = await this.getInstanceId(); this.logger.debug( `Checking for newest version for instanceId=${instanceId}`, @@ -212,8 +214,7 @@ export default class VersionService { }; if (this.telemetryEnabled) { - versionPayload.featureInfo = - await this.getFeatureUsageInfo(); + versionPayload.featureInfo = await telemetryDataProvider(); } if (this.versionCheckUrl) { const res = await fetch(this.versionCheckUrl, { @@ -242,6 +243,7 @@ export default class VersionService { } } + /** @deprecated look into stats service getFeatureUsageInfo */ async getFeatureUsageInfo(): Promise { const [ featureToggles,