Devlog: Real-Time Server Metrics with Glances and SignalR
In this update, I added live server monitoring to xbServerHub. The goal was to collect system metrics (CPU, memory, network) from a Glances instance running in Docker, and stream them to the frontend in real time using SignalR — all while keeping the backend organized around Clean Architecture (Domain, Application, Persistence, API).
What Was Done
1. Metrics Collection via Glances
Glances exposes a REST API with system stats. I added a GlancesClient in the Persistence layer that polls the /cpu, /mem, and /network endpoints in parallel and maps the raw JSON responses into clean domain models (ServerMetrics, CpuStats, MemoryStats, NetworkInterfaceStats).
A BackgroundService using PeriodicTimer polls Glances every 2 seconds and passes the result to a handler (CollectSystemMetricsHandler) in the Application layer, which doesn’t know or care that the data originally came from an HTTP call to Glances — it just receives a ServerMetrics object.
2. Broadcasting Metrics with SignalR
To get this data to the frontend without constant polling from the client, I introduced an IMetricsBroadcaster interface in Application. This follows the same pattern as IGlancesClient: Application defines what it needs (a way to send metrics somewhere), without knowing how that’s actually done.
The implementation, SignalRMetricsBroadcaster, lives in the API layer and uses a MetricsHub to push data to all connected clients via Clients.All.SendAsync. The handler now calls both the Glances client and the broadcaster on every tick, so every 2 seconds connected clients get a fresh snapshot pushed to them automatically.
3. Securing the Hub and Fixing Auth Routes
Since metrics shouldn’t be visible to anyone who isn’t logged in, I added [Authorize] to MetricsHub. Because the app uses cookie-based authentication (SignInManager), this works without needing any token handling on the SignalR connection — the existing session cookie is enough, as long as the connection is treated as same-origin.
This is also where I cleaned up and reorganized the authentication API routes to make sure they’re consistent with how the frontend (and now SignalR) expects to reach them, and wired up the Hub endpoint (MapHub) alongside the rest of the API configuration.
Current State
The backend now collects CPU, memory, and network stats every 2 seconds and pushes them live to any authenticated, connected client. The frontend isn’t consuming this yet — that’s the next step, along with finalizing the dev proxy setup so cookies and WebSocket connections behave the same in development as they will once frontend and backend are deployed together in a single container.
Comments 0
No comments yet. Be the first!
Sign in to join the conversation.