1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-11-10 01:19:53 +01:00

chore: add edge instances to instance stats

This commit is contained in:
Nuno Góis 2025-10-21 12:26:06 +01:00
parent 14b4809c8e
commit 8e52e24313
No known key found for this signature in database
GPG Key ID: 71ECC689F1091765
8 changed files with 122 additions and 0 deletions

View File

@ -65,6 +65,7 @@ export const InstanceStats: FC = () => {
},
{ title: 'Release templates', value: stats?.releaseTemplates },
{ title: 'Release plans', value: stats?.releasePlans },
{ title: 'Edge instances', value: stats?.edgeInstances },
];
if (stats?.versionEnterprise) {

View File

@ -50,6 +50,10 @@ import {
} from './getLicensedUsers.js';
import { ReleasePlanStore } from '../release-plans/release-plan-store.js';
import { ReleasePlanTemplateStore } from '../release-plans/release-plan-template-store.js';
import {
createFakeGetEdgeInstances,
createGetEdgeInstances,
} from './getEdgeInstances.js';
export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
const { eventBus, getLogger, flagResolver } = config;
@ -134,6 +138,7 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
const getActiveUsers = createGetActiveUsers(db);
const getProductionChanges = createGetProductionChanges(db);
const getLicencedUsers = createGetLicensedUsers(db);
const getEdgeInstances = createGetEdgeInstances(db);
const versionService = new VersionService(versionServiceStores, config);
const instanceStatsService = new InstanceStatsService(
@ -143,6 +148,7 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
getActiveUsers,
getProductionChanges,
getLicencedUsers,
getEdgeInstances,
);
return instanceStatsService;
@ -199,6 +205,7 @@ export const createFakeInstanceStatsService = (config: IUnleashConfig) => {
const getActiveUsers = createFakeGetActiveUsers();
const getLicensedUsers = createFakeGetLicensedUsers();
const getProductionChanges = createFakeGetProductionChanges();
const getEdgeInstances = createFakeGetEdgeInstances();
const versionService = new VersionService(versionServiceStores, config);
const instanceStatsService = new InstanceStatsService(
@ -208,6 +215,7 @@ export const createFakeInstanceStatsService = (config: IUnleashConfig) => {
getActiveUsers,
getProductionChanges,
getLicensedUsers,
getEdgeInstances,
);
return instanceStatsService;

View File

@ -0,0 +1,59 @@
import type { Db } from '../../types/index.js';
const TABLE = 'edge_node_presence';
const GRACE_PERCENTAGE = 0.05;
export type GetEdgeInstances = () => Promise<{
last30: number;
last60: number;
last90: number;
}>;
export const createGetEdgeInstances =
(db: Db): GetEdgeInstances =>
async () => {
const result = await db
.with('buckets', (qb) =>
qb
.from(TABLE)
.whereRaw("bucket_ts >= NOW() - INTERVAL '90 days'")
.groupBy('bucket_ts')
.select(
db.raw('bucket_ts'),
db.raw('COUNT(*)::int AS active_nodes'),
),
)
.from('buckets')
.select({
last30: db.raw(
`COALESCE(CEIL(AVG(active_nodes) FILTER (WHERE bucket_ts >= NOW() - INTERVAL '30 days') * ?)::int, 0)`,
[1 + GRACE_PERCENTAGE],
),
last60: db.raw(
`COALESCE(CEIL(AVG(active_nodes) FILTER (WHERE bucket_ts >= NOW() - INTERVAL '60 days') * ?)::int, 0)`,
[1 + GRACE_PERCENTAGE],
),
last90: db.raw(
`COALESCE(CEIL(AVG(active_nodes) FILTER (WHERE bucket_ts >= NOW() - INTERVAL '90 days') * ?)::int, 0)`,
[1 + GRACE_PERCENTAGE],
),
})
.first();
return {
last30: Number(result?.last30 ?? 0),
last60: Number(result?.last60 ?? 0),
last90: Number(result?.last90 ?? 0),
};
};
export const createFakeGetEdgeInstances =
(
edgeInstances: Awaited<ReturnType<GetEdgeInstances>> = {
last30: 0,
last60: 0,
last90: 0,
},
): GetEdgeInstances =>
() =>
Promise.resolve(edgeInstances);

View File

@ -33,6 +33,7 @@ import type { GetLicensedUsers } from './getLicensedUsers.js';
import type { IFeatureUsageInfo } from '../../services/version-service.js';
import type { ReleasePlanTemplateStore } from '../release-plans/release-plan-template-store.js';
import type { ReleasePlanStore } from '../release-plans/release-plan-store.js';
import type { GetEdgeInstances } from './getEdgeInstances.js';
export type TimeRange = 'allTime' | '30d' | '7d';
@ -74,6 +75,7 @@ export interface InstanceStats {
maxConstraintValues: number;
releaseTemplates?: number;
releasePlans?: number;
edgeInstances?: Awaited<ReturnType<GetEdgeInstances>>;
}
export type InstanceStatsSigned = Omit<InstanceStats, 'projects'> & {
@ -124,6 +126,8 @@ export class InstanceStatsService {
getProductionChanges: GetProductionChanges;
getEdgeInstances: GetEdgeInstances;
private featureStrategiesReadModel: IFeatureStrategiesReadModel;
private featureStrategiesStore: IFeatureStrategiesStore;
@ -185,6 +189,7 @@ export class InstanceStatsService {
getActiveUsers: GetActiveUsers,
getProductionChanges: GetProductionChanges,
getLicencedUsers: GetLicensedUsers,
getEdgeInstances: GetEdgeInstances,
) {
this.strategyStore = strategyStore;
this.userStore = userStore;
@ -209,6 +214,8 @@ export class InstanceStatsService {
'getProductionChanges',
getProductionChanges.bind(this),
);
this.getEdgeInstances = () =>
this.memorize('getEdgeInstances', getEdgeInstances.bind(this));
this.apiTokenStore = apiTokenStore;
this.clientMetricsStore = clientMetricsStoreV2;
this.flagResolver = flagResolver;
@ -356,6 +363,7 @@ export class InstanceStatsService {
maxConstraints,
releaseTemplates,
releasePlans,
edgeInstances,
] = await Promise.all([
this.getToggleCount(),
this.getArchivedToggleCount(),
@ -402,6 +410,7 @@ export class InstanceStatsService {
),
this.getReleaseTemplates(),
this.getReleasePlans(),
this.getEdgeInstances(),
]);
return {
@ -442,6 +451,7 @@ export class InstanceStatsService {
maxConstraints: maxConstraints?.count ?? 0,
releaseTemplates,
releasePlans,
edgeInstances,
};
}
@ -471,6 +481,7 @@ export class InstanceStatsService {
hostedBy,
releaseTemplates,
releasePlans,
edgeInstances,
] = await Promise.all([
this.getToggleCount(),
this.getRegisteredUsers(),
@ -496,6 +507,7 @@ export class InstanceStatsService {
this.getHostedBy(),
this.getReleaseTemplates(),
this.getReleasePlans(),
this.getEdgeInstances(),
]);
const versionInfo = await this.versionService.getVersionInfo();
@ -533,6 +545,9 @@ export class InstanceStatsService {
hostedBy,
releaseTemplates,
releasePlans,
edgeInstances30: edgeInstances.last30,
edgeInstances60: edgeInstances.last60,
edgeInstances90: edgeInstances.last90,
};
return featureInfo;
}

View File

@ -284,6 +284,34 @@ export const instanceAdminStatsSchema = {
example: 1,
description: 'The number of release plans in this instance',
},
edgeInstances: {
type: 'object',
description:
'The billable number of edge instances in the last 30, 60 and 90 days',
properties: {
last30: {
type: 'integer',
description:
'The billable number of edge instances in the last 30 days',
example: 10,
minimum: 0,
},
last60: {
type: 'integer',
description:
'The billable number of edge instances in the last 60 days',
example: 12,
minimum: 0,
},
last90: {
type: 'integer',
description:
'The billable number of edge instances in the last 90 days',
example: 15,
minimum: 0,
},
},
},
sum: {
type: 'string',
description:

View File

@ -133,6 +133,11 @@ class InstanceAdminController extends Controller {
maxConstraintValues: 123,
releaseTemplates: 3,
releasePlans: 5,
edgeInstances: {
last30: 10,
last60: 15,
last90: 20,
},
};
}

View File

@ -45,6 +45,9 @@ const fakeTelemetryData = {
hostedBy: 'self-hosted',
releaseTemplates: 2,
releasePlans: 4,
edgeInstances30: 0,
edgeInstances60: 0,
edgeInstances90: 0,
};
test('yields current versions', async () => {

View File

@ -54,6 +54,9 @@ export interface IFeatureUsageInfo {
hostedBy: string;
releaseTemplates: number;
releasePlans: number;
edgeInstances30?: number;
edgeInstances60?: number;
edgeInstances90?: number;
}
export default class VersionService {