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 { FeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store';
import { ProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model';
import { FeatureStrategiesReadModel } from '../features/feature-toggle/feature-strategies-read-model';
export const createStores = (
config: IUnleashConfig,
@ -159,6 +160,7 @@ export const createStores = (
projectOwnersReadModel: new ProjectOwnersReadModel(db),
projectFlagCreatorsReadModel: new ProjectFlagCreatorsReadModel(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,
} from '../../../../test/e2e/helpers/database-init';
import getLogger from '../../../../test/fixtures/no-logger';
import type { IProjectStore, IUnleashStores } from '../../../types';
import type {
IFeatureStrategiesReadModel,
IProjectStore,
IUnleashStores,
} from '../../../types';
let stores: IUnleashStores;
let db: ITestDb;
let featureStrategiesStore: IFeatureStrategiesStore;
let featureToggleStore: IFeatureToggleStore;
let projectStore: IProjectStore;
let featureStrategiesReadModel: IFeatureStrategiesReadModel;
const featureName = 'test-strategies-move-project';
@ -20,12 +25,17 @@ beforeAll(async () => {
featureStrategiesStore = stores.featureStrategiesStore;
featureToggleStore = stores.featureToggleStore;
projectStore = stores.projectStore;
featureStrategiesReadModel = stores.featureStrategiesReadModel;
await featureToggleStore.create('default', {
name: featureName,
createdByUserId: 9999,
});
});
afterEach(async () => {
await featureStrategiesStore.deleteAll();
});
afterAll(async () => {
await db.destroy();
});
@ -236,4 +246,47 @@ describe('strategy parameters default to sane defaults', () => {
});
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',
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({
name: 'feature_toggles_archived_total',
@ -274,6 +284,11 @@ export default class MetricsMonitor {
async function collectStaticCounters() {
try {
const stats = await instanceStatsService.getStats();
const [maxStrategies, maxEnvironmentStrategies] =
await Promise.all([
stores.featureStrategiesReadModel.getMaxFeatureStrategies(),
stores.featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies(),
]);
featureFlagsTotal.reset();
featureFlagsTotal.labels({ version }).set(stats.featureToggles);
@ -302,6 +317,22 @@ export default class MetricsMonitor {
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.set(
stats.previousDayMetricsBucketsCount.enabledCount,
@ -426,6 +457,7 @@ export default class MetricsMonitor {
);
} catch (e) {}
}
await schedulerService.schedule(
collectStaticCounters.bind(this),
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 { IFeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store-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 {
accessStore: IAccessStore;
@ -90,6 +91,7 @@ export interface IUnleashStores {
projectOwnersReadModel: IProjectOwnersReadModel;
projectFlagCreatorsReadModel: IProjectFlagCreatorsReadModel;
featureLifecycleStore: IFeatureLifecycleStore;
featureStrategiesReadModel: IFeatureStrategiesReadModel;
}
export {
@ -136,4 +138,5 @@ export {
IProjectOwnersReadModel,
IFeatureLifecycleStore,
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 { FakeFeatureLifecycleStore } from '../../lib/features/feature-lifecycle/fake-feature-lifecycle-store';
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 = {
select: () => ({
@ -101,6 +102,7 @@ const createStores: () => IUnleashStores = () => {
projectOwnersReadModel: new FakeProjectOwnersReadModel(),
projectFlagCreatorsReadModel: new FakeProjectFlagCreatorsReadModel(),
featureLifecycleStore: new FakeFeatureLifecycleStore(),
featureStrategiesReadModel: new FakeFeatureStrategiesReadModel(),
};
};