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

102 lines
3.7 KiB
Markdown

# RMon Data Model
## Signal
A **signal** is a named scalar time-series. Every signal has a unique `SignalId` and associated metadata.
```rust
/// Globally unique signal identifier.
/// Convention: "{source_type}://{source_name}/{path/to/signal}"
/// Examples:
/// "udp://imu_stream/accel_x"
/// "csv:///mnt/data/temperatures.csv/sensor_3"
/// "epics://PV:SYS:TEMP:READBACK"
type SignalId = String;
struct SignalInfo {
id: SignalId,
label: String, // human-readable display name
unit: String, // e.g. "m/s²", "°C", ""
scale: f64, // raw_value * scale + offset = engineering_value
offset: f64,
sampling_period: Option<Duration>,// None if irregular/event-driven
source_type: SourceType,
path: Vec<String>, // tree path for UI grouping, e.g. ["imu", "accel"]
}
enum SourceType { Csv, Udp, Epics, Custom(String) }
```
Signals are **read-only** from the UI's perspective. The agent owns all signal definitions (from its config file).
## Sample
```rust
struct Sample {
/// Nanoseconds since Unix epoch (UTC).
/// All source timestamps are normalized to this by the adapter.
timestamp: i64,
value: f64,
}
```
All timestamps are normalized at ingestion time inside the adapter. The adapter knows the source-specific format (string, u64 seconds, etc.) and converts it. The rest of the system only sees `i64` nanoseconds.
## Timestamp Normalization Examples
| Source format | Example | Agent conversion |
|--------------|---------|-----------------|
| `%Y.%m.%d %H:%M:%S` string | `"2024.01.15 14:32:07"` | parse → UTC `i64` ns |
| u64 microseconds since epoch | `1705329127000000` | multiply by 1000 |
| u64 seconds since epoch | `1705329127` | multiply by 1_000_000_000 |
| Relative milliseconds | `12345` | add agent start epoch |
Adapters declare their `TimestampFormat` in config; the normalization logic lives in a shared utility in `rmon-agent::sources`.
## Storage Modes
The agent can store signal data in two modes per signal group:
### Continuous Storage
```rust
StorageMode::Continuous
```
- Data is appended to an on-disk log starting when recording is requested
- Data is **kept until explicitly deleted** (either via UI command or manual deletion)
- File format: binary append log of `(i64 timestamp, f64 value)` pairs, one file per signal
- File path: `{storage_dir}/{signal_id_escaped}/{start_timestamp}.bin`
### Circular (Loop) Storage
```rust
StorageMode::Circular { window: Duration }
```
- Agent maintains an in-memory ring buffer of the last `window` duration
- On UI request, can also persist to disk (same format as continuous)
- `window` is configured per signal group in the agent config
### Storage Location
Configured per agent in `config.toml`. Defaults to `~/.rmon/data/`. The agent never deletes data automatically (except from in-memory circular buffers).
## Signal Tree
For UI navigation, signals are organized into a tree using the `path` field of `SignalInfo`. Each element of the `Vec<String>` is a tree level.
Example paths:
```
["system", "imu", "accelerometer"] → system > imu > accelerometer > {signals}
["temperatures", "coolant"] → temperatures > coolant > {signals}
```
The UI renders this as a collapsible tree panel. Signals can be dragged from the tree onto plot panels.
## UI-Side Signal Cache
The UI maintains a per-signal ring buffer for display (configurable, default: last 60 seconds). Incoming `DataBatch` messages are appended to this buffer. Historical query responses fill in older portions.
The display buffer is separate from the agent's storage — it only holds what the UI needs to render the current view.