Nebula
Orchestrator

WorkerLoadTracker

How busy each worker is, as a fraction of its tick budget: WorkerInfo.TickMs / tickPeriodMs, sampled from every heartbeat and kept for a short window (NebulaConfig.ScaleWindowSeconds).

generated from the sources
public sealed class WorkerLoadTracker

How busy each worker is, as a fraction of its tick budget: WorkerInfo.TickMs / tickPeriodMs, sampled from every heartbeat and kept for a short window (NebulaConfig.ScaleWindowSeconds). The 90th percentile of that window is the scaling signal: a GC spike does not launch a machine, a sustained 80 % does.

The tracker is a plain object with an injected clock, so the scaler can be exercised over synthetic histories without a mesh or Unity time. Samples are kept in a per-worker ring of structs and the percentile is taken from a reused scratch buffer, so a pass allocates nothing once the mesh is warm.

Constructors

WorkerLoadTracker

public WorkerLoadTracker(Func<double> now);
ParameterDescription
nowSeconds on a monotonic clock. Tests pass their own; the orchestrator passes Unity time.

Fields

TickPeriodMs

public const float TickPeriodMs = 1000f / NetworkTime.TickRate;

One simulated tick's wall-clock budget, in milliseconds (NetworkTime.TickRate).

Percentile

public const float Percentile = 0.9f;

Fraction of the window used as "the" utilization of a worker.

WindowSeconds

public float WindowSeconds = 20f;

Length of the window each worker keeps, in seconds.

MinSamples

public int MinSamples = 3;

How many heartbeats a worker must have inside the window before its utilization means anything. Below this it reads as 0 % busy simply because nothing has been reported yet, and the worker is not considered for retirement (WorkerScaler.IsWarm).

Properties

Count

public int Count { get; }

Workers with at least one live sample.

Methods

UtilizationOf

public static float UtilizationOf(float tickMs);

Utilization of one tick time, as a fraction of the tick budget (0.5 = half a tick period).

Sample

public void Sample(string workerId, float tickMs);

Record one heartbeat's tick time. Repeated identical heartbeats are kept: the window is time-based.

SampleAt

public void SampleAt(string workerId, float tickMs, double at);

Record one sample at an explicit time (tests, and replaying a batch of heartbeats).

Expire

public void Expire();

Drop samples (and whole workers) older than the window. Called once per pass so a dead worker's history fades.

SampleCount

public int SampleCount(string workerId);

How many samples a worker has inside the window right now.

IsWarm

public bool IsWarm(string workerId);

Has this worker reported enough to be judged (MinSamples)?

Forget

public void Forget(string workerId);

Forget a worker outright (it died or retired).

Utilization

public float Utilization(string workerId);

The 90th percentile of a worker's window, or 0 when it has never reported. Nearest-rank on the samples still inside the window, so one bad tick in twenty does not count as the worker's load.

CopyUtilization

public void CopyUtilization(IEnumerable<WorkerInfo> workers, Dictionary<string, float> result);

Fill result (cleared first) with the p90 utilization of every worker in workers, including ones with no samples yet (0).

Attribute

public static void Attribute(IReadOnlyDictionary<string, float> workerUtilization, IReadOnlyList<LeaseInfo> leases, IReadOnlyDictionary<string, ContainerLoad> occupancy, in CostWeights weights, Dictionary<string, float> result);

Spread each worker's p90 utilization over the containers it leases, in proportion to what CostWeights says their occupants cost. A container nobody reported gets CostWeights.Base, so an empty container still carries its share of the worker's floor. A worker holding no container contributes nothing; its load shows up in the mesh's mean only.

This is a proxy: a container full of static props costs less than one full of NPCs chasing paths. The hint's cost multiplier (phase 3) is the manual correction.

ParameterDescription
resultCleared and filled with container id -> attributed utilization.

On this page