examples/System Monitor/README.md
This example turns Serial Studio into a live system dashboard — like a minimal
htop — by using the Process I/O driver (Pro) to launch a Python script and
read its stdout directly as a telemetry stream.
Serial Studio spawns system-monitor.py (or the platform launcher), and the
script continuously writes metrics to stdout at ~30 Hz. Serial Studio parses
each line as a frame and drives gauges, multiplots, bar graphs, and data grids
in real time.
Note: The Process I/O driver requires a Serial Studio Pro license. Visit serial-studio.com for details.
| Metric | Widget | Notes |
|---|---|---|
| CPU Usage | Gauge + line plot | Overall utilisation (%) |
| CPU Temperature | Bar | °C; N/A if unavailable (e.g. macOS) |
| Per-Core CPU | Multiplot | One curve per logical core; cores beyond the machine's count are hidden via -1 sentinel |
| RAM Usage | Gauge + line plot | Memory pressure (%) |
| RAM Used | Bar | Absolute consumption (GB) |
| Swap Used | Bar | Page-file / swap used (GB) |
| Disk Usage | Gauge + line plot | Root partition (%) |
| Disk Used | Bar | Space consumed (GB) |
| Network Upload / Download | Multiplot | MB/s delta since last frame |
| Process Count | Bar | Total running processes |
| Top 10 Processes | Data grid | Sorted by CPU; format: Name (nn.n% CPU) |
| System Info | Data grid | OS, hostname, CPU model, cores, RAM, disk — static |
The script emits two types of frames to stdout:
Static system information that populates the System Info data grid:
$OS=Darwin 25.3.0|HOSTNAME=myhost|CPU_MODEL=Apple M2 Pro|CPU_CORES=12|CPU_THREADS=12|RAM_TOTAL=17.2 GB|DISK_TOTAL=494.4 GB
Real-time resource usage, per-core CPU, and top processes:
$CPU_USAGE=9.4|RAM_USAGE=62.1|RAM_USED=8.50|DISK_USAGE=18.3|DISK_USED=12.3|CPU_TEMP=N/A|SWAP_USED=1.7|NET_SENT=0.124|NET_RECV=0.871|PROCESSES=682|CORE0=17.2|CORE1=16.0|...|CORE31=-1|PROC0=WindowServer (12.5% CPU)|PROC1=python3 (8.1% CPU)|...
All frames use $ as frameStart and \n as frameEnd.
The project always declares 32 core slots (CORE0–CORE31). Slots beyond the
machine's actual logical core count are emitted as -1. The multiplot widget
treats -1 as out-of-range and hides those curves automatically.
pip install psutil py-cpuinfo
psutil — cross-platform CPU, memory, disk, network, and process counterspy-cpuinfo — reliable CPU brand string on all platforms (including Apple
Silicon, where platform.processor() returns only "arm")The platform launchers (system-monitor.sh / system-monitor.bat) auto-install
both packages via pip if they are missing.
system-monitor.ssproj.system-monitor.shsystem-monitor.batpython3 and set Arguments to the full path of
system-monitor.py.# macOS / Linux
./system-monitor.sh
# Windows
system-monitor.bat
# Direct Python (any platform)
python3 system-monitor.py --interval 0.033
Press Ctrl+C to stop.
Arguments:
| Argument | Default | Description |
|---|---|---|
--interval SECONDS | 1/30 ≈ 0.033 | Seconds between live frames |
The JavaScript parser inside system-monitor.ssproj uses a persistent value
array declared at module scope. Keys absent from a frame keep their last value,
so the static header fields (OS, hostname, CPU model…) remain visible while live
metric frames keep updating the rest.
// Module-scope globals — persist across parse() calls in QJSEngine
var _map = { "OS": 0, "CPU_USAGE": 7, "CORE0": 17, ... };
var _vals = new Array(59).fill('');
function parse(frame) {
var pairs = frame.split('|');
for (var i = 0; i < pairs.length; i++) {
var eq = pairs[i].indexOf('=');
var key = pairs[i].substring(0, eq).trim();
var val = pairs[i].substring(eq + 1).trim();
if (_map.hasOwnProperty(key))
_vals[_map[key]] = val;
}
return _vals;
}
Dataset index N in the .ssproj maps to _vals[N - 1] (Serial Studio uses
1-based dataset indices, the JS array is 0-based).
| Index | Key | Description |
|---|---|---|
| 1–7 | OS … DISK_TOTAL | Static system info |
| 8 | CPU_USAGE | Overall CPU % |
| 9 | RAM_USAGE | RAM % |
| 10 | RAM_USED | RAM used (GB) |
| 11 | DISK_USAGE | Disk % |
| 12 | DISK_USED | Disk used (GB) |
| 13 | CPU_TEMP | CPU temperature (°C or N/A) |
| 14 | SWAP_USED | Swap/page-file used (GB) |
| 15 | NET_SENT | Upload MB/s |
| 16 | NET_RECV | Download MB/s |
| 17 | PROCESSES | Total process count |
| 18–49 | CORE0–CORE31 | Per-core CPU % (-1 = core absent) |
| 50–59 | PROC0–PROC9 | Top processes by CPU |
psutil.cpu_percent(interval=1.0, percpu=True). This is necessary on macOS
where interval=None always returns 0.0 until at least one blocking
measurement has completed. The main loop reads the latest snapshot
non-blocking via a lock.psutil — pip install psutilpy-cpuinfo — pip install py-cpuinfoCopyright (C) 2020-2025 Alex Spataru SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-SerialStudio-Commercial