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
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
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
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
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
type Rate = number | null;
interface RateSample { at: number; value: number }
rateBetween(previous: RateSample, current: RateSample): Rate
nullwhen 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
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
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
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
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
- Overview — the four rules.
- Installation — sources and the proxy.
@sigx/actors-dashboard— the components over this.
