Actors/Packages/Monitor/API reference
@sigx/actors-monitor · Preview

API reference#

Exports of @sigx/actors-monitor v0.11.0.

Two entries: @sigx/actors-monitor (everything below) and @sigx/actors-monitor/format (the display formatters, also re-exported as format).

Sources#

httpSource(options): MonitorSource#

TypeScript
interface HttpSourceOptions {
    url: string;          // origin of the host's mount, or of your proxy
    secret?: string;      // the ops({ secret }) bearer — server-side only
    base?: string;        // '/_sigx/ops'
    timeoutMs?: number;   // 5000
    fetch?: typeof globalThis.fetch;
}

Rejects with OpsRequestError — { status: number | null } — when a request fails.

MonitorSource#

TypeScript
interface MonitorSource {
    readonly kind: 'embedded' | 'http';
    readonly label: string;   // shown in the UI, so it is never ambiguous WHAT is watched
    snapshot(signal?: AbortSignal, options?: SnapshotOptions): Promise<MonitorSnapshot>;
    close(): Promise<void>;
}
interface SnapshotOptions {
    detail?: boolean;   // per-host actor lists and recent errors — O(activations) on every host
    hostId?: string;    // limit the expensive parts to one host
}

hostViewFromReport(report) folds a cluster HostReport into a HostView.

The snapshot#

TypeScript
interface MonitorSnapshot {
    at: number;
    hosts: readonly HostView[];
    cluster: ClusterView | null;              // null on a single-node host — not an error
    metrics: ActorMetricsSnapshot | null;     // the POLLED host's own metrics, not the cluster's
    activations: readonly ActivationInfo[] | null;
    health: HealthStatus | null;
    partial: boolean;                         // a member did not answer: every total is a LOWER BOUND
}

HostView carries one host's stats, counters, reminderShards, membershipVersion, transports, meta, metrics, health, activations and sockets — each of the last five null when the host reported none, and meta null when it published no placement hints.

meta.node is the machine the host runs on — see meta.node. sockets is the sockets ops section (registry.reportOps('sockets', () => stats.snapshot())), a SocketStatsSnapshot, and only the polled host can carry it: the cluster fan-out has no socket digest, so every other host's is null — "said nothing", the same rule as metrics. withSockets(hosts, hostId, sockets) returns the hosts with sockets set on hostId and null on every other. ClusterView carries from, view, totals (with totals.metrics.hosts as the denominator), reminderShards and unreachable[].

The poll loop#

DashboardState#

TypeScript
new DashboardState({ source, intervalMs?: 1000, history?: 60 })

state.view: DashboardView      // { snapshot, error, paused, intervalMs, lastOk, polls, focus }
state.calls / failures / queued / activations: Series

state.start(): void            // idempotent
state.stop(): Promise<void>    // aborts the in-flight poll, closes the source
state.togglePause(): void
state.focus(hostId: string | null): void   // opens a detail poll, or closes it
state.nudgeInterval(factor: number): void  // clamped to [MIN_INTERVAL_MS, MAX_INTERVAL_MS]

DEFAULT_INTERVAL_MS (1000), MIN_INTERVAL_MS (200), MAX_INTERVAL_MS (60 000) and clampInterval(ms) are exported alongside.

DashboardState, Series and RateTracker hold #private fields. If one reaches you through a reactive proxy — component props in a sigx renderer — unwrap it with toRaw before calling methods on it; @sigx/actors-dashboard exports panelState for exactly that.

Rates#

TypeScript
type Rate = number | null;
interface RateSample { at: number; value: number }

rateBetween(previous: RateSample, current: RateSample): Rate

null when the counter moved backwards, or when no time passed. A reset or a restart makes the previous total meaningless, so the interval reports a gap rather than a negative rate or an enormous positive one.

RateTracker tracks several named counters — observe(series, at, value): Rate, lastWasReset(series, at, value), forget(series), retain(keep). It holds only the previous reading per series: the instantaneous rate is what a dashboard wants, and a running average would smooth away the spike you opened it to look at. The first reading of a series is always null.

Series is a fixed-capacity ring of Rate values for a sparkline — push, values(), peak(), latest(), clear(). A gap is stored as null and must be drawn as a break, not a zero.

Alerts and scope#

TypeScript
alertLines(view: DashboardView): Alert[]          // what is wrong, worst first
interface Alert { text: string; tone: 'danger' | 'warn' }

scopeOf(snapshot): string       // 'this host' | 'cluster · 3 host(s) / 1 node(s)'
polledLabel(view): string       // the host whose OWN numbers these are
coverageNote(snapshot): string | null   // why cluster totals might be a lower bound
hostTone(status): 'danger' | 'warn' | 'dim' | null   // fenced / leaving / other / fine

Hosts and nodes#

TypeScript
nodeCount(hosts): number | null                   // distinct meta.node values
hostSpread(hosts): string                         // '3 host(s) / 1 node(s)', or '3 host(s)'
nodeLabels(hosts): ReadonlyMap<string, string>    // full node name → short label

nodeCount is null, not 0 and not "one per host", when no host reports a node: a fleet that said nothing about where it runs gets no guess. hostSpread drops the node half in that case.

nodeLabels exists because node names differ only in their tail (aks-<pool>-<digits>-vmss000001) and both renderers truncate a cell from the right — two different nodes would read the same. The label drops the prefix every node shares, cut at a separator, and marks the cut: …vmss000000 / …vmss000001. A lone node keeps its full name — the same name repeated down a column is the packed-fleet finding.

Reminder shards#

TypeScript
type ShardState = 'claimed' | 'unclaimed' | 'split';
shardStates(shards): ShardStatus[]      // { label, state, claimants }
unclaimedShards(shards): string[]       // nothing is ticking these reminders
splitShards(shards): string[]           // two claimants: membership views diverged

Three states, three meanings. One claimant is healthy. None means those reminders are not firing, and nothing else in the system surfaces it. Two or more means views have diverged — safe, because the per-shard etag CAS keeps delivery at-most-once, but worth knowing.

Histograms#

TypeScript
percentilePoints(snapshot: HistogramSnapshot | null | undefined): PercentilePoint[]
percentileCeiling(snapshots: readonly (HistogramSnapshot | null | undefined)[]): number

percentilePoints returns p50 / p90 / p99, or three nulls for an absent or empty histogram — never three zeroes. percentileCeiling is the largest value across a set, for a shared axis; 0 means "draw no bars".

@sigx/actors-monitor/format#

count(n), durationMs(ms), uptime(ms), rate(value), gauge(value), percent(numerator, denominator), bytes(value), ellipsis(text, width) — the display formatters both renderers use. rate(null) and gauge(null) render a gap marker, not 0. bytes uses decimal units — 950 B, 12.4 kB, 3.1 MB — and renders null as —.

Next steps#