Add capacity overview with host resource history
This commit is contained in:
@@ -4,6 +4,8 @@ import { config } from '../config.js';
|
||||
import { db } from '../db/index.js';
|
||||
import type { MetricsQuery, MetricsData } from '../../metrics.js';
|
||||
import type { LeaderboardOptions, LeaderboardResponse, PlayerProfile, LeaderboardEntry } from '../../leaderboard.js';
|
||||
import type { CapacityHistory, CapacityQuery } from '../../capacity.js';
|
||||
import { ensureHostSchema } from '../hosts/schema.js';
|
||||
|
||||
class ReportWorker {
|
||||
private worker: Worker | null = null;
|
||||
@@ -17,8 +19,9 @@ class ReportWorker {
|
||||
await worker?.terminate();
|
||||
}
|
||||
run<T>(kind: string, input: unknown): Promise<T> {
|
||||
const requestInput = input as { query?: MetricsQuery; now?: number };
|
||||
const key = JSON.stringify(kind === 'metrics' ? [kind, requestInput.query, Math.floor(requestInput.now! / 30_000)] : [kind, input]);
|
||||
const requestInput = input as { query?: unknown; now?: number };
|
||||
const timed = kind === 'metrics' || kind === 'capacity';
|
||||
const key = JSON.stringify(timed ? [kind, requestInput.query, Math.floor(requestInput.now! / 30_000)] : [kind, input]);
|
||||
const flight = this.flights.get(key);
|
||||
if (flight) return flight;
|
||||
if (this.pending.size >= 64) return Promise.reject(new Error('Reports are busy. Please retry shortly.'));
|
||||
@@ -58,7 +61,13 @@ class ReportWorker {
|
||||
}
|
||||
const metrics = new ReportWorker();
|
||||
const leaderboard = new ReportWorker();
|
||||
const capacity = new ReportWorker();
|
||||
export const metricsReport = (query: MetricsQuery) => metrics.run<MetricsData>('metrics', { query, now: Date.now() });
|
||||
export const leaderboardReport = (query: LeaderboardOptions) => leaderboard.run<LeaderboardResponse>('leaderboard', query);
|
||||
export const playerReport = (guid: string, offset: number, options: LeaderboardOptions) => leaderboard.run<{ profile: PlayerProfile | null; entry: LeaderboardEntry | null }>('player', { guid, offset, options });
|
||||
export async function closeReports() { await Promise.all([metrics.close(), leaderboard.close()]); }
|
||||
export const capacityHistoryReport = (query: CapacityQuery) => {
|
||||
ensureHostSchema(db());
|
||||
// Game changes current placement estimates, not measured host resources.
|
||||
return capacity.run<CapacityHistory>('capacity', { query: { ...query, game: 'all' }, now: Date.now() });
|
||||
};
|
||||
export async function closeReports() { await Promise.all([metrics.close(), leaderboard.close(), capacity.close()]); }
|
||||
|
||||
@@ -3,6 +3,7 @@ import { db } from '../db/index.js';
|
||||
import { readMetrics } from '../fleet/metrics.js';
|
||||
import { getLeaderboard, getPlayerLeaderboardEntry } from '../fleet/leaderboard.js';
|
||||
import { getPlayerProfile } from '../fleet/player-profile.js';
|
||||
import { readCapacityHistory } from '../hosts/capacity-history.js';
|
||||
|
||||
const cache = new Map<string, { value: unknown; bytes: number }>();
|
||||
let bytes = 0;
|
||||
@@ -11,12 +12,18 @@ parentPort!.on('message', ({ id, kind, input }) => {
|
||||
try {
|
||||
const result = db().transaction(() => {
|
||||
const topic = kind === 'metrics' ? 'metrics' : 'leaderboard';
|
||||
const { revision } = db().prepare('SELECT revision FROM report_revisions WHERE topic=?').get(topic) as { revision: number };
|
||||
const now = kind === 'metrics' ? input.now : Date.now();
|
||||
const key = JSON.stringify([kind, revision, kind === 'metrics' ? Math.floor(now / 30_000) : 0, kind === 'metrics' ? input.query : input]);
|
||||
// Capacity refreshes every 30 seconds without adding write triggers
|
||||
// to the heartbeat path. It also has its own worker, so large
|
||||
// history ranges cannot queue ahead of fleet or player reports.
|
||||
const revision = kind === 'capacity' ? 0
|
||||
: (db().prepare('SELECT revision FROM report_revisions WHERE topic=?').get(topic) as { revision: number }).revision;
|
||||
const timed = kind === 'metrics' || kind === 'capacity';
|
||||
const now = timed ? input.now : Date.now();
|
||||
const key = JSON.stringify([kind, revision, timed ? Math.floor(now / 30_000) : 0, timed ? input.query : input]);
|
||||
const saved = cache.get(key);
|
||||
if (saved) { cache.delete(key); cache.set(key, saved); return { value: saved.value, cached: true }; }
|
||||
const value = kind === 'metrics' ? readMetrics(input.query, now)
|
||||
: kind === 'capacity' ? readCapacityHistory(input.query, now)
|
||||
: kind === 'leaderboard' ? getLeaderboard(input)
|
||||
: { profile: getPlayerProfile(input.guid, input.offset, input.options), entry: getPlayerLeaderboardEntry(input.guid, input.options) };
|
||||
const size = Buffer.byteLength(JSON.stringify(value));
|
||||
|
||||
Reference in New Issue
Block a user