7.3 KiB
RMon Architecture
Overview
RMon is split into two executables that communicate over a TCP connection tunneled through SSH:
Operator Machine Remote Network
┌─────────────────────────────┐ ┌──────────────────────────────────────┐
│ rmon-ui │ │ rmon-agent │
│ ┌──────────┐ ┌───────────┐ │ SSH │ ┌──────────┐ ┌──────────────────┐ │
│ │ Plot │ │ Signal │ │ Tunnel │ │ Protocol │ │ Source Adapters │ │
│ │ Panels │ │ Tree │◄├──────────┤► │ Server │◄─│ CSV / UDP / EPICS│ │
│ └──────────┘ └───────────┘ │ │ └──────────┘ └──────────────────┘ │
│ ┌──────────────────────┐ │ │ ┌──────────────────┐ │
│ │ SSH Connection Mgr │ │ │ │ Storage Engine │ │
│ └──────────────────────┘ │ │ │ Continuous/Circ. │ │
└─────────────────────────────┘ │ └──────────────────┘ │
└──────────────────────────────────────┘
Language and Build Targets
Single language: Rust throughout agent, UI, and shared library.
| Crate | Purpose | Build Target |
|---|---|---|
rmon-common |
Shared types and protocol messages | (library, no binary) |
rmon-agent |
Remote data acquisition server | x86_64-unknown-linux-musl (fully static, zero runtime deps) |
rmon-ui |
Operator desktop application | native platform (dynamically links X11 + libGL) |
Why musl for the agent
The agent must run on remote machines with arbitrary (potentially old) Linux environments without root access. Compiling with x86_64-unknown-linux-musl produces a single binary with:
- No glibc dependency
- No dynamic linker requirement
- Works on any Linux kernel ≥ 3.2
- Can be copied with
scpand run immediately
The UI runs on the operator's own modern desktop, so strict portability is not required there.
Why egui for the UI
- Immediate-mode rendering — natural fit for real-time data that changes every frame
glowbackend uses OpenGL 2.1+, available on all modern desktop Linux setups- Cross-platform (Linux, Windows, macOS) from a single Rust codebase
- Ships as a single binary (dynamically links only X11/Wayland + libGL which are always present on any desktop Linux)
egui_plotprovides interactive time-series plots with zoom/pan; customeguipainter used for high-throughput rendering paths
Cargo Workspace Layout
rmon/
├── Cargo.toml # workspace root
├── rmon-common/ # shared types and protocol (lib crate)
│ └── src/
│ ├── signal.rs # SignalId, SignalInfo, Sample, StorageMode
│ └── protocol.rs # ClientMessage, AgentMessage enums
├── rmon-agent/ # remote acquisition agent
│ ├── .cargo/config.toml # sets default target to x86_64-unknown-linux-musl
│ └── src/
│ ├── main.rs
│ ├── config.rs # TOML config file parsing
│ ├── sources/ # one module per data source type
│ │ ├── trait.rs # DataSource trait
│ │ ├── csv.rs
│ │ ├── udp.rs
│ │ └── epics.rs
│ ├── storage/
│ │ ├── continuous.rs
│ │ └── circular.rs
│ └── server/
│ ├── listener.rs # TCP accept loop
│ └── session.rs # per-client session handler
└── rmon-ui/ # operator desktop UI
└── src/
├── main.rs
├── app.rs # top-level App struct, egui App impl
├── connection/
│ ├── ssh.rs # SSH tunnel lifecycle
│ └── client.rs # async protocol client (wraps TCP stream)
├── state/
│ └── session.rs # connected session state, signal cache
└── ui/
├── connect_dialog.rs
├── signal_tree.rs
├── plot_panel.rs # one per open plot window
└── toolbar.rs
Remote Access: SSH-Based Deployment
RMon follows the same pattern as VSCode Remote: the UI manages the agent lifecycle transparently, leveraging the user's existing ~/.ssh/config (including ProxyJump for multi-hop networks).
Connection flow
- User enters an SSH host alias (from their
~/.ssh/config) and optional agent port - UI runs:
ssh {host} 'uname -m'to detect remote architecture - UI checks if agent binary is current:
ssh {host} 'cat ~/.rmon/agent.sha256' - If stale/missing: UI pipes the correct pre-built binary via SSH:
cat rmon-agent-{arch} | ssh {host} 'mkdir -p ~/.rmon && cat > ~/.rmon/agent && chmod +x ~/.rmon/agent' - UI starts the agent (if not already running):
ssh {host} 'pgrep -f "rmon-agent --port {port}" || nohup ~/.rmon/agent --port {port} >> ~/.rmon/agent.log 2>&1 &' - UI establishes SSH local port forward:
ssh -N -L {local_port}:localhost:{remote_port} {host} - UI opens TCP connection to
localhost:{local_port}and sendsHandshake
No special software needs to be installed on the remote machine. All multi-hop SSH configuration is handled transparently by the system ssh binary reading the user's ~/.ssh/config.
Data Flow (steady state)
Source (e.g. UDP)
│ raw packets + source timestamp
▼
DataSource adapter
│ normalizes timestamp → i64 nanoseconds since Unix epoch
│ applies scale/offset
▼
Dispatcher (tokio broadcast channel per signal)
│
├──► Storage engine (writes to ring buffer or append log)
│
└──► Session handlers (one per connected UI client)
│ batches samples, sends DataBatch over TCP
▼
rmon-ui client
│ updates per-signal ring buffer (last N seconds for display)
▼
Plot panels (egui repaint)
Protocol Summary
Binary framing: [u32 little-endian length][bincode-serialized payload]
Messages are typed Rust enums defined in rmon-common::protocol. See docs/protocol.md for the full message reference.
Agent Configuration
The agent is configured via a TOML file (default: ~/.rmon/config.toml). It declares:
- Which data sources to connect to (type, address, signal definitions)
- Default storage policy
See docs/agent-spec.md for the full configuration reference.