DEV Community

Cover image for Building a Real-Time System Monitor with Zig, Bun, and WebSockets
Adavize
Adavize

Posted on

Building a Real-Time System Monitor with Zig, Bun, and WebSockets

Intro

Most monitoring tools are either bloated or cloud-dependent. I wanted something minimal, local, and fast so I built one using Zig for system-level collection and Bun for a real-time WebSocket API. Here is how I approached it.

System architecture

system architecture for local monitoring tool

The core idea is simple: collect system metrics at regular intervals, store them in a database for later access, and broadcast updates to a web interface in real time.

Metric collectors

The first step is a set of metric collectors that reads system metrics ands send them to the API every second.

I chose the Zig programming language for this. Why Zig?

  • Zig is a modern alternative to C, offering better safety and performance while giving explicit control over memory. This makes it ideal for building lightweight tools with minimal overhead.

  • Zig provides native access to system resources which enables direct interaction with hardware while offering stronger memory checks than C.

To keep this article concise, I'll explain each metric collector in plain English rather than diving deeply into implementation details.

CPU metric

CPU usage is captured from /proc/stat. The /proc directory in Linux is a virtual filesystem managed by the kernel that exposes real-time system and process information. Other system metrics are also available here.

Flow for CPU metric collector:

[ Get first reading from /proc/stat ]
      |
[ After first reading cycle ]
      |
[ Get second reading from /proc/stat ]
      |
[ Calculate delta total and delta active difference ]
      |
[ Compute the CPU usage in percentage ]
      |
[ Send to API Service ]
Enter fullscreen mode Exit fullscreen mode

CPU usage is calculated by comparing two readings of CPU time and computing the percentage of time spent doing active work versus idle time.

Source code for CPU metric collector here


Memory metric

Memory metrics are read from /proc/meminfo.

Flow for memory metric collection:

[ Read total memory from /proc/meminfo ]
      |
[ Read available memory from /proc/meminfo ]
      |
[ Compute the percentage being used ]
      |
[ Send to API Service ]
Enter fullscreen mode Exit fullscreen mode

Source code for memory metric collector here


Temperature metric

Temperature metrics are read from the /sys virtual filesystem, which exposes hardware attributes and allows inspection or control of devices.

While /proc provides system and process stats, /sys exposes hardware-level information such as temperature sensors and power supply data.

Flow diagram of temperature metric collector:

[ Read the content of /sys/class/thermal/thermal_zone0/temp ]
      |
[ Convert from millidegrees Celsius to degree Celsius ]
      |
[ Send to API Service ]
Enter fullscreen mode Exit fullscreen mode

Source code for temperature metric collector here


Battery metric

Battery metrics are also read from /sys. The collector reads the battery capacity from /sys/class/power_supply/BAT0/capacity (as a percentage) and sends it to the API.

Flow diagram of battery metric collector:

[ Read the content of /sys/class/power_supply/BAT0/capacity ]
      |
[ Send to API Service ]
Enter fullscreen mode Exit fullscreen mode

Source code for battery metric collector here


API service

Once metrics are collected, the next step is to store and make them available to clients in real time. This is handled by a lightweight API service.

The API service is built with TypeScript and Bun. Bun is a fast Javascript runtime designed for modern applications, with built-in HTTP, WebSocket, and SQLite support.

Why Bun?

  • Fast and easy to setup
  • Built-in WebSockets support
  • Built-in SQLite
  • No need for extra tooling

The Web API service is responsible for:

  1. Persisting collected metrics in SQLite
  2. Broadcasting metrics to connected clients via WebSockets
Endpoints:
Method Route Description
POST /api/metrics/cpu Ingest CPU usage
POST /api/metrics/memory Ingest memory usage
POST /api/metrics/temperature Ingest temperature
POST /api/metrics/battery Ingest battery level
GET /api/history?metric=<name> Last 200 data points for a metric
WS /ws Real-time broadcast of all incoming metrics

Database

SQLite is used for its lightweight nature and simplicity, making it ideal for a local monitoring tool.

Metric table

Column Type Constraints Description
id INTEGER PRIMARY KEY, AUTOINCREMENT Unique row identifier
metric TEXT NOT NULL Metric type (cpu, memory, temperature, etc.)
value REAL NOT NULL Recorded metric value
ts INTEGER NOT NULL Unix timestamp of when the metric was recorded

Writing to the database every second could cause performance issues, so SQLite is configured for Write-Ahead Logging (WAL) and relaxed synchronous mode:

  • PRAGMA journal_mode = WAL;
    This configures SQLite to write to a separate WAL file before merging into the main database thereby improving concurrency and write performance, at the cost of slightly more disk usage.

  • PRAGMA synchronous = NORMAL;
    This configuration reduces the number of disk sync operations for faster writes. In rare cases (e.g., sudden power loss), some recent writes may be lost—acceptable for this use case.


Web dashboard

Finally, to visualize the metrics in real time, I built a simple web interface that connects to the WebSocket endpoint. Whenever the API receives a new metric, it broadcasts it to all connected clients. The frontend listens for these events and updates the UI accordingly.

This approach avoids polling and ensures that the dashboard reflects system changes instantly.

I used CanvasJS for visualization, providing charts that show trends over time at a glance.


Conclusion

Building this monitoring tool helped me gain hands-on experience with Linux virtual filesystems (/proc and /sys) and real-time WebSocket communication.

While this is minimal, there are several possible improvements:

  • Adding alerting for threshold breaches
  • Enhancing the visualization layer
  • Aggregating metrics over time for example:
    • average CPU usage over 1 minute
    • maximum temperature over 5 minutes

Overall, this project was an excellent hands-on learning experience, deepening my understanding of low-level system operations and real-time data streaming.

Thank you for taking the time to read. Looking forward to whatever questions or suggestions you may have.

Here is the link to the entire source code. Happy digging!

Top comments (0)