1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-24 01:18:01 +02:00

feat: send traffic info to prometheus (#8541)

This commit is contained in:
Jaanus Sellin 2024-10-25 15:43:14 +03:00 committed by GitHub
parent 7c37de373f
commit 677ec9cee8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 65 additions and 4 deletions

View File

@ -42,6 +42,8 @@ import FakeStrategiesStore from '../../../test/fixtures/fake-strategies-store';
import FakeFeatureStrategiesStore from '../feature-toggle/fakes/fake-feature-strategies-store'; import FakeFeatureStrategiesStore from '../feature-toggle/fakes/fake-feature-strategies-store';
import { FeatureStrategiesReadModel } from '../feature-toggle/feature-strategies-read-model'; import { FeatureStrategiesReadModel } from '../feature-toggle/feature-strategies-read-model';
import { FakeFeatureStrategiesReadModel } from '../feature-toggle/fake-feature-strategies-read-model'; import { FakeFeatureStrategiesReadModel } from '../feature-toggle/fake-feature-strategies-read-model';
import { TrafficDataUsageStore } from '../traffic-data-usage/traffic-data-usage-store';
import { FakeTrafficDataUsageStore } from '../traffic-data-usage/fake-traffic-data-usage-store';
export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => { export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
const { eventBus, getLogger, flagResolver } = config; const { eventBus, getLogger, flagResolver } = config;
@ -93,6 +95,9 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
); );
const featureStrategiesReadModel = new FeatureStrategiesReadModel(db); const featureStrategiesReadModel = new FeatureStrategiesReadModel(db);
const trafficDataUsageStore = new TrafficDataUsageStore(db, getLogger);
const instanceStatsServiceStores = { const instanceStatsServiceStores = {
featureToggleStore, featureToggleStore,
userStore, userStore,
@ -109,6 +114,7 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
apiTokenStore, apiTokenStore,
clientMetricsStoreV2, clientMetricsStoreV2,
featureStrategiesReadModel, featureStrategiesReadModel,
trafficDataUsageStore,
}; };
const featureStrategiesStore = new FeatureStrategyStore( const featureStrategiesStore = new FeatureStrategyStore(
db, db,
@ -157,6 +163,7 @@ export const createFakeInstanceStatsService = (config: IUnleashConfig) => {
const apiTokenStore = new FakeApiTokenStore(); const apiTokenStore = new FakeApiTokenStore();
const clientMetricsStoreV2 = new FakeClientMetricsStoreV2(); const clientMetricsStoreV2 = new FakeClientMetricsStoreV2();
const featureStrategiesReadModel = new FakeFeatureStrategiesReadModel(); const featureStrategiesReadModel = new FakeFeatureStrategiesReadModel();
const trafficDataUsageStore = new FakeTrafficDataUsageStore();
const instanceStatsServiceStores = { const instanceStatsServiceStores = {
featureToggleStore, featureToggleStore,
@ -174,6 +181,7 @@ export const createFakeInstanceStatsService = (config: IUnleashConfig) => {
apiTokenStore, apiTokenStore,
clientMetricsStoreV2, clientMetricsStoreV2,
featureStrategiesReadModel, featureStrategiesReadModel,
trafficDataUsageStore,
}; };
const featureStrategiesStore = new FakeFeatureStrategiesStore(); const featureStrategiesStore = new FakeFeatureStrategiesStore();
const versionServiceStores = { const versionServiceStores = {

View File

@ -6,6 +6,7 @@ import type {
IClientMetricsStoreV2, IClientMetricsStoreV2,
IEventStore, IEventStore,
IFeatureStrategiesReadModel, IFeatureStrategiesReadModel,
ITrafficDataUsageStore,
IUnleashStores, IUnleashStores,
} from '../../types/stores'; } from '../../types/stores';
import type { IContextFieldStore } from '../../types/stores/context-field-store'; import type { IContextFieldStore } from '../../types/stores/context-field-store';
@ -29,6 +30,7 @@ import { CUSTOM_ROOT_ROLE_TYPE } from '../../util';
import type { GetActiveUsers } from './getActiveUsers'; import type { GetActiveUsers } from './getActiveUsers';
import type { ProjectModeCount } from '../project/project-store'; import type { ProjectModeCount } from '../project/project-store';
import type { GetProductionChanges } from './getProductionChanges'; import type { GetProductionChanges } from './getProductionChanges';
import { format } from 'date-fns';
export type TimeRange = 'allTime' | '30d' | '7d'; export type TimeRange = 'allTime' | '30d' | '7d';
@ -115,6 +117,8 @@ export class InstanceStatsService {
private featureStrategiesReadModel: IFeatureStrategiesReadModel; private featureStrategiesReadModel: IFeatureStrategiesReadModel;
private trafficDataUsageStore: ITrafficDataUsageStore;
constructor( constructor(
{ {
featureToggleStore, featureToggleStore,
@ -132,6 +136,7 @@ export class InstanceStatsService {
apiTokenStore, apiTokenStore,
clientMetricsStoreV2, clientMetricsStoreV2,
featureStrategiesReadModel, featureStrategiesReadModel,
trafficDataUsageStore,
}: Pick< }: Pick<
IUnleashStores, IUnleashStores,
| 'featureToggleStore' | 'featureToggleStore'
@ -149,6 +154,7 @@ export class InstanceStatsService {
| 'apiTokenStore' | 'apiTokenStore'
| 'clientMetricsStoreV2' | 'clientMetricsStoreV2'
| 'featureStrategiesReadModel' | 'featureStrategiesReadModel'
| 'trafficDataUsageStore'
>, >,
{ {
getLogger, getLogger,
@ -178,6 +184,7 @@ export class InstanceStatsService {
this.clientMetricsStore = clientMetricsStoreV2; this.clientMetricsStore = clientMetricsStoreV2;
this.flagResolver = flagResolver; this.flagResolver = flagResolver;
this.featureStrategiesReadModel = featureStrategiesReadModel; this.featureStrategiesReadModel = featureStrategiesReadModel;
this.trafficDataUsageStore = trafficDataUsageStore;
} }
getProjectModeCount(): Promise<ProjectModeCount[]> { getProjectModeCount(): Promise<ProjectModeCount[]> {
@ -361,6 +368,16 @@ export class InstanceStatsService {
return this.userStore.countServiceAccounts(); return this.userStore.countServiceAccounts();
} }
async getCurrentTrafficData(): Promise<number> {
const traffic =
await this.trafficDataUsageStore.getTrafficDataUsageForPeriod(
format(new Date(), 'yyyy-MM'),
);
const counts = traffic.map((item) => item.count);
return counts.reduce((total, current) => total + current, 0);
}
async getLabeledAppCounts(): Promise< async getLabeledAppCounts(): Promise<
Partial<{ [key in TimeRange]: number }> Partial<{ [key in TimeRange]: number }>
> { > {

View File

@ -3,8 +3,11 @@ import type {
IStatTrafficUsage, IStatTrafficUsage,
} from './traffic-data-usage-store-type'; } from './traffic-data-usage-store-type';
import type { ITrafficDataUsageStore } from '../../types'; import type { ITrafficDataUsageStore } from '../../types';
import { isSameMonth, parse } from 'date-fns';
export class FakeTrafficDataUsageStore implements ITrafficDataUsageStore { export class FakeTrafficDataUsageStore implements ITrafficDataUsageStore {
private trafficData: IStatTrafficUsage[] = [];
get(key: IStatTrafficUsageKey): Promise<IStatTrafficUsage> { get(key: IStatTrafficUsageKey): Promise<IStatTrafficUsage> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
@ -23,10 +26,28 @@ export class FakeTrafficDataUsageStore implements ITrafficDataUsageStore {
destroy(): void { destroy(): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
upsert(trafficDataUsage: IStatTrafficUsage): Promise<void> { async upsert(trafficDataUsage: IStatTrafficUsage): Promise<void> {
throw new Error('Method not implemented.'); const index = this.trafficData.findIndex(
(data) =>
data.day.getTime() === trafficDataUsage.day.getTime() &&
data.trafficGroup === trafficDataUsage.trafficGroup &&
data.statusCodeSeries === trafficDataUsage.statusCodeSeries,
);
if (index >= 0) {
this.trafficData[index].count += trafficDataUsage.count;
} else {
this.trafficData.push(trafficDataUsage);
}
} }
getTrafficDataUsageForPeriod(period: string): Promise<IStatTrafficUsage[]> {
throw new Error('Method not implemented.'); async getTrafficDataUsageForPeriod(
period: string,
): Promise<IStatTrafficUsage[]> {
const periodDate = parse(period, 'yyyy-MM', new Date());
return this.trafficData.filter((data) =>
isSameMonth(data.day, periodDate),
);
} }
} }

View File

@ -351,3 +351,9 @@ test('should collect limit exceeded metrics', async () => {
/exceeds_limit_error{resource=\"feature flags\",limit=\"5000\"} 1/, /exceeds_limit_error{resource=\"feature flags\",limit=\"5000\"} 1/,
); );
}); });
test('should collect traffic_total metrics', async () => {
const recordedMetric =
await prometheusRegister.getSingleMetricAsString('traffic_total');
expect(recordedMetric).toMatch(/traffic_total 0/);
});

View File

@ -277,6 +277,10 @@ export function registerPrometheusMetrics(
name: 'users_total', name: 'users_total',
help: 'Number of users', help: 'Number of users',
}); });
const trafficTotal = createGauge({
name: 'traffic_total',
help: 'Traffic used current month',
});
const serviceAccounts = createGauge({ const serviceAccounts = createGauge({
name: 'service_accounts_total', name: 'service_accounts_total',
help: 'Number of service accounts', help: 'Number of service accounts',
@ -957,6 +961,11 @@ export function registerPrometheusMetrics(
await instanceStatsService.countServiceAccounts(), await instanceStatsService.countServiceAccounts(),
); );
trafficTotal.reset();
trafficTotal.set(
await instanceStatsService.getCurrentTrafficData(),
);
apiTokens.reset(); apiTokens.reset();
for (const [ for (const [