102 lines
3.7 KiB
Markdown
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.
|