1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-17 01:17:29 +02:00

fix: add metrics for service account and api tokens (#5478)

This commit is contained in:
Ivar Conradi Østhus 2023-11-29 13:09:30 +01:00 committed by GitHub
parent 961655d5dd
commit 07fcdbb053
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 81 additions and 6 deletions

View File

@ -96,12 +96,26 @@ export class ApiTokenStore implements IApiTokenStore {
});
}
count(): Promise<number> {
async count(): Promise<number> {
return this.db(TABLE)
.count('*')
.then((res) => Number(res[0].count));
}
async countByType(): Promise<Map<string, number>> {
return this.db(TABLE)
.select('type')
.count('*')
.groupBy('type')
.then((res) => {
const map = new Map<string, number>();
res.forEach((row) => {
map.set(row.type.toString(), Number(row.count));
});
return map;
});
}
async getAll(): Promise<IApiToken[]> {
const stopTimer = this.timer('getAll');
const rows = await this.makeTokenProjectQuery();

View File

@ -201,6 +201,16 @@ class UserStore implements IUserStore {
.then((res) => Number(res[0].count));
}
async countServiceAccounts(): Promise<number> {
return this.db(TABLE)
.where({
deleted_at: null,
is_service: true,
})
.count('*')
.then((res) => Number(res[0].count));
}
destroy(): void {}
async exists(id: number): Promise<boolean> {

View File

@ -17,7 +17,11 @@ import { ISegmentStore } from '../../types/stores/segment-store';
import { IRoleStore } from '../../types/stores/role-store';
import VersionService from '../../services/version-service';
import { ISettingStore } from '../../types/stores/settings-store';
import { FEATURES_EXPORTED, FEATURES_IMPORTED } from '../../types';
import {
FEATURES_EXPORTED,
FEATURES_IMPORTED,
IApiTokenStore,
} from '../../types';
import { CUSTOM_ROOT_ROLE_TYPE } from '../../util';
import { type GetActiveUsers } from './getActiveUsers';
import { ProjectModeCount } from '../../db/project-store';
@ -31,6 +35,8 @@ export interface InstanceStats {
versionOSS: string;
versionEnterprise?: string;
users: number;
serviceAccounts: number;
apiTokens: Map<string, number>;
featureToggles: number;
projects: ProjectModeCount[];
contextFields: number;
@ -78,6 +84,8 @@ export class InstanceStatsService {
private eventStore: IEventStore;
private apiTokenStore: IApiTokenStore;
private versionService: VersionService;
private settingStore: ISettingStore;
@ -106,6 +114,7 @@ export class InstanceStatsService {
settingStore,
clientInstanceStore,
eventStore,
apiTokenStore,
}: Pick<
IUnleashStores,
| 'featureToggleStore'
@ -120,6 +129,7 @@ export class InstanceStatsService {
| 'settingStore'
| 'clientInstanceStore'
| 'eventStore'
| 'apiTokenStore'
>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
versionService: VersionService,
@ -142,6 +152,7 @@ export class InstanceStatsService {
this.logger = getLogger('services/stats-service.js');
this.getActiveUsers = getActiveUsers;
this.getProductionChanges = getProductionChanges;
this.apiTokenStore = apiTokenStore;
}
async refreshStatsSnapshot(): Promise<void> {
@ -194,6 +205,8 @@ export class InstanceStatsService {
const [
featureToggles,
users,
serviceAccounts,
apiTokens,
activeUsers,
projects,
contextFields,
@ -213,6 +226,8 @@ export class InstanceStatsService {
] = await Promise.all([
this.getToggleCount(),
this.userStore.count(),
this.userStore.countServiceAccounts(),
this.apiTokenStore.countByType(),
this.getActiveUsers(),
this.getProjectModeCount(),
this.contextFieldStore.count(),
@ -237,6 +252,8 @@ export class InstanceStatsService {
versionOSS: versionInfo.current.oss,
versionEnterprise: versionInfo.current.enterprise,
users,
serviceAccounts,
apiTokens,
activeUsers,
featureToggles,
projects,

View File

@ -31,8 +31,8 @@ export default class MetricsMonitor {
poolMetricsTimer?: Timer;
constructor() {
this.timer = null;
this.poolMetricsTimer = null;
this.timer = undefined;
this.poolMetricsTimer = undefined;
}
startMonitoring(
@ -44,7 +44,7 @@ export default class MetricsMonitor {
db: Knex,
): Promise<void> {
if (!config.server.serverMetrics) {
return;
return Promise.resolve();
}
const { eventStore } = stores;
@ -94,6 +94,15 @@ export default class MetricsMonitor {
name: 'users_total',
help: 'Number of users',
});
const serviceAccounts = new client.Gauge({
name: 'service_accounts_total',
help: 'Number of service accounts',
});
const apiTokens = new client.Gauge({
name: 'api_tokens_total',
help: 'Number of API tokens',
labelNames: ['type'],
});
const usersActive7days = new client.Gauge({
name: 'users_active_7',
help: 'Number of users active in the last 7 days',
@ -214,6 +223,15 @@ export default class MetricsMonitor {
usersTotal.reset();
usersTotal.set(stats.users);
serviceAccounts.reset();
serviceAccounts.set(stats.serviceAccounts);
apiTokens.reset();
for (const [type, value] of stats.apiTokens) {
apiTokens.labels(type).set(value);
}
usersActive7days.reset();
usersActive7days.set(stats.activeUsers.last7);
usersActive30days.reset();
@ -420,6 +438,8 @@ export default class MetricsMonitor {
});
this.configureDbMetrics(db, eventBus);
return Promise.resolve();
}
stopMonitoring(): void {

View File

@ -105,6 +105,8 @@ class InstanceAdminController extends Controller {
sum: 'some-sha256-hash',
timestamp: new Date(2023, 6, 12, 10, 0, 0, 0),
users: 10,
serviceAccounts: 2,
apiTokens: new Map([]),
versionEnterprise: '5.1.7',
versionOSS: '5.1.7',
activeUsers: {

View File

@ -7,4 +7,5 @@ export interface IApiTokenStore extends Store<IApiToken, string> {
setExpiry(secret: string, expiresAt: Date): Promise<IApiToken>;
markSeenAt(secrets: string[]): Promise<void>;
count(): Promise<number>;
countByType(): Promise<Map<string, number>>;
}

View File

@ -32,4 +32,5 @@ export interface IUserStore extends Store<IUser, number> {
incLoginAttempts(user: IUser): Promise<void>;
successfullyLogin(user: IUser): Promise<void>;
count(): Promise<number>;
countServiceAccounts(): Promise<number>;
}

View File

@ -1,5 +1,9 @@
import { IApiTokenStore } from '../../lib/types/stores/api-token-store';
import { IApiToken, IApiTokenCreate } from '../../lib/types/models/api-token';
import {
ApiTokenType,
IApiToken,
IApiTokenCreate,
} from '../../lib/types/models/api-token';
import NotFoundError from '../../lib/error/notfound-error';
import EventEmitter from 'events';
@ -8,6 +12,9 @@ export default class FakeApiTokenStore
extends EventEmitter
implements IApiTokenStore
{
countByType(): Promise<Map<ApiTokenType, number>> {
return Promise.resolve(new Map());
}
tokens: IApiToken[] = [];
async delete(key: string): Promise<void> {

View File

@ -14,6 +14,9 @@ class UserStoreMock implements IUserStore {
this.idSeq = 1;
this.data = [];
}
countServiceAccounts(): Promise<number> {
return Promise.resolve(0);
}
async hasUser({
id,