1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-09 13:47:13 +02:00

feat: start tracking operation duration (#6514)

This commit is contained in:
Jaanus Sellin 2024-03-12 12:30:30 +02:00 committed by GitHub
parent d2767a0eb9
commit b7915171ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 44 additions and 3 deletions

View File

@ -16,12 +16,14 @@ import {
import { validateOrigins } from '../../util'; import { validateOrigins } from '../../util';
import { BadDataError, InvalidTokenError } from '../../error'; import { BadDataError, InvalidTokenError } from '../../error';
import { import {
OPERATION_TIME,
FRONTEND_API_REPOSITORY_CREATED, FRONTEND_API_REPOSITORY_CREATED,
PROXY_REPOSITORY_CREATED, PROXY_REPOSITORY_CREATED,
} from '../../metric-events'; } from '../../metric-events';
import { FrontendApiRepository } from './frontend-api-repository'; import { FrontendApiRepository } from './frontend-api-repository';
import { GlobalFrontendApiCache } from './global-frontend-api-cache'; import { GlobalFrontendApiCache } from './global-frontend-api-cache';
import { ProxyRepository } from './proxy-repository'; import { ProxyRepository } from './proxy-repository';
import metricsHelper from '../../util/metrics-helper';
export type Config = Pick< export type Config = Pick<
IUnleashConfig, IUnleashConfig,
@ -61,6 +63,8 @@ export class FrontendApiService {
private cachedFrontendSettings?: FrontendSettings; private cachedFrontendSettings?: FrontendSettings;
private timer: Function;
constructor( constructor(
config: Config, config: Config,
stores: Stores, stores: Stores,
@ -72,17 +76,23 @@ export class FrontendApiService {
this.stores = stores; this.stores = stores;
this.services = services; this.services = services;
this.globalFrontendApiCache = globalFrontendApiCache; this.globalFrontendApiCache = globalFrontendApiCache;
this.timer = (operationId) =>
metricsHelper.wrapTimer(config.eventBus, OPERATION_TIME, {
operationId,
});
} }
async getFrontendApiFeatures( async getFrontendApiFeatures(
token: IApiUser, token: IApiUser,
context: Context, context: Context,
): Promise<FrontendApiFeatureSchema[]> { ): Promise<FrontendApiFeatureSchema[]> {
const stopTimer = this.timer('getFrontendApiFeatures');
const client = await this.clientForFrontendApiToken(token); const client = await this.clientForFrontendApiToken(token);
const definitions = client.getFeatureToggleDefinitions() || []; const definitions = client.getFeatureToggleDefinitions() || [];
const sessionId = context.sessionId || String(Math.random()); const sessionId = context.sessionId || String(Math.random());
return definitions const resultDefinitions = definitions
.filter((feature) => .filter((feature) =>
client.isEnabled(feature.name, { client.isEnabled(feature.name, {
...context, ...context,
@ -98,17 +108,20 @@ export class FrontendApiService {
}), }),
impressionData: Boolean(feature.impressionData), impressionData: Boolean(feature.impressionData),
})); }));
stopTimer();
return resultDefinitions;
} }
async getNewFrontendApiFeatures( async getNewFrontendApiFeatures(
token: IApiUser, token: IApiUser,
context: Context, context: Context,
): Promise<FrontendApiFeatureSchema[]> { ): Promise<FrontendApiFeatureSchema[]> {
const stopTimer = this.timer('getNewFrontendApiFeatures');
const client = await this.newClientForFrontendApiToken(token); const client = await this.newClientForFrontendApiToken(token);
const definitions = client.getFeatureToggleDefinitions() || []; const definitions = client.getFeatureToggleDefinitions() || [];
const sessionId = context.sessionId || String(Math.random()); const sessionId = context.sessionId || String(Math.random());
return definitions const resultDefinitions = definitions
.filter((feature) => { .filter((feature) => {
const enabled = client.isEnabled(feature.name, { const enabled = client.isEnabled(feature.name, {
...context, ...context,
@ -125,6 +138,8 @@ export class FrontendApiService {
}), }),
impressionData: Boolean(feature.impressionData), impressionData: Boolean(feature.impressionData),
})); }));
stopTimer();
return resultDefinitions;
} }
async registerFrontendApiMetrics( async registerFrontendApiMetrics(

View File

@ -1,5 +1,6 @@
const REQUEST_TIME = 'request_time'; const REQUEST_TIME = 'request_time';
const DB_TIME = 'db_time'; const DB_TIME = 'db_time';
const OPERATION_TIME = 'operation_time';
const SCHEDULER_JOB_TIME = 'scheduler_job_time'; const SCHEDULER_JOB_TIME = 'scheduler_job_time';
const FEATURES_CREATED_BY_PROCESSED = 'features_created_by_processed'; const FEATURES_CREATED_BY_PROCESSED = 'features_created_by_processed';
const EVENTS_CREATED_BY_PROCESSED = 'events_created_by_processed'; const EVENTS_CREATED_BY_PROCESSED = 'events_created_by_processed';
@ -11,6 +12,7 @@ export {
REQUEST_TIME, REQUEST_TIME,
DB_TIME, DB_TIME,
SCHEDULER_JOB_TIME, SCHEDULER_JOB_TIME,
OPERATION_TIME,
FEATURES_CREATED_BY_PROCESSED, FEATURES_CREATED_BY_PROCESSED,
EVENTS_CREATED_BY_PROCESSED, EVENTS_CREATED_BY_PROCESSED,
FRONTEND_API_REPOSITORY_CREATED, FRONTEND_API_REPOSITORY_CREATED,

View File

@ -2,7 +2,7 @@ import { register } from 'prom-client';
import EventEmitter from 'events'; import EventEmitter from 'events';
import { IEventStore } from './types/stores/event-store'; import { IEventStore } from './types/stores/event-store';
import { createTestConfig } from '../test/config/test-config'; import { createTestConfig } from '../test/config/test-config';
import { DB_TIME, REQUEST_TIME } from './metric-events'; import { DB_TIME, OPERATION_TIME, REQUEST_TIME } from './metric-events';
import { import {
CLIENT_METRICS, CLIENT_METRICS,
CLIENT_REGISTER, CLIENT_REGISTER,
@ -172,6 +172,18 @@ test('should collect metrics for db query timings', async () => {
); );
}); });
test('should collect metrics for operation timings', async () => {
eventBus.emit(OPERATION_TIME, {
operationId: 'getToggles',
time: 0.1337,
});
const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch(
/operation_duration_seconds\{quantile="0\.99",operationId="getToggles"\} 0.1337/,
);
});
test('should collect metrics for feature toggle size', async () => { test('should collect metrics for feature toggle size', async () => {
const metrics = await prometheusRegister.metrics(); const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch(/feature_toggles_total\{version="(.*)"\} 0/); expect(metrics).toMatch(/feature_toggles_total\{version="(.*)"\} 0/);

View File

@ -85,6 +85,14 @@ export default class MetricsMonitor {
maxAgeSeconds: 600, maxAgeSeconds: 600,
ageBuckets: 5, ageBuckets: 5,
}); });
const operationDuration = createSummary({
name: 'operation_duration_seconds',
help: 'Operation duration time',
labelNames: ['operationId'],
percentiles: [0.1, 0.5, 0.9, 0.95, 0.99],
maxAgeSeconds: 600,
ageBuckets: 5,
});
const featureToggleUpdateTotal = createCounter({ const featureToggleUpdateTotal = createCounter({
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.',
@ -405,6 +413,10 @@ export default class MetricsMonitor {
schedulerDuration.labels(jobId).observe(time); schedulerDuration.labels(jobId).observe(time);
}); });
eventBus.on(events.OPERATION_TIME, ({ operationId, time }) => {
operationDuration.labels(operationId).observe(time);
});
eventBus.on(events.EVENTS_CREATED_BY_PROCESSED, ({ updated }) => { eventBus.on(events.EVENTS_CREATED_BY_PROCESSED, ({ updated }) => {
eventCreatedByMigration.inc(updated); eventCreatedByMigration.inc(updated);
}); });