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

feat: now CLIENT_METRICS event will be emitted with new structure (#7210)

1. CLIENT_METRICS event will be emitted with new structure
2. CLIENT_METRICS event will be emitted from bulkMetrics endpoint
This commit is contained in:
Jaanus Sellin 2024-05-31 12:40:46 +03:00 committed by GitHub
parent 3c73ce9dd9
commit d17ae37800
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 69 additions and 55 deletions

View File

@ -33,10 +33,12 @@ test('can insert and read lifecycle stages', async () => {
);
function emitMetricsEvent(environment: string) {
eventBus.emit(CLIENT_METRICS, {
bucket: { toggles: { [featureName]: 'irrelevant' } },
environment,
});
eventBus.emit(CLIENT_METRICS, [
{
featureName,
environment,
},
]);
}
function reachedStage(feature: string, name: StageName) {
return new Promise((resolve) =>

View File

@ -20,9 +20,10 @@ import type {
import EventEmitter from 'events';
import type { Logger } from '../../logger';
import type EventService from '../events/event-service';
import type { ValidatedClientMetrics } from '../metrics/shared/schema';
import type { FeatureLifecycleCompletedSchema } from '../../openapi';
import { calculateStageDurations } from './calculate-stage-durations';
import type { IClientMetricsEnv } from '../metrics/client-metrics/client-metrics-store-v2-type';
import groupBy from 'lodash.groupby';
export const STAGE_ENTERED = 'STAGE_ENTERED';
@ -95,13 +96,20 @@ export class FeatureLifecycleService extends EventEmitter {
});
this.eventBus.on(
CLIENT_METRICS,
async (event: ValidatedClientMetrics) => {
if (event.environment) {
const features = Object.keys(event.bucket.toggles);
const environment = event.environment;
await this.checkEnabled(() =>
this.featuresReceivedMetrics(features, environment),
);
async (events: IClientMetricsEnv[]) => {
if (events.length > 0) {
const groupedByEnvironment = groupBy(events, 'environment');
for (const [environment, metrics] of Object.entries(
groupedByEnvironment,
)) {
const features = metrics.map(
(metric) => metric.featureName,
);
await this.checkEnabled(() =>
this.featuresReceivedMetrics(features, environment),
);
}
}
},
);

View File

@ -121,29 +121,32 @@ test('should return lifecycle stages', async () => {
eventStore.emit(FEATURE_CREATED, { featureName: 'my_feature_a' });
await reachedStage('my_feature_a', 'initial');
await expectFeatureStage('my_feature_a', 'initial');
eventBus.emit(CLIENT_METRICS, {
bucket: {
toggles: {
my_feature_a: 'irrelevant',
non_existent_feature: 'irrelevant',
},
eventBus.emit(CLIENT_METRICS, [
{
featureName: 'my_feature_a',
environment: 'default',
},
environment: 'default',
});
{
featureName: 'non_existent_feature',
environment: 'default',
},
]);
// missing feature
eventBus.emit(CLIENT_METRICS, {
environment: 'default',
bucket: { toggles: {} },
});
// non existent env
eventBus.emit(CLIENT_METRICS, {
bucket: {
toggles: {
my_feature_a: 'irrelevant',
},
eventBus.emit(CLIENT_METRICS, [
{
environment: 'default',
yes: 0,
no: 0,
},
environment: 'non-existent',
});
]);
// non existent env
eventBus.emit(CLIENT_METRICS, [
{
featureName: 'my_feature_a',
environment: 'non-existent',
},
]);
await reachedStage('my_feature_a', 'live');
await expectFeatureStage('my_feature_a', 'live');
eventStore.emit(FEATURE_ARCHIVED, { featureName: 'my_feature_a' });

View File

@ -171,7 +171,7 @@ export default class ClientMetricsServiceV2 {
);
await this.registerBulkMetrics(clientMetrics);
this.config.eventBus.emit(CLIENT_METRICS, value);
this.config.eventBus.emit(CLIENT_METRICS, clientMetrics);
}
}

View File

@ -1,9 +1,10 @@
import type { Response } from 'express';
import Controller from '../../../routes/controller';
import type {
IFlagResolver,
IUnleashConfig,
IUnleashServices,
import {
CLIENT_METRICS,
type IFlagResolver,
type IUnleashConfig,
type IUnleashServices,
} from '../../../types';
import type ClientInstanceService from './instance-service';
import type { Logger } from '../../../logger';
@ -161,8 +162,10 @@ export default class ClientMetricsController extends Controller {
promises.push(
this.metricsV2.registerBulkMetrics(filteredData),
);
this.config.eventBus.emit(CLIENT_METRICS, data);
}
await Promise.all(promises);
res.status(202).end();
} catch (e) {
res.status(400).end();

View File

@ -162,16 +162,13 @@ test('should set environmentType when toggle is flipped', async () => {
});
test('should collect metrics for client metric reports', async () => {
eventBus.emit(CLIENT_METRICS, {
bucket: {
toggles: {
TestToggle: {
yes: 10,
no: 5,
},
},
eventBus.emit(CLIENT_METRICS, [
{
featureName: 'TestToggle',
yes: 10,
no: 5,
},
});
]);
const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch(

View File

@ -24,7 +24,6 @@ import type { IUnleashConfig } from './types/option';
import type { ISettingStore, IUnleashStores } from './types/stores';
import { hoursToMilliseconds, minutesToMilliseconds } from 'date-fns';
import type { InstanceStatsService } from './features/instance-stats/instance-stats-service';
import type { ValidatedClientMetrics } from './features/metrics/shared/schema';
import type { IEnvironment } from './types';
import {
createCounter,
@ -33,6 +32,7 @@ import {
createHistogram,
} from './util/metrics';
import type { SchedulerService } from './services';
import type { IClientMetricsEnv } from './features/metrics/client-metrics/client-metrics-store-v2-type';
export default class MetricsMonitor {
constructor() {}
@ -617,30 +617,31 @@ export default class MetricsMonitor {
});
const logger = config.getLogger('metrics.ts');
eventBus.on(CLIENT_METRICS, (m: ValidatedClientMetrics) => {
eventBus.on(CLIENT_METRICS, (metrics: IClientMetricsEnv[]) => {
try {
for (const entry of Object.entries(m.bucket.toggles)) {
for (const metric of metrics) {
featureFlagUsageTotal.increment(
{
toggle: entry[0],
toggle: metric.featureName,
active: 'true',
appName: m.appName,
appName: metric.appName,
},
entry[1].yes,
metric.yes,
);
featureFlagUsageTotal.increment(
{
toggle: entry[0],
toggle: metric.featureName,
active: 'false',
appName: m.appName,
appName: metric.appName,
},
entry[1].no,
metric.no,
);
}
} catch (e) {
logger.warn('Metrics registration failed', e);
}
});
eventStore.on(CLIENT_REGISTER, (m) => {
if (m.sdkVersion && m.sdkVersion.indexOf(':') > -1) {
const [sdkName, sdkVersion] = m.sdkVersion.split(':');