mirror of
https://github.com/HeyPuter/puter.git
synced 2026-09-21 12:46:00 +00:00
feat: add alerting for usage abuse (#2413)
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test-backend (24.x) (push) Has been cancelled
test / API tests (node env, api-test) (24.x) (push) Has been cancelled
test / puterjs (node env, vitest) (24.x) (push) Has been cancelled
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test-backend (24.x) (push) Has been cancelled
test / API tests (node env, api-test) (24.x) (push) Has been cancelled
test / puterjs (node env, vitest) (24.x) (push) Has been cancelled
This commit is contained in:
@@ -8,6 +8,7 @@ import { DEFAULT_FREE_SUBSCRIPTION, DEFAULT_TEMP_SUBSCRIPTION, GLOBAL_APP_KEY, M
|
||||
import { COST_MAPS } from './costMaps/index.js';
|
||||
import { SUB_POLICIES } from './subPolicies/index.js';
|
||||
import { AppTotals, MeteringServiceDeps, UsageAddons, UsageByType, UsageRecord } from './types.js';
|
||||
import { toMicroCents } from './utils.js';
|
||||
/**
|
||||
* Handles usage metering and supports stubbs for billing methods for current scoped actor
|
||||
*/
|
||||
@@ -15,6 +16,7 @@ export class MeteringService {
|
||||
|
||||
static GLOBAL_SHARD_COUNT = 1000; // number of global usage shards to spread writes across
|
||||
static APP_SHARD_COUNT = 1000; // number of app usage shards to spread writes across
|
||||
static MAX_GLOBAL_USAGE_PER_MINUTE = toMicroCents(.2); // 20 cents per minute max global usage to help detect abuse
|
||||
#kvStore: DynamoKVStore;
|
||||
#superUserService: SUService;
|
||||
#alarmService: AlarmService;
|
||||
@@ -24,6 +26,9 @@ export class MeteringService {
|
||||
this.#kvStore = kvStore;
|
||||
this.#alarmService = alarmService;
|
||||
this.#eventService = eventService;
|
||||
setInterval(() => {
|
||||
this.#checkRateOfChange();
|
||||
}, 1000 * 60 * 5); // check every 5 minutes
|
||||
}
|
||||
|
||||
utilRecordUsageObject<T extends Record<string, number>>(trackedUsageObject: T, actor: Actor, modelPrefix: string, costsOverrides?: Partial<Record<keyof T, number>>) {
|
||||
@@ -641,4 +646,37 @@ export class MeteringService {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async #checkRateOfChange () {
|
||||
const globalUsage = await this.getGlobalUsage();
|
||||
const now = Date.now();
|
||||
const lastChange = await this.#superUserService.sudo(async () => {
|
||||
return this.#kvStore.get({ key: `${METRICS_PREFIX}:lastGlobalUsageCheck` }) as Promise<{ total: number, timestamp: number } | null>;
|
||||
});
|
||||
|
||||
const currTotal = globalUsage.total;
|
||||
|
||||
if ( lastChange ) {
|
||||
const timeDelta = now - lastChange.timestamp;
|
||||
const usageDelta = currTotal - lastChange.total;
|
||||
const usagePerMinute = (usageDelta / (timeDelta / 60000));
|
||||
|
||||
if ( usagePerMinute > MeteringService.MAX_GLOBAL_USAGE_PER_MINUTE ) {
|
||||
this.#alarmService.create('metering:excessiveGlobalUsageRate', `Global usage rate is excessive: ${usagePerMinute} micro-cents per minute`, {
|
||||
usagePerMinute,
|
||||
maxAllowedPerMinute: MeteringService.MAX_GLOBAL_USAGE_PER_MINUTE,
|
||||
});
|
||||
}
|
||||
}
|
||||
await this.#superUserService.sudo(async () => {
|
||||
await this.#kvStore.set({
|
||||
key: `${METRICS_PREFIX}:lastGlobalUsageCheck`,
|
||||
value: {
|
||||
total: currTotal,
|
||||
timestamp: now,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user