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

3.7 KiB

RMon Data Model

Signal

A signal is a named scalar time-series. Every signal has a unique SignalId and associated metadata.

/// 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

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

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

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.