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

feat: include environment type label in feature_toggle_update metrics (#5809)

This is needed in order to identify what type of an environment a toggle
is updated in. This can be test, development, pre-production or
production.
This commit is contained in:
Gard Rimestad 2024-01-09 16:33:00 +01:00 committed by GitHub
parent 2590b4eaea
commit 24b202ef0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 99 additions and 18 deletions

View File

@ -6,6 +6,7 @@ import { REQUEST_TIME, DB_TIME } from './metric-events';
import { import {
CLIENT_METRICS, CLIENT_METRICS,
CLIENT_REGISTER, CLIENT_REGISTER,
FEATURE_ENVIRONMENT_ENABLED,
FEATURE_UPDATED, FEATURE_UPDATED,
} from './types/events'; } from './types/events';
import { createMetricsMonitor } from './metrics'; import { createMetricsMonitor } from './metrics';
@ -14,11 +15,14 @@ import { InstanceStatsService } from './features/instance-stats/instance-stats-s
import VersionService from './services/version-service'; import VersionService from './services/version-service';
import { createFakeGetActiveUsers } from './features/instance-stats/getActiveUsers'; import { createFakeGetActiveUsers } from './features/instance-stats/getActiveUsers';
import { createFakeGetProductionChanges } from './features/instance-stats/getProductionChanges'; import { createFakeGetProductionChanges } from './features/instance-stats/getProductionChanges';
import { IEnvironmentStore } from './types';
import FakeEnvironmentStore from './features/project-environments/fake-environment-store';
const monitor = createMetricsMonitor(); const monitor = createMetricsMonitor();
const eventBus = new EventEmitter(); const eventBus = new EventEmitter();
const prometheusRegister = register; const prometheusRegister = register;
let eventStore: IEventStore; let eventStore: IEventStore;
let environmentStore: IEnvironmentStore;
let statsService: InstanceStatsService; let statsService: InstanceStatsService;
let stores; let stores;
beforeAll(() => { beforeAll(() => {
@ -29,6 +33,8 @@ beforeAll(() => {
}); });
stores = createStores(); stores = createStores();
eventStore = stores.eventStore; eventStore = stores.eventStore;
environmentStore = new FakeEnvironmentStore();
stores.environmentStore = environmentStore;
const versionService = new VersionService( const versionService = new VersionService(
stores, stores,
config, config,
@ -93,7 +99,32 @@ test('should collect metrics for updated toggles', async () => {
const metrics = await prometheusRegister.metrics(); const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch( expect(metrics).toMatch(
/feature_toggle_update_total\{toggle="TestToggle",project="default",environment="default"\} 1/, /feature_toggle_update_total\{toggle="TestToggle",project="default",environment="default",environmentType="production"\} 1/,
);
});
test('should set environmentType when toggle is flipped', async () => {
await environmentStore.create({
name: 'testEnvironment',
enabled: true,
type: 'testType',
sortOrder: 1,
});
stores.eventStore.emit(FEATURE_ENVIRONMENT_ENABLED, {
featureName: 'TestToggle',
project: 'default',
environment: 'testEnvironment',
data: { name: 'TestToggle' },
});
// Wait for event to be processed, not nice, but it works.
await new Promise((done) => {
setTimeout(done, 1);
});
const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch(
/feature_toggle_update_total\{toggle="TestToggle",project="default",environment="testEnvironment",environmentType="testType"\} 1/,
); );
}); });

View File

@ -1,4 +1,5 @@
import client from 'prom-client'; import client from 'prom-client';
import memoizee from 'memoizee';
import EventEmitter from 'events'; import EventEmitter from 'events';
import { Knex } from 'knex'; import { Knex } from 'knex';
import * as events from './metric-events'; import * as events from './metric-events';
@ -19,11 +20,12 @@ import {
CLIENT_REGISTER, CLIENT_REGISTER,
} from './types/events'; } from './types/events';
import { IUnleashConfig } from './types/option'; import { IUnleashConfig } from './types/option';
import { IUnleashStores } from './types/stores'; import { IEnvironmentStore, IUnleashStores } from './types/stores';
import { hoursToMilliseconds, minutesToMilliseconds } from 'date-fns'; import { hoursToMilliseconds, minutesToMilliseconds } from 'date-fns';
import Timer = NodeJS.Timer; import Timer = NodeJS.Timer;
import { InstanceStatsService } from './features/instance-stats/instance-stats-service'; import { InstanceStatsService } from './features/instance-stats/instance-stats-service';
import { ValidatedClientMetrics } from './services/client-metrics/schema'; import { ValidatedClientMetrics } from './services/client-metrics/schema';
import { IEnvironment } from './types';
export default class MetricsMonitor { export default class MetricsMonitor {
timer?: Timer; timer?: Timer;
@ -47,7 +49,15 @@ export default class MetricsMonitor {
return Promise.resolve(); return Promise.resolve();
} }
const { eventStore } = stores; const { eventStore, environmentStore } = stores;
const cachedEnvironments: () => Promise<IEnvironment[]> = memoizee(
async () => environmentStore.getAll(),
{
promise: true,
maxAge: hoursToMilliseconds(1),
},
);
client.collectDefaultMetrics(); client.collectDefaultMetrics();
@ -78,7 +88,7 @@ export default class MetricsMonitor {
const featureToggleUpdateTotal = new client.Counter({ const featureToggleUpdateTotal = new client.Counter({
name: 'feature_toggle_update_total', name: 'feature_toggle_update_total',
help: 'Number of times a toggle has been updated. Environment label would be "n/a" when it is not available, e.g. when a feature toggle is created.', help: 'Number of times a toggle has been updated. Environment label would be "n/a" when it is not available, e.g. when a feature toggle is created.',
labelNames: ['toggle', 'project', 'environment'], labelNames: ['toggle', 'project', 'environment', 'environmentType'],
}); });
const featureToggleUsageTotal = new client.Counter({ const featureToggleUsageTotal = new client.Counter({
name: 'feature_toggle_usage_total', name: 'feature_toggle_usage_total',
@ -360,56 +370,82 @@ export default class MetricsMonitor {
}); });
eventStore.on(FEATURE_CREATED, ({ featureName, project }) => { eventStore.on(FEATURE_CREATED, ({ featureName, project }) => {
featureToggleUpdateTotal.labels(featureName, project, 'n/a').inc(); featureToggleUpdateTotal
.labels(featureName, project, 'n/a', 'n/a')
.inc();
}); });
eventStore.on(FEATURE_VARIANTS_UPDATED, ({ featureName, project }) => { eventStore.on(FEATURE_VARIANTS_UPDATED, ({ featureName, project }) => {
featureToggleUpdateTotal.labels(featureName, project, 'n/a').inc(); featureToggleUpdateTotal
.labels(featureName, project, 'n/a', 'n/a')
.inc();
}); });
eventStore.on(FEATURE_METADATA_UPDATED, ({ featureName, project }) => { eventStore.on(FEATURE_METADATA_UPDATED, ({ featureName, project }) => {
featureToggleUpdateTotal.labels(featureName, project, 'n/a').inc(); featureToggleUpdateTotal
.labels(featureName, project, 'n/a', 'n/a')
.inc();
}); });
eventStore.on(FEATURE_UPDATED, ({ featureName, project }) => { eventStore.on(FEATURE_UPDATED, ({ featureName, project }) => {
featureToggleUpdateTotal featureToggleUpdateTotal
.labels(featureName, project, 'default') .labels(featureName, project, 'default', 'production')
.inc(); .inc();
}); });
eventStore.on( eventStore.on(
FEATURE_STRATEGY_ADD, FEATURE_STRATEGY_ADD,
({ featureName, project, environment }) => { async ({ featureName, project, environment }) => {
const environmentType = await this.resolveEnvironmentType(
environment,
cachedEnvironments,
);
featureToggleUpdateTotal featureToggleUpdateTotal
.labels(featureName, project, environment) .labels(featureName, project, environment, environmentType)
.inc(); .inc();
}, },
); );
eventStore.on( eventStore.on(
FEATURE_STRATEGY_REMOVE, FEATURE_STRATEGY_REMOVE,
({ featureName, project, environment }) => { async ({ featureName, project, environment }) => {
const environmentType = await this.resolveEnvironmentType(
environment,
cachedEnvironments,
);
featureToggleUpdateTotal featureToggleUpdateTotal
.labels(featureName, project, environment) .labels(featureName, project, environment, environmentType)
.inc(); .inc();
}, },
); );
eventStore.on( eventStore.on(
FEATURE_STRATEGY_UPDATE, FEATURE_STRATEGY_UPDATE,
({ featureName, project, environment }) => { async ({ featureName, project, environment }) => {
const environmentType = await this.resolveEnvironmentType(
environment,
cachedEnvironments,
);
featureToggleUpdateTotal featureToggleUpdateTotal
.labels(featureName, project, environment) .labels(featureName, project, environment, environmentType)
.inc(); .inc();
}, },
); );
eventStore.on( eventStore.on(
FEATURE_ENVIRONMENT_DISABLED, FEATURE_ENVIRONMENT_DISABLED,
({ featureName, project, environment }) => { async ({ featureName, project, environment }) => {
const environmentType = await this.resolveEnvironmentType(
environment,
cachedEnvironments,
);
featureToggleUpdateTotal featureToggleUpdateTotal
.labels(featureName, project, environment) .labels(featureName, project, environment, environmentType)
.inc(); .inc();
}, },
); );
eventStore.on( eventStore.on(
FEATURE_ENVIRONMENT_ENABLED, FEATURE_ENVIRONMENT_ENABLED,
({ featureName, project, environment }) => { async ({ featureName, project, environment }) => {
const environmentType = await this.resolveEnvironmentType(
environment,
cachedEnvironments,
);
featureToggleUpdateTotal featureToggleUpdateTotal
.labels(featureName, project, environment) .labels(featureName, project, environment, environmentType)
.inc(); .inc();
}, },
); );
@ -504,6 +540,20 @@ export default class MetricsMonitor {
// eslint-disable-next-line no-empty // eslint-disable-next-line no-empty
} catch (e) {} } catch (e) {}
} }
async resolveEnvironmentType(
environment: string,
cachedEnvironments: () => Promise<IEnvironment[]>,
): Promise<string> {
const environments = await cachedEnvironments();
const env = environments.find((e) => e.name === environment);
if (env) {
return env.type;
} else {
return 'unknown';
}
}
} }
export function createMetricsMonitor(): MetricsMonitor { export function createMetricsMonitor(): MetricsMonitor {
return new MetricsMonitor(); return new MetricsMonitor();