1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat: Max strategies metrics (#7392)

This commit is contained in:
Mateusz Kwasniewski 2024-06-14 09:20:43 +02:00 committed by GitHub
parent 8e64303ef6
commit 0c79b36b74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 174 additions and 1 deletions

View File

@ -46,6 +46,7 @@ import { SegmentReadModel } from '../features/segment/segment-read-model';
import { ProjectOwnersReadModel } from '../features/project/project-owners-read-model'; import { ProjectOwnersReadModel } from '../features/project/project-owners-read-model';
import { FeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store'; import { FeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store';
import { ProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model'; import { ProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model';
import { FeatureStrategiesReadModel } from '../features/feature-toggle/feature-strategies-read-model';
export const createStores = ( export const createStores = (
config: IUnleashConfig, config: IUnleashConfig,
@ -159,6 +160,7 @@ export const createStores = (
projectOwnersReadModel: new ProjectOwnersReadModel(db), projectOwnersReadModel: new ProjectOwnersReadModel(db),
projectFlagCreatorsReadModel: new ProjectFlagCreatorsReadModel(db), projectFlagCreatorsReadModel: new ProjectFlagCreatorsReadModel(db),
featureLifecycleStore: new FeatureLifecycleStore(db), featureLifecycleStore: new FeatureLifecycleStore(db),
featureStrategiesReadModel: new FeatureStrategiesReadModel(db),
}; };
}; };

View File

@ -0,0 +1,20 @@
import type { IFeatureStrategiesReadModel } from './types/feature-strategies-read-model-type';
export class FakeFeatureStrategiesReadModel
implements IFeatureStrategiesReadModel
{
async getMaxFeatureEnvironmentStrategies(): Promise<{
feature: string;
environment: string;
count: number;
} | null> {
return null;
}
async getMaxFeatureStrategies(): Promise<{
feature: string;
count: number;
} | null> {
return null;
}
}

View File

@ -0,0 +1,50 @@
import type { Db } from '../../db/db';
import type { IFeatureStrategiesReadModel } from './types/feature-strategies-read-model-type';
export class FeatureStrategiesReadModel implements IFeatureStrategiesReadModel {
private db: Db;
constructor(db: Db) {
this.db = db;
}
async getMaxFeatureEnvironmentStrategies(): Promise<{
feature: string;
environment: string;
count: number;
} | null> {
const rows = await this.db('feature_strategies')
.select('feature_name', 'environment')
.count('id as strategy_count')
.groupBy('feature_name', 'environment')
.orderBy('strategy_count', 'desc')
.limit(1);
return rows.length > 0
? {
feature: String(rows[0].feature_name),
environment: String(rows[0].environment),
count: Number(rows[0].strategy_count),
}
: null;
}
async getMaxFeatureStrategies(): Promise<{
feature: string;
count: number;
} | null> {
const rows = await this.db('feature_strategies')
.select('feature_name')
.count('id as strategy_count')
.groupBy('feature_name')
.orderBy('strategy_count', 'desc')
.limit(1);
return rows.length > 0
? {
feature: String(rows[0].feature_name),
count: Number(rows[0].strategy_count),
}
: null;
}
}

View File

@ -4,13 +4,18 @@ import dbInit, {
type ITestDb, type ITestDb,
} from '../../../../test/e2e/helpers/database-init'; } from '../../../../test/e2e/helpers/database-init';
import getLogger from '../../../../test/fixtures/no-logger'; import getLogger from '../../../../test/fixtures/no-logger';
import type { IProjectStore, IUnleashStores } from '../../../types'; import type {
IFeatureStrategiesReadModel,
IProjectStore,
IUnleashStores,
} from '../../../types';
let stores: IUnleashStores; let stores: IUnleashStores;
let db: ITestDb; let db: ITestDb;
let featureStrategiesStore: IFeatureStrategiesStore; let featureStrategiesStore: IFeatureStrategiesStore;
let featureToggleStore: IFeatureToggleStore; let featureToggleStore: IFeatureToggleStore;
let projectStore: IProjectStore; let projectStore: IProjectStore;
let featureStrategiesReadModel: IFeatureStrategiesReadModel;
const featureName = 'test-strategies-move-project'; const featureName = 'test-strategies-move-project';
@ -20,12 +25,17 @@ beforeAll(async () => {
featureStrategiesStore = stores.featureStrategiesStore; featureStrategiesStore = stores.featureStrategiesStore;
featureToggleStore = stores.featureToggleStore; featureToggleStore = stores.featureToggleStore;
projectStore = stores.projectStore; projectStore = stores.projectStore;
featureStrategiesReadModel = stores.featureStrategiesReadModel;
await featureToggleStore.create('default', { await featureToggleStore.create('default', {
name: featureName, name: featureName,
createdByUserId: 9999, createdByUserId: 9999,
}); });
}); });
afterEach(async () => {
await featureStrategiesStore.deleteAll();
});
afterAll(async () => { afterAll(async () => {
await db.destroy(); await db.destroy();
}); });
@ -236,4 +246,47 @@ describe('strategy parameters default to sane defaults', () => {
}); });
expect(strategy.parameters.stickiness).toBe(defaultStickiness); expect(strategy.parameters.stickiness).toBe(defaultStickiness);
}); });
test('Read feature with max number of strategies', async () => {
const toggle = await featureToggleStore.create('default', {
name: 'featureA',
createdByUserId: 9999,
});
const maxStrategiesBefore =
await featureStrategiesReadModel.getMaxFeatureStrategies();
const maxEnvStrategiesBefore =
await featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies();
expect(maxStrategiesBefore).toBe(null);
expect(maxEnvStrategiesBefore).toBe(null);
await featureStrategiesStore.createStrategyFeatureEnv({
strategyName: 'gradualRollout',
projectId: 'default',
environment: 'default',
featureName: toggle.name,
constraints: [],
sortOrder: 0,
parameters: {},
});
await featureStrategiesStore.createStrategyFeatureEnv({
strategyName: 'gradualRollout',
projectId: 'default',
environment: 'default',
featureName: toggle.name,
constraints: [],
sortOrder: 0,
parameters: {},
});
const maxStrategies =
await featureStrategiesReadModel.getMaxFeatureStrategies();
const maxEnvStrategies =
await featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies();
expect(maxStrategies).toEqual({ feature: 'featureA', count: 2 });
expect(maxEnvStrategies).toEqual({
feature: 'featureA',
environment: 'default',
count: 2,
});
});
}); });

View File

@ -0,0 +1,11 @@
export interface IFeatureStrategiesReadModel {
getMaxFeatureEnvironmentStrategies(): Promise<{
feature: string;
environment: string;
count: number;
} | null>;
getMaxFeatureStrategies(): Promise<{
feature: string;
count: number;
} | null>;
}

View File

@ -109,6 +109,16 @@ export default class MetricsMonitor {
help: 'Number of feature flags', help: 'Number of feature flags',
labelNames: ['version'], labelNames: ['version'],
}); });
const maxFeatureEnvironmentStrategies = createGauge({
name: 'max_feature_environment_strategies',
help: 'Maximum number of environment strategies in one feature',
labelNames: ['feature', 'environment'],
});
const maxFeatureStrategies = createGauge({
name: 'max_feature_strategies',
help: 'Maximum number of strategies in one feature',
labelNames: ['feature'],
});
const featureTogglesArchivedTotal = createGauge({ const featureTogglesArchivedTotal = createGauge({
name: 'feature_toggles_archived_total', name: 'feature_toggles_archived_total',
@ -274,6 +284,11 @@ export default class MetricsMonitor {
async function collectStaticCounters() { async function collectStaticCounters() {
try { try {
const stats = await instanceStatsService.getStats(); const stats = await instanceStatsService.getStats();
const [maxStrategies, maxEnvironmentStrategies] =
await Promise.all([
stores.featureStrategiesReadModel.getMaxFeatureStrategies(),
stores.featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies(),
]);
featureFlagsTotal.reset(); featureFlagsTotal.reset();
featureFlagsTotal.labels({ version }).set(stats.featureToggles); featureFlagsTotal.labels({ version }).set(stats.featureToggles);
@ -302,6 +317,22 @@ export default class MetricsMonitor {
apiTokens.labels({ type }).set(value); apiTokens.labels({ type }).set(value);
} }
if (maxEnvironmentStrategies) {
maxFeatureEnvironmentStrategies.reset();
maxFeatureEnvironmentStrategies
.labels({
environment: maxEnvironmentStrategies.environment,
feature: maxEnvironmentStrategies.feature,
})
.set(maxEnvironmentStrategies.count);
}
if (maxStrategies) {
maxFeatureStrategies.reset();
maxFeatureStrategies
.labels({ feature: maxStrategies.feature })
.set(maxStrategies.count);
}
enabledMetricsBucketsPreviousDay.reset(); enabledMetricsBucketsPreviousDay.reset();
enabledMetricsBucketsPreviousDay.set( enabledMetricsBucketsPreviousDay.set(
stats.previousDayMetricsBucketsCount.enabledCount, stats.previousDayMetricsBucketsCount.enabledCount,
@ -426,6 +457,7 @@ export default class MetricsMonitor {
); );
} catch (e) {} } catch (e) {}
} }
await schedulerService.schedule( await schedulerService.schedule(
collectStaticCounters.bind(this), collectStaticCounters.bind(this),
hoursToMilliseconds(2), hoursToMilliseconds(2),

View File

@ -43,6 +43,7 @@ import { ISegmentReadModel } from '../features/segment/segment-read-model-type';
import { IProjectOwnersReadModel } from '../features/project/project-owners-read-model.type'; import { IProjectOwnersReadModel } from '../features/project/project-owners-read-model.type';
import { IFeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store-type'; import { IFeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store-type';
import { IProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model.type'; import { IProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model.type';
import { IFeatureStrategiesReadModel } from '../features/feature-toggle/types/feature-strategies-read-model-type';
export interface IUnleashStores { export interface IUnleashStores {
accessStore: IAccessStore; accessStore: IAccessStore;
@ -90,6 +91,7 @@ export interface IUnleashStores {
projectOwnersReadModel: IProjectOwnersReadModel; projectOwnersReadModel: IProjectOwnersReadModel;
projectFlagCreatorsReadModel: IProjectFlagCreatorsReadModel; projectFlagCreatorsReadModel: IProjectFlagCreatorsReadModel;
featureLifecycleStore: IFeatureLifecycleStore; featureLifecycleStore: IFeatureLifecycleStore;
featureStrategiesReadModel: IFeatureStrategiesReadModel;
} }
export { export {
@ -136,4 +138,5 @@ export {
IProjectOwnersReadModel, IProjectOwnersReadModel,
IFeatureLifecycleStore, IFeatureLifecycleStore,
IProjectFlagCreatorsReadModel, IProjectFlagCreatorsReadModel,
IFeatureStrategiesReadModel,
}; };

View File

@ -46,6 +46,7 @@ import { FakeSegmentReadModel } from '../../lib/features/segment/fake-segment-re
import { FakeProjectOwnersReadModel } from '../../lib/features/project/fake-project-owners-read-model'; import { FakeProjectOwnersReadModel } from '../../lib/features/project/fake-project-owners-read-model';
import { FakeFeatureLifecycleStore } from '../../lib/features/feature-lifecycle/fake-feature-lifecycle-store'; import { FakeFeatureLifecycleStore } from '../../lib/features/feature-lifecycle/fake-feature-lifecycle-store';
import { FakeProjectFlagCreatorsReadModel } from '../../lib/features/project/fake-project-flag-creators-read-model'; import { FakeProjectFlagCreatorsReadModel } from '../../lib/features/project/fake-project-flag-creators-read-model';
import { FakeFeatureStrategiesReadModel } from '../../lib/features/feature-toggle/fake-feature-strategies-read-model';
const db = { const db = {
select: () => ({ select: () => ({
@ -101,6 +102,7 @@ const createStores: () => IUnleashStores = () => {
projectOwnersReadModel: new FakeProjectOwnersReadModel(), projectOwnersReadModel: new FakeProjectOwnersReadModel(),
projectFlagCreatorsReadModel: new FakeProjectFlagCreatorsReadModel(), projectFlagCreatorsReadModel: new FakeProjectFlagCreatorsReadModel(),
featureLifecycleStore: new FakeFeatureLifecycleStore(), featureLifecycleStore: new FakeFeatureLifecycleStore(),
featureStrategiesReadModel: new FakeFeatureStrategiesReadModel(),
}; };
}; };