1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: start monitoring total time to update cache (#6517)

This commit is contained in:
Jaanus Sellin 2024-03-12 14:27:04 +02:00 committed by GitHub
parent 1d526e707b
commit 2a57acca41
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 58 additions and 21 deletions

View File

@ -16,7 +16,7 @@ import {
import { validateOrigins } from '../../util'; import { validateOrigins } from '../../util';
import { BadDataError, InvalidTokenError } from '../../error'; import { BadDataError, InvalidTokenError } from '../../error';
import { import {
OPERATION_TIME, FUNCTION_TIME,
FRONTEND_API_REPOSITORY_CREATED, FRONTEND_API_REPOSITORY_CREATED,
PROXY_REPOSITORY_CREATED, PROXY_REPOSITORY_CREATED,
} from '../../metric-events'; } from '../../metric-events';
@ -77,9 +77,10 @@ export class FrontendApiService {
this.services = services; this.services = services;
this.globalFrontendApiCache = globalFrontendApiCache; this.globalFrontendApiCache = globalFrontendApiCache;
this.timer = (operationId) => this.timer = (functionName) =>
metricsHelper.wrapTimer(config.eventBus, OPERATION_TIME, { metricsHelper.wrapTimer(config.eventBus, FUNCTION_TIME, {
operationId, className: 'FrontendApiService',
functionName,
}); });
} }

View File

@ -48,7 +48,11 @@ const createCache = (
segment: ISegment = defaultSegment, segment: ISegment = defaultSegment,
features: Record<string, Record<string, IFeatureToggleClient>> = {}, features: Record<string, Record<string, IFeatureToggleClient>> = {},
) => { ) => {
const config = { getLogger: noLogger, flagResolver: alwaysOnFlagResolver }; const config = {
getLogger: noLogger,
flagResolver: alwaysOnFlagResolver,
eventBus: <any>{ emit: jest.fn() },
};
const segmentReadModel = new FakeSegmentReadModel([segment as ISegment]); const segmentReadModel = new FakeSegmentReadModel([segment as ISegment]);
const clientFeatureToggleReadModel = new FakeClientFeatureToggleReadModel( const clientFeatureToggleReadModel = new FakeClientFeatureToggleReadModel(
features, features,

View File

@ -15,8 +15,10 @@ import { ALL_ENVS } from '../../util/constants';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
import { UPDATE_REVISION } from '../feature-toggle/configuration-revision-service'; import { UPDATE_REVISION } from '../feature-toggle/configuration-revision-service';
import { IClientFeatureToggleReadModel } from './client-feature-toggle-read-model-type'; import { IClientFeatureToggleReadModel } from './client-feature-toggle-read-model-type';
import metricsHelper from '../../util/metrics-helper';
import { FUNCTION_TIME } from '../../metric-events';
type Config = Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>; type Config = Pick<IUnleashConfig, 'getLogger' | 'flagResolver' | 'eventBus'>;
type FrontendApiFeatureCache = Record<string, Record<string, FeatureInterface>>; type FrontendApiFeatureCache = Record<string, Record<string, FeatureInterface>>;
@ -39,6 +41,8 @@ export class GlobalFrontendApiCache extends EventEmitter {
private status: GlobalFrontendApiCacheState = 'starting'; private status: GlobalFrontendApiCacheState = 'starting';
private timer: Function;
constructor( constructor(
config: Config, config: Config,
segmentReadModel: ISegmentReadModel, segmentReadModel: ISegmentReadModel,
@ -52,6 +56,12 @@ export class GlobalFrontendApiCache extends EventEmitter {
this.configurationRevisionService = configurationRevisionService; this.configurationRevisionService = configurationRevisionService;
this.segmentReadModel = segmentReadModel; this.segmentReadModel = segmentReadModel;
this.onUpdateRevisionEvent = this.onUpdateRevisionEvent.bind(this); this.onUpdateRevisionEvent = this.onUpdateRevisionEvent.bind(this);
this.timer = (functionName) =>
metricsHelper.wrapTimer(config.eventBus, FUNCTION_TIME, {
className: 'GlobalFrontendApiCache',
functionName,
});
this.refreshData(); this.refreshData();
this.configurationRevisionService.on( this.configurationRevisionService.on(
UPDATE_REVISION, UPDATE_REVISION,
@ -103,6 +113,7 @@ export class GlobalFrontendApiCache extends EventEmitter {
// TODO: also consider not fetching disabled features, because those are not returned by frontend API // TODO: also consider not fetching disabled features, because those are not returned by frontend API
private async refreshData() { private async refreshData() {
try { try {
const stopTimer = this.timer('refreshData');
this.featuresByEnvironment = await this.getAllFeatures(); this.featuresByEnvironment = await this.getAllFeatures();
this.segments = await this.getAllSegments(); this.segments = await this.getAllSegments();
if (this.status === 'starting') { if (this.status === 'starting') {
@ -112,6 +123,7 @@ export class GlobalFrontendApiCache extends EventEmitter {
this.status = 'updated'; this.status = 'updated';
this.emit('updated'); this.emit('updated');
} }
stopTimer();
} catch (e) { } catch (e) {
this.logger.error('Cannot load data for token', e); this.logger.error('Cannot load data for token', e);
} }

View File

@ -17,7 +17,11 @@ import { Logger } from '../../logger';
import ConfigurationRevisionService, { import ConfigurationRevisionService, {
UPDATE_REVISION, UPDATE_REVISION,
} from '../feature-toggle/configuration-revision-service'; } from '../feature-toggle/configuration-revision-service';
import { PROXY_FEATURES_FOR_TOKEN_TIME } from '../../metric-events'; import {
FUNCTION_TIME,
PROXY_FEATURES_FOR_TOKEN_TIME,
} from '../../metric-events';
import metricsHelper from '../../util/metrics-helper';
type Config = Pick<IUnleashConfig, 'getLogger' | 'frontendApi' | 'eventBus'>; type Config = Pick<IUnleashConfig, 'getLogger' | 'frontendApi' | 'eventBus'>;
@ -55,6 +59,8 @@ export class ProxyRepository
private running: boolean; private running: boolean;
private methodTimer: Function;
constructor( constructor(
config: Config, config: Config,
stores: Stores, stores: Stores,
@ -71,6 +77,12 @@ export class ProxyRepository
this.token = token; this.token = token;
this.onUpdateRevisionEvent = this.onUpdateRevisionEvent.bind(this); this.onUpdateRevisionEvent = this.onUpdateRevisionEvent.bind(this);
this.interval = config.frontendApi.refreshIntervalInMs; this.interval = config.frontendApi.refreshIntervalInMs;
this.methodTimer = (functionName) =>
metricsHelper.wrapTimer(config.eventBus, FUNCTION_TIME, {
className: 'ProxyRepository',
functionName,
});
} }
getTogglesWithSegmentData(): EnhancedFeatureInterface[] { getTogglesWithSegmentData(): EnhancedFeatureInterface[] {
@ -135,8 +147,10 @@ export class ProxyRepository
private async loadDataForToken() { private async loadDataForToken() {
try { try {
const stopTimer = this.methodTimer('loadDataForToken');
this.features = await this.featuresForToken(); this.features = await this.featuresForToken();
this.segments = await this.segmentsForToken(); this.segments = await this.segmentsForToken();
stopTimer();
} catch (e) { } catch (e) {
this.logger.error('Cannot load data for token', e); this.logger.error('Cannot load data for token', e);
} }

View File

@ -1,6 +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 FUNCTION_TIME = 'function_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';
@ -12,7 +12,7 @@ export {
REQUEST_TIME, REQUEST_TIME,
DB_TIME, DB_TIME,
SCHEDULER_JOB_TIME, SCHEDULER_JOB_TIME,
OPERATION_TIME, FUNCTION_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, OPERATION_TIME, REQUEST_TIME } from './metric-events'; import { DB_TIME, FUNCTION_TIME, REQUEST_TIME } from './metric-events';
import { import {
CLIENT_METRICS, CLIENT_METRICS,
CLIENT_REGISTER, CLIENT_REGISTER,
@ -172,15 +172,16 @@ test('should collect metrics for db query timings', async () => {
); );
}); });
test('should collect metrics for operation timings', async () => { test('should collect metrics for function timings', async () => {
eventBus.emit(OPERATION_TIME, { eventBus.emit(FUNCTION_TIME, {
operationId: 'getToggles', functionName: 'getToggles',
className: 'ToggleService',
time: 0.1337, time: 0.1337,
}); });
const metrics = await prometheusRegister.metrics(); const metrics = await prometheusRegister.metrics();
expect(metrics).toMatch( expect(metrics).toMatch(
/operation_duration_seconds\{quantile="0\.99",operationId="getToggles"\} 0.1337/, /function_duration_seconds\{quantile="0\.99",functionName="getToggles",className="ToggleService"\} 0.1337/,
); );
}); });

View File

@ -85,10 +85,10 @@ export default class MetricsMonitor {
maxAgeSeconds: 600, maxAgeSeconds: 600,
ageBuckets: 5, ageBuckets: 5,
}); });
const operationDuration = createSummary({ const functionDuration = createSummary({
name: 'operation_duration_seconds', name: 'function_duration_seconds',
help: 'Operation duration time', help: 'Function duration time',
labelNames: ['operationId'], labelNames: ['functionName', 'className'],
percentiles: [0.1, 0.5, 0.9, 0.95, 0.99], percentiles: [0.1, 0.5, 0.9, 0.95, 0.99],
maxAgeSeconds: 600, maxAgeSeconds: 600,
ageBuckets: 5, ageBuckets: 5,
@ -413,9 +413,14 @@ export default class MetricsMonitor {
schedulerDuration.labels(jobId).observe(time); schedulerDuration.labels(jobId).observe(time);
}); });
eventBus.on(events.OPERATION_TIME, ({ operationId, time }) => { eventBus.on(
operationDuration.labels(operationId).observe(time); events.FUNCTION_TIME,
}); ({ functionName, className, time }) => {
functionDuration
.labels({ functionName, className })
.observe(time);
},
);
eventBus.on(events.EVENTS_CREATED_BY_PROCESSED, ({ updated }) => { eventBus.on(events.EVENTS_CREATED_BY_PROCESSED, ({ updated }) => {
eventCreatedByMigration.inc(updated); eventCreatedByMigration.inc(updated);