mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-28 00:06:53 +01:00
cfd9e4894a
This PR adds a counter in Prometheus for counting the number of "environment disabled" events we get per project. The purpose of this is to establish a baseline for one of the "project management UI" project's key results. ## On gauges vs counters This PR uses a counter. Using a gauge would give you the total number of envs disabled, not the number of disable events. The difference is subtle, but important. For projects that were created before the new feature, the gauge might be appropriate. Because each disabled env would require at least one disabled event, we can get a floor of how many events were triggered for each project. However, for projects created after we introduce the planned change, we're not interested in the total envs anymore, because you can disable a hundred envs on creation with a single action. In this case, a gauge showing 100 disabled envs would be misleading, because it didn't take 100 events to disable them. So the interesting metric here is how many times did you specifically disable an environment in project settings, hence the counter. ## Assumptions and future plans To make this easier on ourselves, we make the follow assumption: people primarily disable envs **when creating a project**. This means that there might be a few lagging indicators granting some projects a smaller number of events than expected, but we may be able to filter those out. Further, if we had a metric for each project and its creation date, we could correlate that with the metrics to answer the question "how many envs do people disable in the first week? Two weeks? A month?". Or worded differently: after creating a project, how long does it take for people to configure environments? Similarly, if we gather that data, it will also make filtering out the number of events for projects created **after** the new changes have been released much easier. The good news: Because the project creation metric with dates is a static aggregate, it can be applied at any time, even retroactively, to see the effects.
278 lines
9.0 KiB
TypeScript
278 lines
9.0 KiB
TypeScript
import { register } from 'prom-client';
|
|
import EventEmitter from 'events';
|
|
import type { IEventStore } from './types/stores/event-store';
|
|
import { createTestConfig } from '../test/config/test-config';
|
|
import { DB_TIME, FUNCTION_TIME, REQUEST_TIME } from './metric-events';
|
|
import {
|
|
CLIENT_METRICS,
|
|
CLIENT_REGISTER,
|
|
FEATURE_ENVIRONMENT_ENABLED,
|
|
FEATURE_UPDATED,
|
|
PROJECT_ENVIRONMENT_REMOVED,
|
|
} from './types/events';
|
|
import { createMetricsMonitor } from './metrics';
|
|
import createStores from '../test/fixtures/store';
|
|
import { InstanceStatsService } from './features/instance-stats/instance-stats-service';
|
|
import VersionService from './services/version-service';
|
|
import { createFakeGetActiveUsers } from './features/instance-stats/getActiveUsers';
|
|
import { createFakeGetProductionChanges } from './features/instance-stats/getProductionChanges';
|
|
import type { IEnvironmentStore, IUnleashStores } from './types';
|
|
import FakeEnvironmentStore from './features/project-environments/fake-environment-store';
|
|
import { SchedulerService } from './services';
|
|
import noLogger from '../test/fixtures/no-logger';
|
|
|
|
const monitor = createMetricsMonitor();
|
|
const eventBus = new EventEmitter();
|
|
const prometheusRegister = register;
|
|
let eventStore: IEventStore;
|
|
let environmentStore: IEnvironmentStore;
|
|
let statsService: InstanceStatsService;
|
|
let stores: IUnleashStores;
|
|
let schedulerService: SchedulerService;
|
|
beforeAll(async () => {
|
|
const config = createTestConfig({
|
|
server: {
|
|
serverMetrics: true,
|
|
},
|
|
});
|
|
stores = createStores();
|
|
eventStore = stores.eventStore;
|
|
environmentStore = new FakeEnvironmentStore();
|
|
stores.environmentStore = environmentStore;
|
|
const versionService = new VersionService(
|
|
stores,
|
|
config,
|
|
createFakeGetActiveUsers(),
|
|
createFakeGetProductionChanges(),
|
|
);
|
|
statsService = new InstanceStatsService(
|
|
stores,
|
|
config,
|
|
versionService,
|
|
createFakeGetActiveUsers(),
|
|
createFakeGetProductionChanges(),
|
|
);
|
|
|
|
schedulerService = new SchedulerService(
|
|
noLogger,
|
|
{
|
|
isMaintenanceMode: () => Promise.resolve(false),
|
|
},
|
|
eventBus,
|
|
);
|
|
|
|
const db = {
|
|
client: {
|
|
pool: {
|
|
min: 0,
|
|
max: 4,
|
|
numUsed: () => 2,
|
|
numFree: () => 2,
|
|
numPendingAcquires: () => 0,
|
|
numPendingCreates: () => 1,
|
|
},
|
|
},
|
|
};
|
|
|
|
await monitor.startMonitoring(
|
|
config,
|
|
stores,
|
|
'4.0.0',
|
|
eventBus,
|
|
statsService,
|
|
schedulerService,
|
|
// @ts-ignore - We don't want a full knex implementation for our tests, it's enough that it actually yields the numbers we want.
|
|
db,
|
|
);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
schedulerService.stop();
|
|
});
|
|
|
|
test('should collect metrics for requests', async () => {
|
|
eventBus.emit(REQUEST_TIME, {
|
|
path: 'somePath',
|
|
method: 'GET',
|
|
statusCode: 200,
|
|
time: 1337,
|
|
});
|
|
|
|
const metrics = await prometheusRegister.metrics();
|
|
expect(metrics).toMatch(
|
|
/http_request_duration_milliseconds\{quantile="0\.99",path="somePath",method="GET",status="200",appName="undefined"\}.*1337/,
|
|
);
|
|
});
|
|
|
|
test('should collect metrics for updated toggles', async () => {
|
|
stores.eventStore.emit(FEATURE_UPDATED, {
|
|
featureName: 'TestToggle',
|
|
project: 'default',
|
|
data: { name: 'TestToggle' },
|
|
});
|
|
|
|
const metrics = await prometheusRegister.metrics();
|
|
expect(metrics).toMatch(
|
|
/feature_toggle_update_total\{toggle="TestToggle",project="default",environment="default",environmentType="production"\} 1/,
|
|
);
|
|
});
|
|
|
|
test('should set environmentType when toggle is flipped', async () => {
|
|
await environmentStore.create({
|
|
name: 'testEnvironment',
|
|
enabled: true,
|
|
type: 'testType',
|
|
sortOrder: 1,
|
|
});
|
|
stores.eventStore.emit(FEATURE_ENVIRONMENT_ENABLED, {
|
|
featureName: 'TestToggle',
|
|
project: 'default',
|
|
environment: 'testEnvironment',
|
|
data: { name: 'TestToggle' },
|
|
});
|
|
|
|
// Wait for event to be processed, not nice, but it works.
|
|
await new Promise((done) => {
|
|
setTimeout(done, 1);
|
|
});
|
|
const metrics = await prometheusRegister.metrics();
|
|
|
|
expect(metrics).toMatch(
|
|
/feature_toggle_update_total\{toggle="TestToggle",project="default",environment="testEnvironment",environmentType="testType"\} 1/,
|
|
);
|
|
});
|
|
|
|
test('should collect metrics for client metric reports', async () => {
|
|
eventBus.emit(CLIENT_METRICS, {
|
|
bucket: {
|
|
toggles: {
|
|
TestToggle: {
|
|
yes: 10,
|
|
no: 5,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const metrics = await prometheusRegister.metrics();
|
|
expect(metrics).toMatch(
|
|
/feature_toggle_usage_total\{toggle="TestToggle",active="true",appName="undefined"\} 10\nfeature_toggle_usage_total\{toggle="TestToggle",active="false",appName="undefined"\} 5/,
|
|
);
|
|
});
|
|
|
|
test('should collect metrics for db query timings', async () => {
|
|
eventBus.emit(DB_TIME, {
|
|
store: 'foo',
|
|
action: 'bar',
|
|
time: 0.1337,
|
|
});
|
|
|
|
const metrics = await prometheusRegister.metrics();
|
|
expect(metrics).toMatch(
|
|
/db_query_duration_seconds\{quantile="0\.99",store="foo",action="bar"\} 0.1337/,
|
|
);
|
|
});
|
|
|
|
test('should collect metrics for function timings', async () => {
|
|
eventBus.emit(FUNCTION_TIME, {
|
|
functionName: 'getToggles',
|
|
className: 'ToggleService',
|
|
time: 0.1337,
|
|
});
|
|
|
|
const metrics = await prometheusRegister.metrics();
|
|
expect(metrics).toMatch(
|
|
/function_duration_seconds\{quantile="0\.99",functionName="getToggles",className="ToggleService"\} 0.1337/,
|
|
);
|
|
});
|
|
|
|
test('should collect metrics for feature toggle size', async () => {
|
|
const metrics = await prometheusRegister.metrics();
|
|
expect(metrics).toMatch(/feature_toggles_total\{version="(.*)"\} 0/);
|
|
});
|
|
|
|
test('should collect metrics for archived feature toggle size', async () => {
|
|
const metrics = await prometheusRegister.metrics();
|
|
expect(metrics).toMatch(/feature_toggles_archived_total 0/);
|
|
});
|
|
|
|
test('should collect metrics for total client apps', async () => {
|
|
await statsService.refreshAppCountSnapshot();
|
|
const metrics = await prometheusRegister.metrics();
|
|
expect(metrics).toMatch(/client_apps_total\{range="(.*)"\} 0/);
|
|
});
|
|
|
|
test('Should collect metrics for database', async () => {
|
|
const metrics = await prometheusRegister.metrics();
|
|
expect(metrics).toMatch(/db_pool_max/);
|
|
expect(metrics).toMatch(/db_pool_min/);
|
|
expect(metrics).toMatch(/db_pool_used/);
|
|
expect(metrics).toMatch(/db_pool_free/);
|
|
expect(metrics).toMatch(/db_pool_pending_creates/);
|
|
expect(metrics).toMatch(/db_pool_pending_acquires/);
|
|
});
|
|
|
|
test('Should collect metrics for client sdk versions', async () => {
|
|
eventStore.emit(CLIENT_REGISTER, {
|
|
sdkVersion: 'unleash-client-node:3.2.5',
|
|
});
|
|
eventStore.emit(CLIENT_REGISTER, {
|
|
sdkVersion: 'unleash-client-node:3.2.5',
|
|
});
|
|
eventStore.emit(CLIENT_REGISTER, {
|
|
sdkVersion: 'unleash-client-node:3.2.5',
|
|
});
|
|
eventStore.emit(CLIENT_REGISTER, {
|
|
sdkVersion: 'unleash-client-java:5.0.0',
|
|
});
|
|
eventStore.emit(CLIENT_REGISTER, {
|
|
sdkVersion: 'unleash-client-java:5.0.0',
|
|
});
|
|
eventStore.emit(CLIENT_REGISTER, {
|
|
sdkVersion: 'unleash-client-java:5.0.0',
|
|
});
|
|
const metrics = await prometheusRegister.getSingleMetricAsString(
|
|
'client_sdk_versions',
|
|
);
|
|
expect(metrics).toMatch(
|
|
/client_sdk_versions\{sdk_name="unleash-client-node",sdk_version="3\.2\.5"\} 3/,
|
|
);
|
|
expect(metrics).toMatch(
|
|
/client_sdk_versions\{sdk_name="unleash-client-java",sdk_version="5\.0\.0"\} 3/,
|
|
);
|
|
eventStore.emit(CLIENT_REGISTER, {
|
|
sdkVersion: 'unleash-client-node:3.2.5',
|
|
});
|
|
const newmetrics = await prometheusRegister.getSingleMetricAsString(
|
|
'client_sdk_versions',
|
|
);
|
|
expect(newmetrics).toMatch(
|
|
/client_sdk_versions\{sdk_name="unleash-client-node",sdk_version="3\.2\.5"\} 4/,
|
|
);
|
|
});
|
|
|
|
test('Should not collect client sdk version if sdkVersion is of wrong format or non-existent', async () => {
|
|
eventStore.emit(CLIENT_REGISTER, { sdkVersion: 'unleash-client-rust' });
|
|
eventStore.emit(CLIENT_REGISTER, {});
|
|
const metrics = await prometheusRegister.getSingleMetricAsString(
|
|
'client_sdk_versions',
|
|
);
|
|
expect(metrics).not.toMatch(/unleash-client-rust/);
|
|
});
|
|
|
|
test('should collect metrics for project disabled numbers', async () => {
|
|
eventStore.emit(PROJECT_ENVIRONMENT_REMOVED, {
|
|
project: 'default',
|
|
environment: 'staging',
|
|
createdBy: 'Jay',
|
|
createdByUserId: 26,
|
|
});
|
|
|
|
const recordedMetric = await prometheusRegister.getSingleMetricAsString(
|
|
'project_environments_disabled',
|
|
);
|
|
expect(recordedMetric).toMatch(
|
|
/project_environments_disabled{project_id=\"default\"} 1/,
|
|
);
|
|
});
|