Improve application responsiveness and background processing
This commit is contained in:
@@ -4,21 +4,57 @@ import { isProvisioning } from './provisioning.js';
|
||||
import { restartSettling } from '../provisioning/restarts.js';
|
||||
import { type ServerRow } from './servers.js';
|
||||
import { telemetryReport, latestGameSubmission } from './status-evidence.js';
|
||||
import { db } from '../db/index.js';
|
||||
import { parseGameEvent, type GameTelemetry } from '../../telemetry.js';
|
||||
import { restartRequestInFlight } from '../provisioning/restarts.js';
|
||||
|
||||
interface Evidence { provisioning: boolean; settling: boolean; telemetry: GameTelemetry | null; submission: string | null }
|
||||
|
||||
/** One set of evidence reads per snapshot, rather than four queries per server. */
|
||||
export function observationReader() {
|
||||
const handle = db();
|
||||
const operations = new Map((handle.prepare(`SELECT server, action, state FROM server_deployments
|
||||
WHERE rowid IN (SELECT MAX(rowid) FROM server_deployments WHERE server_deleted_at IS NULL GROUP BY server)`).all() as
|
||||
{ server: string; action: string; state: string }[]).map(row => [row.server, row]));
|
||||
const restarts = new Map((handle.prepare('SELECT server, requested_at, healthy_at FROM restart_rollout').all() as
|
||||
{ server: string; requested_at: string; healthy_at: string | null }[]).map(row => [row.server, row]));
|
||||
const submissions = new Map((handle.prepare(`SELECT server, MAX(at) AS at FROM (
|
||||
SELECT server, submitted_at AS at FROM server_deployments WHERE server_deleted_at IS NULL AND action IN ('create','start','restart','rebuild')
|
||||
UNION ALL SELECT server, requested_at AS at FROM restart_rollout) GROUP BY server`).all() as
|
||||
{ server: string; at: string | null }[]).map(row => [row.server, row.at]));
|
||||
const reports = new Map<string, GameTelemetry>();
|
||||
for (const row of handle.prepare('SELECT server, received_at, payload FROM server_telemetry WHERE payload IS NOT NULL').all() as
|
||||
{ server: string; received_at: string; payload: string }[]) {
|
||||
try {
|
||||
const saved = JSON.parse(row.payload) as GameTelemetry;
|
||||
const event = parseGameEvent(saved);
|
||||
if (event.server === row.server) reports.set(row.server, { ...event, receivedAt: row.received_at, timing: saved.timing });
|
||||
} catch { /* Invalid retained telemetry is unavailable. */ }
|
||||
}
|
||||
return (server: ServerRow) => {
|
||||
const operation = operations.get(server.name), restart = restarts.get(server.name);
|
||||
return serverObservation(server, {
|
||||
provisioning: !!operation && ['create','rebuild','start'].includes(operation.action) && ['queued','running','awaiting-health'].includes(operation.state),
|
||||
settling: restartRequestInFlight(server.name) || !!(restart && !restart.healthy_at && Date.now() - Date.parse(restart.requested_at) < 15_000),
|
||||
telemetry: reports.get(server.name) ?? null, submission: submissions.get(server.name) ?? null
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** Shared snapshot for REST, status events, metrics, and other server-side readers. */
|
||||
export function serverObservation(server: ServerRow) {
|
||||
export function serverObservation(server: ServerRow, evidence?: Evidence) {
|
||||
let roster = null;
|
||||
try { roster = server.last_roster ? JSON.parse(server.last_roster) : null; } catch { /* unavailable */ }
|
||||
const rcon: GameObservation = { last_players: server.last_players, last_map: server.last_map,
|
||||
last_gametype: server.last_gametype, last_seen_at: server.last_seen_at, last_error: server.last_error, roster };
|
||||
const provisioning = isProvisioning(server.name);
|
||||
const state = restartSettling(server.name) ? 'restarting' : server.container_state;
|
||||
const provisioning = evidence ? evidence.provisioning : isProvisioning(server.name);
|
||||
const state = (evidence ? evidence.settling : restartSettling(server.name)) ? 'restarting' : server.container_state;
|
||||
const ready = !provisioning && (!state || state === 'running');
|
||||
const health = provisioning ? 'provisioning' : classifyHealth(server.last_seen_at, server.last_error, state);
|
||||
return selectServerObservation({ ...server, ...rcon, rcon_status: rcon, container_state: state, health,
|
||||
status_source: null as 'telemetry' | 'rcon' | null,
|
||||
telemetry: telemetryReport(server.name) ?? undefined,
|
||||
telemetry_after: latestGameSubmission(server.name),
|
||||
telemetry: (evidence ? evidence.telemetry : telemetryReport(server.name)) ?? undefined,
|
||||
telemetry_after: evidence ? evidence.submission : latestGameSubmission(server.name),
|
||||
last_error: ready ? server.last_error : null,
|
||||
replySnippet: ready ? server.last_reply_snippet : null,
|
||||
active_since: ready ? server.active_since : null, inactive_since: ready ? server.inactive_since : null,
|
||||
|
||||
Reference in New Issue
Block a user