Support game-wide autoscaling rules with per-map targets
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { ScalingDecision, ScalingMember, ScalingPolicy, ScalingSnapshot } from '../../autoscaling.js';
|
||||
import { parseServerName, zombieMaps } from '../../map-aliases.js';
|
||||
import { ONLINE_MAX_MS } from '../../server-freshness.js';
|
||||
|
||||
export interface EmptyObservation { identity: string; observedAt: number | null; emptySince: number | null; evaluatedAt: number }
|
||||
@@ -14,13 +15,32 @@ export function emptyObservation(member: ScalingMember, previous: EmptyObservati
|
||||
}
|
||||
|
||||
export function planScaling(policy: ScalingPolicy, snapshot: ScalingSnapshot, now: number): ScalingDecision {
|
||||
if (policy.mapSlug === '*') {
|
||||
const maps = zombieMaps(policy.game);
|
||||
const decisions = maps.map(map => {
|
||||
const matches = (name: string) => (snapshot.members.find(m => m.name === name)?.mapSlug ?? parseServerName(name)?.mapSlug) === map.slug;
|
||||
return planScaling({ ...policy, mapSlug: map.slug }, {
|
||||
...snapshot, members: snapshot.members.filter(m => matches(m.name)),
|
||||
reservedNames: snapshot.reservedNames.filter(matches), keyedNames: snapshot.keyedNames.filter(matches)
|
||||
}, now);
|
||||
});
|
||||
// Serialize game-wide work and prefer replenishing any map before trimming another.
|
||||
const pending = snapshot.members.find(m => m.state === 'creating' || m.state === 'removing');
|
||||
const selected = decisions.find(d => d.action === 'create') ?? decisions.find(d => d.action === 'destroy')
|
||||
?? decisions.find(d => d.deficit > 0 || d.unknown > 0) ?? decisions[0];
|
||||
const map = maps[decisions.indexOf(selected)];
|
||||
return { ...selected, action: pending ? 'wait' : selected.action, server: pending ? null : selected.server,
|
||||
reason: pending ? pending.blocker ?? `Waiting for ${pending.name}: ${pending.state}.` : `${map.friendly}: ${selected.reason}`,
|
||||
desired: decisions.reduce((n, d) => n + d.desired, 0), deficit: decisions.reduce((n, d) => n + d.deficit, 0), occupied: decisions.reduce((n, d) => n + d.occupied, 0),
|
||||
empty: decisions.reduce((n, d) => n + d.empty, 0), pending: decisions.reduce((n, d) => n + d.pending, 0), unknown: decisions.reduce((n, d) => n + d.unknown, 0) };
|
||||
}
|
||||
const { members } = snapshot;
|
||||
const occupied = members.filter(m => m.state === 'occupied').length, empty = members.filter(m => m.state === 'empty').length;
|
||||
const pending = members.filter(m => m.state === 'creating' || m.state === 'removing').length;
|
||||
const unknown = members.filter(m => m.state === 'unknown' || m.state === 'stopped').length;
|
||||
const desired = Math.min(policy.maximum, Math.max(policy.minimum, occupied + policy.spares));
|
||||
const result = (action: ScalingDecision['action'], reason: string, server: string | null = null): ScalingDecision =>
|
||||
({ action, reason, server, desired, occupied, empty, pending, unknown });
|
||||
({ action, reason, server, desired, deficit: Math.max(0, policy.spares - empty), occupied, empty, pending, unknown });
|
||||
if (policy.mode === 'off') return result('wait', policy.pausedReason ?? 'Policy is off.');
|
||||
if (policy.pausedReason) return result('wait', policy.pausedReason);
|
||||
if (snapshot.blockers.length) return result('wait', snapshot.blockers[0]);
|
||||
|
||||
Reference in New Issue
Block a user