Files
rmon/docs/architecture.md
T
Martino Ferrari dca378b5ef initial commit
2026-04-11 16:30:18 +02:00

149 lines
7.3 KiB
Markdown

# 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 `scp` and 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
- `glow` backend 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_plot` provides interactive time-series plots with zoom/pan; custom `egui` painter 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
1. User enters an SSH host alias (from their `~/.ssh/config`) and optional agent port
2. UI runs: `ssh {host} 'uname -m'` to detect remote architecture
3. UI checks if agent binary is current: `ssh {host} 'cat ~/.rmon/agent.sha256'`
4. 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'
```
5. 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 &'
```
6. UI establishes SSH local port forward: `ssh -N -L {local_port}:localhost:{remote_port} {host}`
7. UI opens TCP connection to `localhost:{local_port}` and sends `Handshake`
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.