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:
parent
3c73ce9dd9
commit
d17ae37800
@ -33,10 +33,12 @@ test('can insert and read lifecycle stages', async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
function emitMetricsEvent(environment: string) {
|
function emitMetricsEvent(environment: string) {
|
||||||
eventBus.emit(CLIENT_METRICS, {
|
eventBus.emit(CLIENT_METRICS, [
|
||||||
bucket: { toggles: { [featureName]: 'irrelevant' } },
|
{
|
||||||
environment,
|
featureName,
|
||||||
});
|
environment,
|
||||||
|
},
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
function reachedStage(feature: string, name: StageName) {
|
function reachedStage(feature: string, name: StageName) {
|
||||||
return new Promise((resolve) =>
|
return new Promise((resolve) =>
|
||||||
|
@ -20,9 +20,10 @@ import type {
|
|||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import type { Logger } from '../../logger';
|
import type { Logger } from '../../logger';
|
||||||
import type EventService from '../events/event-service';
|
import type EventService from '../events/event-service';
|
||||||
import type { ValidatedClientMetrics } from '../metrics/shared/schema';
|
|
||||||
import type { FeatureLifecycleCompletedSchema } from '../../openapi';
|
import type { FeatureLifecycleCompletedSchema } from '../../openapi';
|
||||||
import { calculateStageDurations } from './calculate-stage-durations';
|
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';
|
export const STAGE_ENTERED = 'STAGE_ENTERED';
|
||||||
|
|
||||||
@ -95,13 +96,20 @@ export class FeatureLifecycleService extends EventEmitter {
|
|||||||
});
|
});
|
||||||
this.eventBus.on(
|
this.eventBus.on(
|
||||||
CLIENT_METRICS,
|
CLIENT_METRICS,
|
||||||
async (event: ValidatedClientMetrics) => {
|
async (events: IClientMetricsEnv[]) => {
|
||||||
if (event.environment) {
|
if (events.length > 0) {
|
||||||
const features = Object.keys(event.bucket.toggles);
|
const groupedByEnvironment = groupBy(events, 'environment');
|
||||||
const environment = event.environment;
|
|
||||||
await this.checkEnabled(() =>
|
for (const [environment, metrics] of Object.entries(
|
||||||
this.featuresReceivedMetrics(features, environment),
|
groupedByEnvironment,
|
||||||
);
|
)) {
|
||||||
|
const features = metrics.map(
|
||||||
|
(metric) => metric.featureName,
|
||||||
|
);
|
||||||
|
await this.checkEnabled(() =>
|
||||||
|
this.featuresReceivedMetrics(features, environment),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -121,29 +121,32 @@ test('should return lifecycle stages', async () => {
|
|||||||
eventStore.emit(FEATURE_CREATED, { featureName: 'my_feature_a' });
|
eventStore.emit(FEATURE_CREATED, { featureName: 'my_feature_a' });
|
||||||
await reachedStage('my_feature_a', 'initial');
|
await reachedStage('my_feature_a', 'initial');
|
||||||
await expectFeatureStage('my_feature_a', 'initial');
|
await expectFeatureStage('my_feature_a', 'initial');
|
||||||
eventBus.emit(CLIENT_METRICS, {
|
eventBus.emit(CLIENT_METRICS, [
|
||||||
bucket: {
|
{
|
||||||
toggles: {
|
featureName: 'my_feature_a',
|
||||||
my_feature_a: 'irrelevant',
|
environment: 'default',
|
||||||
non_existent_feature: 'irrelevant',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
environment: 'default',
|
{
|
||||||
});
|
featureName: 'non_existent_feature',
|
||||||
|
environment: 'default',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
// missing feature
|
// missing feature
|
||||||
eventBus.emit(CLIENT_METRICS, {
|
eventBus.emit(CLIENT_METRICS, [
|
||||||
environment: 'default',
|
{
|
||||||
bucket: { toggles: {} },
|
environment: 'default',
|
||||||
});
|
yes: 0,
|
||||||
// non existent env
|
no: 0,
|
||||||
eventBus.emit(CLIENT_METRICS, {
|
|
||||||
bucket: {
|
|
||||||
toggles: {
|
|
||||||
my_feature_a: 'irrelevant',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
environment: 'non-existent',
|
]);
|
||||||
});
|
// non existent env
|
||||||
|
eventBus.emit(CLIENT_METRICS, [
|
||||||
|
{
|
||||||
|
featureName: 'my_feature_a',
|
||||||
|
environment: 'non-existent',
|
||||||
|
},
|
||||||
|
]);
|
||||||
await reachedStage('my_feature_a', 'live');
|
await reachedStage('my_feature_a', 'live');
|
||||||
await expectFeatureStage('my_feature_a', 'live');
|
await expectFeatureStage('my_feature_a', 'live');
|
||||||
eventStore.emit(FEATURE_ARCHIVED, { featureName: 'my_feature_a' });
|
eventStore.emit(FEATURE_ARCHIVED, { featureName: 'my_feature_a' });
|
||||||
|
@ -171,7 +171,7 @@ export default class ClientMetricsServiceV2 {
|
|||||||
);
|
);
|
||||||
await this.registerBulkMetrics(clientMetrics);
|
await this.registerBulkMetrics(clientMetrics);
|
||||||
|
|
||||||
this.config.eventBus.emit(CLIENT_METRICS, value);
|
this.config.eventBus.emit(CLIENT_METRICS, clientMetrics);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import type { Response } from 'express';
|
import type { Response } from 'express';
|
||||||
import Controller from '../../../routes/controller';
|
import Controller from '../../../routes/controller';
|
||||||
import type {
|
import {
|
||||||
IFlagResolver,
|
CLIENT_METRICS,
|
||||||
IUnleashConfig,
|
type IFlagResolver,
|
||||||
IUnleashServices,
|
type IUnleashConfig,
|
||||||
|
type IUnleashServices,
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
import type ClientInstanceService from './instance-service';
|
import type ClientInstanceService from './instance-service';
|
||||||
import type { Logger } from '../../../logger';
|
import type { Logger } from '../../../logger';
|
||||||
@ -161,8 +162,10 @@ export default class ClientMetricsController extends Controller {
|
|||||||
promises.push(
|
promises.push(
|
||||||
this.metricsV2.registerBulkMetrics(filteredData),
|
this.metricsV2.registerBulkMetrics(filteredData),
|
||||||
);
|
);
|
||||||
|
this.config.eventBus.emit(CLIENT_METRICS, data);
|
||||||
}
|
}
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
||||||
res.status(202).end();
|
res.status(202).end();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.status(400).end();
|
res.status(400).end();
|
||||||
|
@ -162,16 +162,13 @@ test('should set environmentType when toggle is flipped', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should collect metrics for client metric reports', async () => {
|
test('should collect metrics for client metric reports', async () => {
|
||||||
eventBus.emit(CLIENT_METRICS, {
|
eventBus.emit(CLIENT_METRICS, [
|
||||||
bucket: {
|
{
|
||||||
toggles: {
|
featureName: 'TestToggle',
|
||||||
TestToggle: {
|
yes: 10,
|
||||||
yes: 10,
|
no: 5,
|
||||||
no: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
]);
|
||||||
|
|
||||||
const metrics = await prometheusRegister.metrics();
|
const metrics = await prometheusRegister.metrics();
|
||||||
expect(metrics).toMatch(
|
expect(metrics).toMatch(
|
||||||
|
@ -24,7 +24,6 @@ import type { IUnleashConfig } from './types/option';
|
|||||||
import type { ISettingStore, IUnleashStores } from './types/stores';
|
import type { ISettingStore, IUnleashStores } from './types/stores';
|
||||||
import { hoursToMilliseconds, minutesToMilliseconds } from 'date-fns';
|
import { hoursToMilliseconds, minutesToMilliseconds } from 'date-fns';
|
||||||
import type { InstanceStatsService } from './features/instance-stats/instance-stats-service';
|
import type { InstanceStatsService } from './features/instance-stats/instance-stats-service';
|
||||||
import type { ValidatedClientMetrics } from './features/metrics/shared/schema';
|
|
||||||
import type { IEnvironment } from './types';
|
import type { IEnvironment } from './types';
|
||||||
import {
|
import {
|
||||||
createCounter,
|
createCounter,
|
||||||
@ -33,6 +32,7 @@ import {
|
|||||||
createHistogram,
|
createHistogram,
|
||||||
} from './util/metrics';
|
} from './util/metrics';
|
||||||
import type { SchedulerService } from './services';
|
import type { SchedulerService } from './services';
|
||||||
|
import type { IClientMetricsEnv } from './features/metrics/client-metrics/client-metrics-store-v2-type';
|
||||||
|
|
||||||
export default class MetricsMonitor {
|
export default class MetricsMonitor {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
@ -617,30 +617,31 @@ export default class MetricsMonitor {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const logger = config.getLogger('metrics.ts');
|
const logger = config.getLogger('metrics.ts');
|
||||||
eventBus.on(CLIENT_METRICS, (m: ValidatedClientMetrics) => {
|
eventBus.on(CLIENT_METRICS, (metrics: IClientMetricsEnv[]) => {
|
||||||
try {
|
try {
|
||||||
for (const entry of Object.entries(m.bucket.toggles)) {
|
for (const metric of metrics) {
|
||||||
featureFlagUsageTotal.increment(
|
featureFlagUsageTotal.increment(
|
||||||
{
|
{
|
||||||
toggle: entry[0],
|
toggle: metric.featureName,
|
||||||
active: 'true',
|
active: 'true',
|
||||||
appName: m.appName,
|
appName: metric.appName,
|
||||||
},
|
},
|
||||||
entry[1].yes,
|
metric.yes,
|
||||||
);
|
);
|
||||||
featureFlagUsageTotal.increment(
|
featureFlagUsageTotal.increment(
|
||||||
{
|
{
|
||||||
toggle: entry[0],
|
toggle: metric.featureName,
|
||||||
active: 'false',
|
active: 'false',
|
||||||
appName: m.appName,
|
appName: metric.appName,
|
||||||
},
|
},
|
||||||
entry[1].no,
|
metric.no,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn('Metrics registration failed', e);
|
logger.warn('Metrics registration failed', e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
eventStore.on(CLIENT_REGISTER, (m) => {
|
eventStore.on(CLIENT_REGISTER, (m) => {
|
||||||
if (m.sdkVersion && m.sdkVersion.indexOf(':') > -1) {
|
if (m.sdkVersion && m.sdkVersion.indexOf(':') > -1) {
|
||||||
const [sdkName, sdkVersion] = m.sdkVersion.split(':');
|
const [sdkName, sdkVersion] = m.sdkVersion.split(':');
|
||||||
|
Loading…
Reference in New Issue
Block a user