Live Monitoring

Real-time in-memory ring buffers

Where monitoring persists metrics for history and alerting, liveMonitoring is the lightweight, zero-database counterpart: rolling in-memory ring buffers of the most recent activity, streamed to a dashboard over SSE. It's built for the live view — what is my service doing right now.

It tracks four feeds — memory, CPU, Dapr events and WebSocket activity — plus request logs, each capped at a configurable size so memory stays bounded. Because nothing touches the database, it's cheap enough to leave on in production.

Feed toggles#

Enable the subsystem and pick which feeds to capture. Each feed is independent, so you can watch just memory and CPU, or add the Dapr and WebSocket streams when debugging realtime issues.

config.nucleus.json — liveMonitoring
1{2  "liveMonitoring": {3    "enabled": true,4    "logMemory": true,5    "logCpu": true,6    "logDapr": false,7    "logWebSocket": true,8    "memoryLogInterval": 2000,9    "cpuLogInterval": 2000,10    "memoryLogLimit": 120,11    "cpuLogLimit": 120,12    "daprLogLimit": 100,13    "wsLogLimit": 200,14    "requestLogLimit": 200,15    "streamInterval": 1000,16    "basePath": "/live-monitoring"17  }18}
enabledbooleanOptional

Master switch for live monitoring.

Defaultfalse
logMemorybooleanOptional

Capture a rolling memory-usage feed.

logCpubooleanOptional

Capture a rolling CPU-usage feed.

logDaprbooleanOptional

Capture Dapr sidecar events (pub/sub publish/subscribe, state get/save/delete, service invocation).

logWebSocketbooleanOptional

Capture WebSocket connection and message activity.

Sampling intervals#

How often samples are taken and how often the buffers are pushed to connected clients. Values are in milliseconds. Defaults: memoryLogInterval / cpuLogInterval 1000ms, streamInterval 150ms; every *LogLimit defaults to 100; and the four feed toggles default to true once the subsystem is enabled.

memoryLogIntervalnumberOptional

Milliseconds between memory samples.

cpuLogIntervalnumberOptional

Milliseconds between CPU samples.

streamIntervalnumberOptional

Milliseconds between SSE pushes to connected dashboards — the cadence at which new buffer data is flushed to the client.

Buffer limits#

Each feed is a bounded ring buffer: it accumulates until it grows past 2× its configured limit, then bulk-trims back to the limit — so memory stays bounded and the newest samples win. One consequence: GET {basePath}/logs returns the raw buffers, so it can hand back up to ~2× the limit between trims (e.g. requestLogLimit 100 can return ~200 entries), whereas the SSE snapshot is always sliced to exactly the limit.

memoryLogLimitnumberOptional

Max retained memory samples.

cpuLogLimitnumberOptional

Max retained CPU samples.

daprLogLimitnumberOptional

Max retained Dapr events.

wsLogLimitnumberOptional

Max retained WebSocket events.

requestLogLimitnumberOptional

Max retained request log entries.

Endpoint#

Served under basePath (default /monitoring — set a distinct one to avoid overlapping the persisted monitoring feature). Routes (all godmin-guarded): GET {basePath}/health, GET {basePath}/settings, PATCH {basePath}/settings, GET {basePath}/logs, and GET {basePath}/logs/stream (the SSE stream).

basePathstringOptional

Route prefix for the live-monitoring endpoints, including the SSE stream the dashboard subscribes to.

Under the hood — LiveMonitoringService#

Everything here lives in process memory — no Redis, no database. The service keeps small ring buffers and streams deltas, which is what makes it cheap enough to leave on.

ring buffersin-memory, trimmedOptional

memory, cpu, requests, dapr and ws each have their own array, trimmed back to its configured limit once it grows past 2× — so memory use is bounded and the newest samples win. Nothing survives a restart, by design.

collectorsprocess.memoryUsage / os.cpusOptional

A memory timer samples rss/heapUsed/heapTotal every memoryLogInterval; a CPU timer computes utilisation from os.cpus() deltas every cpuLogInterval. recordRequest always pushes the request feed (no toggle); recordDaprEvent / recordWsEvent push the Dapr and WebSocket feeds and are gated by the logDapr / logWebSocket toggles.

delta streaminggetSnapshot + getUpdatesSinceOptional

The SSE endpoint sends one full getSnapshot, then on streamInterval calls getUpdatesSince(timestamps) and pushes only the new samples per feed — so the dashboard stays live without resending the whole buffer each tick. Three named SSE events distinguish the frames: `snapshot` (the full buffer, once on connect), `update` (emitted only on ticks that have new samples), and `heartbeat` ({timestamp} keep-alive emitted on idle ticks when getUpdatesSince returns nothing). Clients should treat `heartbeat` as a no-data ping, not a data frame. Note the two data frames carry different shapes: beyond the five feeds (memory, cpu, requests, dapr, ws), the `snapshot` frame also includes a `workers` array — currently a single entry for the running process (pid, a `workerId` that is presently always null, and the latest memory/cpu sample); cross-process aggregation is not yet wired, so it is not one-entry-per-process — plus `logLimits` and `configs` echoing the current settings; the `update` frame carries only the per-feed deltas plus a `timestamp` (no workers/logLimits/configs).

live-tunablechangeSettingsOptional

The settings endpoint flips the Dapr/WebSocket feeds on/off (checked at record time, so they flip fully in all cases), toggles memory/CPU capture, changes intervals (restarting the affected collector) and resizes buffers at runtime — no redeploy, which is the point of a debugging feed. One edge case: because memory/CPU default to on, their collector is already running and re-toggling takes effect immediately — but a memory/CPU feed explicitly configured OFF at startup only begins sampling once an interval change (or restart) relaunches its collector; a bare on-toggle sets the flag but doesn't start the timer.

Related sections