Skip to content

Latest commit

 

History

62 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Parallel System Monitor

Tests

A desktop dashboard that polls multiple hosts for CPU/memory stats in parallel instead of one at a time, and proves it with real measured timing rather than a claim.

Dashboard - dark mode

Problem

Monitoring N hosts by contacting them one after another means total wait time grows linearly with N — if each host takes ~0.5s to respond, 6 hosts takes ~3s, 20 hosts takes ~10s, and so on. That's wasted time: the app is idle, waiting on network I/O, while it could be waiting on all hosts at once.

Parallel System Monitor polls every host concurrently using a thread pool, so total wait time is bounded by the slowest host, not the sum of all hosts.

Proof

This isn't a claim — it's measured. tests/core/test_dispatcher.py spins up 3 real mock agent processes, times polling them one-by-one, then times polling them with poll_all(), and asserts the parallel run is meaningfully faster:

Sequential time (one host at a time): 1.512s
Parallel time (poll_all):              0.509s

That's a real ~3x speedup on this machine, from an actual test run, not a synthetic benchmark. The app's own Parallel Connection Timeline panel (visible in the screenshot above) shows the same thing live: every host's poll bar starts at the same offset, because they were all dispatched together.

Features

  • Parallel polling — all hosts are polled concurrently via a ThreadPoolExecutor, proven faster with real timing (see above)
  • Light/dark mode — every custom-drawn widget (timeline, history graph, gauge dials) is fully theme-aware, not just the built-in CustomTkinter widgets
  • Per-host detail — click a host's chevron to expand real started_at/finished_at timestamps for that poll, without rebuilding the row
  • Settings with live text scaling — a settings dialog with an Appearance toggle and a Display text-size slider (80%-200%) that previews instantly
  • Search & sort — filter and reorder hosts entirely in-memory; neither ever triggers a re-poll
  • Auto-refresh — optional periodic polling on a Tkinter .after() timer (not a background thread), cleanly cancelled on window close
  • Health score — a single load-weighted score (100 - max(cpu, memory) per host, averaged, offline hosts count as 0) with a 24-hour trend graph
  • Event history — status-change events (e.g. healthy -> critical) are recorded to SQLite only when a host's status actually changes, not on every poll

Screenshots

View Light Dark
Dashboard Dashboard - light mode Dashboard - dark mode
Host Offline Host offline - light mode Host offline - dark mode
Search Filtering Search filtering - light mode Search filtering - dark mode
Host Detail Host detail - light mode Host detail - dark mode
Settings Settings - light mode Settings - dark mode

Architecture

Data flows in one direction: a list of hosts goes in, every host gets polled at the same time, and each response is checked, labeled with a health status, then both saved to disk and shown on screen. No step reaches backward — the polling code doesn't know the database or the dashboard exist.

flowchart LR
    A["Host list<br/>(HostConfig)"] --> B["Dispatcher<br/>polls all hosts in parallel"]
    B --> C["Agent Client<br/>HTTP GET /stats"]
    C --> D["Agent<br/>(Flask + psutil, one per host)"]
    D --> E["Parser<br/>validates values,<br/>classifies status"]
    E --> F["PollResult<br/>(one per host)"]
    F --> G["Storage<br/>SQLite history + events"]
    F --> H["GUI<br/>dashboard, timeline,<br/>health score"]
Loading

This one-way layering is deliberate: the core (dispatcher, parser, models) has zero dependency on the GUI or the database, so every layer is tested on its own — and it's what makes the "swap one layer to scale" story in the Scaling note below possible without touching anything else. The code lives in src/psm/ (core/, storage/, gui/) plus the simulated host in mock-agents/.

Status thresholds (parser.py), based on the higher of CPU or memory usage:

Status Range
Healthy < 50%
Warning 50% – 75%
Critical > 75%
Offline host unreachable or timed out

Scaling note

poll_all() (dispatcher.py) uses a ThreadPoolExecutor with max_workers=10 by default. Since each poll is a short HTTP request, the work is I/O-bound, so threads (not processes) are sufficient — the GIL is released while waiting on the socket. This means:

  • With ≤10 hosts, every host is dispatched at once, so wall-clock time ≈ the slowest single host's response time.
  • Beyond 10 hosts, requests queue for a free worker thread, so wall-clock time grows in batches of 10 rather than linearly per host — still far better than fully sequential polling.

Setup

Requires Python 3.10+.

git clone https://github.com/Aayush29052006/Parallel-System-Monitor.git
cd Parallel-System-Monitor

Linux/macOS

python3 -m venv .venv
source .venv/bin/activate
pip install -e .

Start the 6 mock agents the GUI expects (srv-cache-01 ... srv-storage-01 on ports 9001-9006):

python mock-agents/agent_server.py --port 9001 &
python mock-agents/agent_server.py --port 9002 &
python mock-agents/agent_server.py --port 9003 &
python mock-agents/agent_server.py --port 9004 &
python mock-agents/agent_server.py --port 9005 &
python mock-agents/agent_server.py --port 9006 &

Windows

python -m venv .venv
.venv\Scripts\activate
pip install -e .

PowerShell's trailing & doesn't background a process the way bash's does, so start each agent with Start-Process instead (or open 6 separate terminal windows and run one python mock-agents/agent_server.py --port ... in each):

Start-Process python -ArgumentList "mock-agents/agent_server.py --port 9001"
Start-Process python -ArgumentList "mock-agents/agent_server.py --port 9002"
Start-Process python -ArgumentList "mock-agents/agent_server.py --port 9003"
Start-Process python -ArgumentList "mock-agents/agent_server.py --port 9004"
Start-Process python -ArgumentList "mock-agents/agent_server.py --port 9005"
Start-Process python -ArgumentList "mock-agents/agent_server.py --port 9006"

Each Start-Process opens its own window; close those windows (or use Stop-Process) to stop the agents.

No other code paths differ by OS — agent_server.py and the GUI use no OS-specific paths or signal handling. The one real platform difference we've hit is networking, not setup: connecting to a closed port on Windows' loopback interface times out instead of refusing immediately the way Linux/macOS do, which agent_client.py already accounts for (verified in CI on windows-latest).

Run the app (all platforms)

python -m psm.gui.app

For screenshots/demos, an agent can be forced to report a fixed load value instead of real psutil data:

python mock-agents/agent_server.py --port 9001 --fake-load 85

Running tests

pip install pytest
pytest

All 47 tests pass, and run on Ubuntu, Windows, and macOS via GitHub Actions on every push and pull request to main.

Tech stack


Built by Aayush Chaudhari as a portfolio project demonstrating parallel I/O, real-time UI updates, and measured (not assumed) performance claims.

About

Desktop app that polls multiple hosts in parallel for CPU/memory stats, with live dashboard and history

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages