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

chore: add metrics/gauges for "max constraint values" and "max constraints" (#7398)

This PR adds metrics tracking for:
- "maxConstraintValues": the highest number of constraint values that
are in use
- "maxConstraintsPerStrategy": the highest number of constraints used on
a strategy

It updates the existing feature strategy read model that returns max
metrics for other strategy-related things.

It also moves one test into a more fitting describe block.
This commit is contained in:
Thomas Heartman 2024-06-17 11:13:13 +02:00 committed by GitHub
parent 4eaa1525a0
commit c4e2159401
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 244 additions and 6 deletions

View File

@ -17,4 +17,20 @@ export class FakeFeatureStrategiesReadModel
} | null> {
return null;
}
async getMaxConstraintValues(): Promise<{
feature: string;
environment: string;
count: number;
} | null> {
return null;
}
async getMaxConstraintsPerStrategy(): Promise<{
feature: string;
environment: string;
count: number;
} | null> {
return null;
}
}

View File

@ -7,7 +7,6 @@ export class FeatureStrategiesReadModel implements IFeatureStrategiesReadModel {
constructor(db: Db) {
this.db = db;
}
async getMaxFeatureEnvironmentStrategies(): Promise<{
feature: string;
environment: string;
@ -47,4 +46,60 @@ export class FeatureStrategiesReadModel implements IFeatureStrategiesReadModel {
}
: null;
}
async getMaxConstraintValues(): Promise<{
feature: string;
environment: string;
count: number;
} | null> {
const rows = await this.db('feature_strategies')
.select(
'feature_name',
'environment',
this.db.raw(
"MAX(coalesce(jsonb_array_length(constraint_value->'values'), 0)) as max_values_count",
),
)
.from(
this.db.raw(
'feature_strategies, jsonb_array_elements(constraints) AS constraint_value',
),
)
.groupBy('feature_name', 'environment')
.orderBy('max_values_count', 'desc')
.limit(1);
return rows.length > 0
? {
feature: String(rows[0].feature_name),
environment: String(rows[0].environment),
count: Number(rows[0].max_values_count),
}
: null;
}
async getMaxConstraintsPerStrategy(): Promise<{
feature: string;
environment: string;
count: number;
} | null> {
const rows = await this.db('feature_strategies')
.select(
'feature_name',
'environment',
this.db.raw(
'jsonb_array_length(constraints) as constraint_count',
),
)
.orderBy('constraint_count', 'desc')
.limit(1);
return rows.length > 0
? {
feature: String(rows[0].feature_name),
environment: String(rows[0].environment),
count: Number(rows[0].constraint_count),
}
: null;
}
}

View File

@ -9,6 +9,7 @@ import type {
IProjectStore,
IUnleashStores,
} from '../../../types';
import { randomId } from '../../../util';
let stores: IUnleashStores;
let db: ITestDb;
@ -246,6 +247,9 @@ describe('strategy parameters default to sane defaults', () => {
});
expect(strategy.parameters.stickiness).toBe(defaultStickiness);
});
});
describe('max metrics collection', () => {
test('Read feature with max number of strategies', async () => {
const toggle = await featureToggleStore.create('default', {
name: 'featureA',
@ -289,4 +293,122 @@ describe('strategy parameters default to sane defaults', () => {
count: 2,
});
});
test('Read feature with max number of constraint values', async () => {
const flagA = await featureToggleStore.create('default', {
name: randomId(),
createdByUserId: 9999,
});
const flagB = await featureToggleStore.create('default', {
name: randomId(),
createdByUserId: 9999,
});
const maxConstraintValuesBefore =
await featureStrategiesReadModel.getMaxConstraintValues();
expect(maxConstraintValuesBefore).toBe(null);
const maxValueCount = 100;
await featureStrategiesStore.createStrategyFeatureEnv({
strategyName: 'gradualRollout',
projectId: 'default',
environment: 'default',
featureName: flagA.name,
constraints: [
{
values: ['only one'],
operator: 'IN',
contextName: 'appName',
},
{
values: Array.from({ length: maxValueCount }, (_, i) =>
i.toString(),
),
operator: 'IN',
contextName: 'appName',
},
],
sortOrder: 0,
parameters: {},
});
await featureStrategiesStore.createStrategyFeatureEnv({
strategyName: 'gradualRollout',
projectId: 'default',
environment: 'default',
featureName: flagB.name,
constraints: [
{
operator: 'IN',
contextName: 'appName',
},
],
sortOrder: 0,
parameters: {},
});
const maxConstraintValues =
await featureStrategiesReadModel.getMaxConstraintValues();
expect(maxConstraintValues).toEqual({
feature: flagA.name,
environment: 'default',
count: maxValueCount,
});
});
test('Read feature strategy with max number of constraints', async () => {
const flagA = await featureToggleStore.create('default', {
name: randomId(),
createdByUserId: 9999,
});
const flagB = await featureToggleStore.create('default', {
name: randomId(),
createdByUserId: 9999,
});
const maxConstraintValuesBefore =
await featureStrategiesReadModel.getMaxConstraintsPerStrategy();
expect(maxConstraintValuesBefore).toBe(null);
await featureStrategiesStore.createStrategyFeatureEnv({
strategyName: 'gradualRollout',
projectId: 'default',
environment: 'default',
featureName: flagA.name,
constraints: [
{
values: ['blah'],
operator: 'IN',
contextName: 'appName',
},
{
values: ['blah'],
operator: 'IN',
contextName: 'appName',
},
],
sortOrder: 0,
parameters: {},
});
await featureStrategiesStore.createStrategyFeatureEnv({
strategyName: 'gradualRollout',
projectId: 'default',
environment: 'default',
featureName: flagB.name,
constraints: [],
sortOrder: 0,
parameters: {},
});
const maxConstraintValues =
await featureStrategiesReadModel.getMaxConstraintsPerStrategy();
expect(maxConstraintValues).toEqual({
feature: flagA.name,
environment: 'default',
count: 2,
});
});
});

View File

@ -8,4 +8,14 @@ export interface IFeatureStrategiesReadModel {
feature: string;
count: number;
} | null>;
getMaxConstraintValues(): Promise<{
feature: string;
environment: string;
count: number;
} | null>;
getMaxConstraintsPerStrategy(): Promise<{
feature: string;
environment: string;
count: number;
} | null>;
}

View File

@ -119,6 +119,16 @@ export default class MetricsMonitor {
help: 'Maximum number of strategies in one feature',
labelNames: ['feature'],
});
const maxConstraintValues = createGauge({
name: 'max_constraint_values',
help: 'Maximum number of constraint values used in a single constraint',
labelNames: ['feature', 'environment'],
});
const maxConstraintsPerStrategy = createGauge({
name: 'max_strategy_constraints',
help: 'Maximum number of constraints used on a single strategy',
labelNames: ['feature', 'environment'],
});
const featureTogglesArchivedTotal = createGauge({
name: 'feature_toggles_archived_total',
@ -284,11 +294,17 @@ 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(),
]);
const [
maxStrategies,
maxEnvironmentStrategies,
maxConstraintValuesResult,
maxConstraintsPerStrategyResult,
] = await Promise.all([
stores.featureStrategiesReadModel.getMaxFeatureStrategies(),
stores.featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies(),
stores.featureStrategiesReadModel.getMaxConstraintValues(),
stores.featureStrategiesReadModel.getMaxConstraintsPerStrategy(),
]);
featureFlagsTotal.reset();
featureFlagsTotal.labels({ version }).set(stats.featureToggles);
@ -332,6 +348,25 @@ export default class MetricsMonitor {
.labels({ feature: maxStrategies.feature })
.set(maxStrategies.count);
}
if (maxConstraintValuesResult) {
maxConstraintValues.reset();
maxConstraintValues
.labels({
environment: maxConstraintValuesResult.environment,
feature: maxConstraintValuesResult.feature,
})
.set(maxConstraintValuesResult.count);
}
if (maxConstraintsPerStrategyResult) {
maxConstraintsPerStrategy.reset();
maxConstraintsPerStrategy
.labels({
environment:
maxConstraintsPerStrategyResult.environment,
feature: maxConstraintsPerStrategyResult.feature,
})
.set(maxConstraintsPerStrategyResult.count);
}
enabledMetricsBucketsPreviousDay.reset();
enabledMetricsBucketsPreviousDay.set(