Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
57 changes: 56 additions & 1 deletion src/lib/features/feature-toggle/feature-strategies-read-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export class FeatureStrategiesReadModel implements IFeatureStrategiesReadModel {
constructor(db: Db) {
this.db = db;
}

async getMaxFeatureEnvironmentStrategies(): Promise<{
feature: string;
environment: string;
Expand Down Expand Up @@ -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',
'constraints',
this.db.raw(
thomasheartman marked this conversation as resolved.
Show resolved Hide resolved
'MAX(jsonb_array_length(constraints)) as constraint_count',
),
this.db.raw(
"MAX(coalesce(jsonb_array_length(value->'values'), 0)) as max_values_count",
),
)
.joinRaw('JOIN jsonb_array_elements(constraints) as value ON true')
thomasheartman marked this conversation as resolved.
Show resolved Hide resolved
.groupBy('feature_name', 'environment', 'constraints')
.orderBy('max_values_count', 'desc')
.limit(2);

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
IProjectStore,
IUnleashStores,
} from '../../../types';
import { randomId } from '../../../util';

let stores: IUnleashStores;
let db: ITestDb;
Expand Down Expand Up @@ -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 () => {
thomasheartman marked this conversation as resolved.
Show resolved Hide resolved
const toggle = await featureToggleStore.create('default', {
name: 'featureA',
Expand Down Expand Up @@ -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,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
}
45 changes: 40 additions & 5 deletions src/lib/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(
Expand Down
Loading