Skip to content

Realtime Events (SignalR)

Live updates travel over one SignalR hub at /hub/realtime. Authentication uses the same JWT as the REST API, passed as ?access_token=<jwt> (or an Authorization header).

High-frequency streams are opt-in per group, joined only while a consuming screen is mounted and re-joined automatically on reconnect:

GroupCarriesConsumed by
sensorsLive sensor reading batchesRealtime Monitor
predictionsNew prediction batchesDashboard charts
logsFull log stream (all levels)LogViewer page only
alertsWarning/Error log entries onlyNotification bell, app-wide

The alerts group exists so warnings surface everywhere without streaming the full logs firehose to every dashboard.

The hub publishes twelve events, listed here in full. Field names arrive camel-cased: the JSON hub protocol applies ASP.NET Core’s camelCase policy to the server-side payload records. Timestamps are Unix epoch milliseconds unless noted.

EventDelivered toPayload
ConnectedThe connecting client onlyconnectionId, timestamp
SensorReadingBatchsensors groupticks — the sensor ticks coalesced since the last flush (shape below)
NewPredictionBatchpredictions grouppredictions — the prediction records coalesced since the last flush (shape below)
LogEntrylogs groupid, timestamp (ISO-8601 string), source, level, message, logger (nullable), correlationId (nullable; the same id on every line of one tick, from the input read to each output write), inputDatasourceId, inputDatasourceName, outputDatasourceId, outputDatasourceName (all nullable; set when the line belongs to an inference tick — names are as of the time of the line, ids stay stable across renames)
AlertLogEntryalerts groupSame shape as LogEntry, carrying warning and error entries only
InferenceStateChangedAll clientsstate ∈ idle · ready · running, activeInputDatasourceId (nullable), loadedModelId (nullable), loadedModelVersion (nullable), timestamp — emitted on every lifecycle transition
InferenceFaultedAll clientsreason, datasourceId (nullable), timestamp, severity (defaults to error), code (nullable) — also raises the blocking fault banner
HealthMetricsUpdateAll clientsstatus, timestamp, gpu (nullable object), cpu, memory, storage (nullable object), uptimeSeconds (inference engine), backendUptimeSeconds, engineRestartCount, lastEngineRestartAt (nullable) (nested shapes below)
ModelActivatedAll clientsmodelId, version, inputShape, outputShape, fileSizeMb
ModelUploadProgressAll clientsmodelId, version, phase, isError, errorMessage (nullable)
ModelUploadFailedAll clientsmodelId, version, reason
OutputWriteFailedAll clientsdatasourceId, reason, code (nullable), severity (warning), timestamp, count — coalesced per sink: the first failure alerts immediately, then at most one follow-up per 30 s window with count repeats folded in

An event name is the method name on the server’s hub client interface, so the names above are the literal wire names a client subscribes to.

Each entry of SensorReadingBatch.ticks is one tick — every channel value stamped at a single producer time:

FieldMeaning
timestampProducer time for the whole tick
valuesOne value per channel; values[i] is channel index i

HealthMetricsUpdate nests these objects:

ObjectFields
gpuutilization, memoryUsedMb, memoryTotalMb, memoryUsagePercent, temperatureCelsius — the whole object is null on a host with no GPU, and before the first successful poll
gpu.memoryKindshared (Jetson: the GPU uses system RAM, so the GPU memory fields repeat the host reading), dedicated (discrete VRAM), or null when unknown
cpuusagePercent, temperatureCelsius (null when the host exposes no CPU thermal sensor)
memoryusagePercent, usedMb, totalMb — the host’s RAM
memory swapswapUsedMb, swapTotalMb, swapUsagePercent — null when the host has no swap or it cannot be read
memory.inferenceusedMb, limitMb, usagePercent, source (nullable) — the inference container against the limit it is stopped at (RAM plus swap); the whole object is null when the container has no limit or it cannot be read
storagetotalGb, usedGb, freeGb, usagePercent, databaseMb — each nullable; the whole object is null when the disk cannot be read

The memoryKind, swap and inference fields are additive: the inference engine reports them, so against an older engine they arrive as null and the dashboard shows them as not reported. storage is measured by the backend itself.

Each entry of NewPredictionBatch.predictions is one prediction record:

FieldMeaning
requestIdIdentifier of the inference request that produced the record
predictionsModel output, one value per output channel
confidenceScoresOne confidence value per output channel
modelIdModel that produced the prediction
inferenceTimeMsModel execution time
timestamp, windowStartTimestamp, windowEndTimestamp, emittedAtSee the next section

A prediction describes a past window of input. All timestamps are Unix epoch milliseconds from one clock — the backend stamps each sample once at read time, and that value is echoed through the pipeline, never regenerated:

FieldMeaning
windowStartTimestampOldest input sample in the window
windowEndTimestampNewest input sample — the “as-of” time the prediction is valid for
timestampMirrors windowEndTimestamp (compatibility)
emittedAtBackend wall-clock at emission — emittedAt − windowEndTimestamp ≈ end-to-end latency

On the dashboard the prediction trace visibly trails the sensor trace by the real pipeline latency. That gap is an intended operational signal, not a rendering defect.

Slow consumers never throttle inference: dashboard broadcasts are fire-and-forget, and events to a client that can’t keep up are dropped and counted (/api/inference/backpressure). Notification-worthy events are deduplicated and rate-limited to keep the bell useful — see Notifications.