---
title: Schema Reference
description: Complete OTel schema reference for ROSQL — table definitions, field mappings, span attributes, and profiles
sidebar_position: 7
keywords: [rosql, schema, opentelemetry, otel, ros2, tables, postgresql, clickhouse, parquet, ddl, span attributes]
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Schema Reference

ROSQL expects telemetry data in the OpenTelemetry schema conventions for ROS2. This document defines the complete schema: table definitions, span attributes, field mappings, and schema profiles.

If you're building a ROS2-to-OTel bridge or setting up your own telemetry storage, implement these conventions to get full ROSQL feature support.

The **table names** are the same across all backends. What differs is the physical layout — column naming conventions for SQL databases, or directory structure for the Parquet backend.

---

## Span attributes

### ROS2 action executions

| Attribute | Type | Example | Used by ROSQL for |
|-----------|------|---------|-------------------|
| `ros.node` | string | `/bt_navigator` | `WHERE node = '...'` |
| `ros.action.name` | string | `/navigate_to_pose` | `WHERE action_name = '...'` |
| `ros.action.type` | string | `nav2_msgs/action/NavigateToPose` | Action type filtering |
| `ros.action.goal_id` | string | UUID | Goal correlation |
| `ros.action.status` | string | `succeeded` / `aborted` / `canceled` | Action status filtering |

### ROS2 pub/sub tracing

| Attribute | Type | Example | Used by ROSQL for |
|-----------|------|---------|-------------------|
| `ros.topic` | string | `/cmd_vel` | Topic filtering, `MESSAGE FLOW` |
| `ros.message_type` | string | `geometry_msgs/msg/Twist` | Message type filtering |
| `ros.publisher_node` | string | `/controller_server` | Publisher attribution |
| `ros.subscriber_node` | string | `/motor_driver` | Subscriber attribution |
| `ros.session.id` | string | `sess_abc123` | `FOR SESSION` scoping |

### Universal scope resource attributes

These resource attributes are set at the SDK/bridge level and enable `FOR` clause scoping across all query types.

| Attribute | Type | Example | Used by ROSQL for |
|-----------|------|---------|-------------------|
| `robot.id` | string | `robot_42` | `FOR ROBOT` scoping |
| `service.version` | string | `v1.2.3` | `FOR VERSION` scoping |
| `deployment.environment` | string | `production` | `FOR ENVIRONMENT` scoping |
| `ros.session.id` | string | `sess_abc123` | `FOR SESSION` scoping |

### ParentSpanId convention for `TRACE`

The publish span's `SpanId` must be set as the `ParentSpanId` of the corresponding subscribe span. This creates the causal chain that `TRACE` traverses recursively via a CTE.

```
Publisher span (SpanId: "abc")
  └── Subscriber span (ParentSpanId: "abc", SpanId: "def")
        └── Downstream span (ParentSpanId: "def", SpanId: "ghi")
```

Implementing this correctly requires middleware-level instrumentation — it cannot be done purely at the application level without code changes to every node.

---

## Metric naming conventions

| Metric name | Unit | Description |
|-------------|------|-------------|
| `ros2.topic.message_rate` | Hz | Topic publish rate |
| `ros2.topic.bandwidth` | B/s | Topic bandwidth |
| `ros2.topic.messages_received` | — | Messages received by subscribers |
| `ros2.topic.messages_captured` | — | Messages captured (e.g. by recorder) |
| `ros2.topic.messages_filtered` | — | Messages filtered/dropped |
| `ros2.action_servers.count` | — | Active action server count |
| `ros2.services.count` | — | Active service count |
| `ros2.action.queued_goals` | — | Queued action goals |
| `ros2.action.active_goals` | — | Active action goals |
| `ros2.action.completion_rate` | Hz | Action completion rate |
| `system.cpu.utilization` | % | Total CPU utilization |
| `system.memory.utilization` | % | Memory utilization |
| `system.memory.usage` | B | Memory usage in bytes |
| `system.filesystem.utilization` | % | Disk utilization |
| `system.filesystem.usage` | B | Disk usage in bytes |
| `system.disk.io` | B/s | Disk I/O throughput |
| `system.disk.operations` | ops/s | Disk IOPS |
| `system.network.io` | B/s | Network I/O throughput |
| `system.network.packets` | packets/s | Network packet rate |
| `system.network.latency` | ms | Network latency |
| `system.network.jitter` | ms | Network jitter |
| `system.network.packet_loss` | % | Packet loss rate |
| `system.temperature` | °C | System temperature |
| `system.battery.charge` | % | Battery state of charge |
| `system.battery.voltage` | V | Battery voltage |
| `system.battery.current` | A | Battery current draw |
| `system.battery.temperature` | °C | Battery temperature |
| `process.cpu.utilization` | % | Per-process CPU utilization |
| `process.memory.usage` | B | Per-process memory usage |

---

## Required tables

These three tables are required for basic ROSQL functionality.

### `otel_traces`

Used by: `FROM traces`, `TRACE`, `MESSAGE FLOW`, `DURING()`, `ACTION_SUCCESS_RATE`, `MOVING_AVG`, `DERIVATIVE`, `SHOW DEPLOYMENTS`, `SHOW SPAN SUMMARY`, `SHOW PLANS`

<Tabs>
  <TabItem value="postgres" label="PostgreSQL" default>

```sql
CREATE TABLE otel_traces (
    timestamp            TIMESTAMPTZ NOT NULL,
    trace_id             TEXT NOT NULL,
    span_id              TEXT NOT NULL,
    parent_span_id       TEXT NOT NULL DEFAULT '',
    span_name_col        TEXT NOT NULL,
    span_kind            TEXT NOT NULL DEFAULT 'INTERNAL',
    service_name         TEXT NOT NULL DEFAULT '',
    duration             BIGINT NOT NULL,          -- nanoseconds
    status_code          TEXT NOT NULL DEFAULT 'OK',
    span_attributes      JSONB NOT NULL DEFAULT '{}',
    resource_attributes  JSONB NOT NULL DEFAULT '{}'
);
```

Use `--schema otel-postgres` (default for PostgreSQL and MySQL).

  </TabItem>
  <TabItem value="clickhouse" label="ClickHouse">

```sql
CREATE TABLE otel_traces (
    Timestamp            DateTime64(9) NOT NULL,
    TraceId              String NOT NULL,
    SpanId               String NOT NULL,
    ParentSpanId         String NOT NULL DEFAULT '',
    SpanName             String NOT NULL,
    SpanKind             String NOT NULL DEFAULT 'INTERNAL',
    ServiceName          String NOT NULL DEFAULT '',
    Duration             Int64 NOT NULL,           -- nanoseconds
    StatusCode           String NOT NULL DEFAULT 'OK',
    SpanAttributes       Map(String, String) NOT NULL,
    ResourceAttributes   Map(String, String) NOT NULL
) ENGINE = MergeTree()
ORDER BY (Timestamp, TraceId, SpanId);
```

Use `--schema otel-clickhouse`.

  </TabItem>
  <TabItem value="parquet" label="Parquet">

Create a `traces/` subdirectory under your base URL. ROSQL creates a DuckDB view over `traces/**/*.parquet`.

```
<url>/
  traces/
    *.parquet    ← or any nested structure, e.g. date=2026-04-11/*.parquet
```

Required Parquet columns (snake_case, matching the `otel-postgres` profile):

| Column | Parquet type |
|--------|-------------|
| `timestamp` | TIMESTAMP (UTC) |
| `trace_id` | STRING |
| `span_id` | STRING |
| `parent_span_id` | STRING |
| `span_name_col` | STRING |
| `span_kind` | STRING |
| `service_name` | STRING |
| `duration` | INT64 (nanoseconds) |
| `status_code` | STRING |
| `span_attributes` | STRING (JSON) |
| `resource_attributes` | STRING (JSON) |

If the `traces/` subdirectory is absent or contains no `.parquet` files, ROSQL silently skips the view and reports `FROM traces` as unavailable.

  </TabItem>
</Tabs>

### `otel_logs`

Used by: `FROM logs`

<Tabs>
  <TabItem value="postgres" label="PostgreSQL" default>

```sql
CREATE TABLE otel_logs (
    timestamp            TIMESTAMPTZ NOT NULL,
    trace_id             TEXT NOT NULL DEFAULT '',
    span_id              TEXT NOT NULL DEFAULT '',
    severity_text        TEXT NOT NULL DEFAULT 'INFO',
    severity_number      INTEGER NOT NULL DEFAULT 9,
    service_name         TEXT NOT NULL DEFAULT '',
    body                 TEXT NOT NULL DEFAULT '',
    resource_attributes  JSONB NOT NULL DEFAULT '{}',
    log_attributes       JSONB NOT NULL DEFAULT '{}'
);
```

  </TabItem>
  <TabItem value="clickhouse" label="ClickHouse">

```sql
CREATE TABLE otel_logs (
    Timestamp            DateTime64(9) NOT NULL,
    TraceId              String NOT NULL DEFAULT '',
    SpanId               String NOT NULL DEFAULT '',
    SeverityText         String NOT NULL DEFAULT 'INFO',
    SeverityNumber       UInt8 NOT NULL DEFAULT 9,
    ServiceName          String NOT NULL DEFAULT '',
    Body                 String NOT NULL DEFAULT '',
    ResourceAttributes   Map(String, String) NOT NULL,
    LogAttributes        Map(String, String) NOT NULL
) ENGINE = MergeTree()
ORDER BY Timestamp;
```

  </TabItem>
  <TabItem value="parquet" label="Parquet">

Subdirectory: `logs/`. ROSQL creates a view over `logs/**/*.parquet`.

| Column | Parquet type |
|--------|-------------|
| `timestamp` | TIMESTAMP (UTC) |
| `trace_id` | STRING |
| `span_id` | STRING |
| `severity_text` | STRING |
| `severity_number` | INT32 |
| `service_name` | STRING |
| `body` | STRING |
| `resource_attributes` | STRING (JSON) |
| `log_attributes` | STRING (JSON) |

  </TabItem>
</Tabs>

### `otel_metrics`

Used by: `FROM metrics`, `TOPIC_RATE`

<Tabs>
  <TabItem value="postgres" label="PostgreSQL" default>

```sql
CREATE TABLE otel_metrics (
    timestamp            TIMESTAMPTZ NOT NULL,
    metric_name          TEXT NOT NULL,
    value                DOUBLE PRECISION NOT NULL,
    attributes           JSONB NOT NULL DEFAULT '{}',
    service_name         TEXT NOT NULL DEFAULT ''
);
```

  </TabItem>
  <TabItem value="clickhouse" label="ClickHouse">

```sql
CREATE TABLE otel_metrics (
    Timestamp            DateTime64(9) NOT NULL,
    MetricName           String NOT NULL,
    Value                Float64 NOT NULL,
    Attributes           Map(String, String) NOT NULL,
    ServiceName          String NOT NULL DEFAULT ''
) ENGINE = MergeTree()
ORDER BY (Timestamp, MetricName);
```

  </TabItem>
  <TabItem value="parquet" label="Parquet">

Subdirectory: `metrics/`. ROSQL creates a view over `metrics/**/*.parquet`.

| Column | Parquet type |
|--------|-------------|
| `timestamp` | TIMESTAMP (UTC) |
| `metric_name` | STRING |
| `value` | DOUBLE |
| `attributes` | STRING (JSON) |
| `service_name` | STRING |

  </TabItem>
</Tabs>

---

## Optional tables

These tables enable additional ROSQL features. If absent, ROSQL returns a clear `DataSourceUnavailable` error with guidance.

### `topic_messages`

Used by: `FROM topics`, `FROM odom` (and other topic aliases)

<Tabs>
  <TabItem value="postgres" label="PostgreSQL" default>

```sql
CREATE TABLE topic_messages (
    robot_id             TEXT NOT NULL,
    topic_name           TEXT NOT NULL,
    timestamp            TIMESTAMPTZ NOT NULL,
    fields               JSONB NOT NULL DEFAULT '{}',
    message_type         TEXT NOT NULL DEFAULT ''
);
```

  </TabItem>
  <TabItem value="clickhouse" label="ClickHouse">

```sql
CREATE TABLE topic_messages (
    robot_id             String NOT NULL,
    topic_name           String NOT NULL,
    Timestamp            DateTime64(9) NOT NULL,
    fields               String NOT NULL DEFAULT '{}',
    message_type         String NOT NULL DEFAULT ''
) ENGINE = MergeTree()
ORDER BY (Timestamp, topic_name, robot_id);
```

  </TabItem>
  <TabItem value="parquet" label="Parquet">

Subdirectory: `topic_messages/`. ROSQL creates a view over `topic_messages/**/*.parquet`.

| Column | Parquet type |
|--------|-------------|
| `robot_id` | STRING |
| `topic_name` | STRING |
| `timestamp` | TIMESTAMP (UTC) |
| `fields` | STRING (JSON) |
| `message_type` | STRING |

  </TabItem>
</Tabs>

### `mcap_metadata`

Used by: `FROM recordings`, `FROM recordings WHERE topic = '...'`

<Tabs>
  <TabItem value="postgres" label="PostgreSQL" default>

```sql
CREATE TABLE mcap_metadata (
    robot_id             TEXT NOT NULL,
    session_id           TEXT NOT NULL,
    start_time           TIMESTAMPTZ NOT NULL,
    end_time             TIMESTAMPTZ NOT NULL,
    s3_key               TEXT NOT NULL,
    topics               TEXT[] NOT NULL DEFAULT '{}',
    message_types        JSONB NOT NULL DEFAULT '{}'  -- topic → message_type map
);
```

  </TabItem>
  <TabItem value="clickhouse" label="ClickHouse">

```sql
CREATE TABLE mcap_metadata (
    robot_id             String NOT NULL,
    session_id           String NOT NULL,
    start_time           DateTime64(9) NOT NULL,
    end_time             DateTime64(9) NOT NULL,
    s3_key               String NOT NULL,
    topics               Array(String) NOT NULL DEFAULT [],
    message_types        String NOT NULL DEFAULT '{}'  -- topic → message_type JSON
) ENGINE = MergeTree()
ORDER BY (start_time, robot_id);
```

  </TabItem>
  <TabItem value="parquet" label="Parquet">

Subdirectory: `mcap_metadata/`. ROSQL creates a view over `mcap_metadata/**/*.parquet`.

| Column | Parquet type |
|--------|-------------|
| `robot_id` | STRING |
| `session_id` | STRING |
| `start_time` | TIMESTAMP (UTC) |
| `end_time` | TIMESTAMP (UTC) |
| `s3_key` | STRING |
| `topics` | LIST\<STRING\> |
| `message_types` | STRING (JSON) |

  </TabItem>
</Tabs>

### `robot_joint_map` _(v0.4.3+)_

Stores URDF-derived joint metadata per robot model. Required for `SHOW JOINTS`.

:::note
Not available in the Parquet backend — requires a persistent database table.
:::

<Tabs>
  <TabItem value="postgres" label="PostgreSQL" default>

```sql
CREATE TABLE robot_joint_map (
    robot_model          TEXT NOT NULL,
    valid_from           TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    valid_to             TIMESTAMPTZ,                          -- NULL = currently active
    version              TEXT NOT NULL DEFAULT '',
    robot_ids            TEXT[] NOT NULL DEFAULT '{}',         -- robots using this model
    joint_map            JSONB NOT NULL DEFAULT '[]'           -- array of joint descriptors
);
```

  </TabItem>
  <TabItem value="clickhouse" label="ClickHouse">

```sql
CREATE TABLE robot_joint_map (
    robot_model          String NOT NULL,
    valid_from           DateTime64(9) NOT NULL,
    valid_to             Nullable(DateTime64(9)),
    version              String NOT NULL DEFAULT '',
    robot_ids            Array(String) NOT NULL DEFAULT [],
    joint_map            String NOT NULL DEFAULT '[]'          -- JSON array of joint descriptors
) ENGINE = ReplacingMergeTree(valid_from)
ORDER BY (robot_model, valid_from);
```

  </TabItem>
</Tabs>

`joint_map` element schema:
```json
{
  "joint_name": "shoulder_pan_joint",
  "joint_index": 0,
  "joint_type": "revolute",
  "lower_limit": -3.14159,
  "upper_limit": 3.14159
}
```

Used by: `SHOW JOINTS`, `JOINT DEVIATION`

---

### Additional tables (optional)

These tables power the additional data sources. If absent, ROSQL returns a `DataSourceUnavailable` error.

:::note
These tables are supported in PostgreSQL and MySQL only. They are not part of the standard Parquet directory layout.
:::

| ROSQL source | Table | Description |
|---|---|---|
| `FROM tf` | `tf_states` | ROS2 TF transform data |
| `FROM heartbeats` | `robot_heartbeats` | Robot heartbeat / liveness data |
| `FROM system_logs` | `system_logs` | System-level log events (OS/kernel) |
| `FROM diagnostics` | `otel_metrics` | ROS2 diagnostic messages (maps to metrics) |
| `FROM events` | `ros2_events` | ROS2 lifecycle and state machine events |

---

## ROSQL field mappings

ROSQL field names are resolved to physical column names based on the active schema profile. Use `--schema otel-postgres` (default) or `--schema otel-clickhouse`.

The Parquet backend uses `otel-postgres` column names.

### From `otel_traces`

<Tabs>
  <TabItem value="otel-postgres" label="otel-postgres" default>

| ROSQL field | Column | Storage unit |
|-------------|--------|-------------|
| `trace_id` | `trace_id` | — |
| `span_id` | `span_id` | — |
| `parent_span_id` | `parent_span_id` | — |
| `span_name` | `span_name_col` | — |
| `service` | `service_name` | — |
| `duration` | `duration` | nanoseconds |
| `status` | `status_code` | OK / ERROR |
| `node` | `span_attributes->>'ros.node'` | — |
| `action_name` | `span_attributes->>'ros.action.name'` | — |
| `action_status` | `span_attributes->>'ros.action.status'` | — |
| `topic` | `span_attributes->>'ros.topic'` | — |
| `robot_id` | `resource_attributes->>'robot.id'` | — |
| `version` | `resource_attributes->>'service.version'` | — |
| `environment` | `resource_attributes->>'deployment.environment'` | — |
| `session_id` | `resource_attributes->>'ros.session.id'` | — |

  </TabItem>
  <TabItem value="otel-clickhouse" label="otel-clickhouse">

| ROSQL field | Column | Storage unit |
|-------------|--------|-------------|
| `trace_id` | `TraceId` | — |
| `span_id` | `SpanId` | — |
| `parent_span_id` | `ParentSpanId` | — |
| `span_name` | `SpanName` | — |
| `service` | `ServiceName` | — |
| `duration` | `Duration` | nanoseconds |
| `status` | `StatusCode` | OK / ERROR |
| `node` | `SpanAttributes['ros.node']` | — |
| `action_name` | `SpanAttributes['ros.action.name']` | — |
| `action_status` | `SpanAttributes['ros.action.status']` | — |
| `topic` | `SpanAttributes['ros.topic']` | — |
| `robot_id` | `ResourceAttributes['robot.id']` | — |
| `version` | `ResourceAttributes['service.version']` | — |
| `environment` | `ResourceAttributes['deployment.environment']` | — |
| `session_id` | `ResourceAttributes['ros.session.id']` | — |

  </TabItem>
</Tabs>

### From `otel_logs`

<Tabs>
  <TabItem value="otel-postgres" label="otel-postgres" default>

| ROSQL field | Column |
|-------------|--------|
| `message` | `body` |
| `severity` | `severity_text` |
| `severity_number` | `severity_number` |

  </TabItem>
  <TabItem value="otel-clickhouse" label="otel-clickhouse">

| ROSQL field | Column |
|-------------|--------|
| `message` | `Body` |
| `severity` | `SeverityText` |
| `severity_number` | `SeverityNumber` |

  </TabItem>
</Tabs>

### From `otel_metrics`

<Tabs>
  <TabItem value="otel-postgres" label="otel-postgres" default>

| ROSQL field | Resolved as | Storage unit |
|-------------|------------|-------------|
| `metric_name` | `metric_name` | — |
| `metric_value` | `value` | varies |
| `publish_rate` | `value` WHERE `metric_name = 'ros2.topic.message_rate'` | Hz |
| `bandwidth` | `value` WHERE `metric_name = 'ros2.topic.bandwidth'` | B/s |
| `messages_received` | `value` WHERE `metric_name = 'ros2.topic.messages_received'` | — |
| `messages_captured` | `value` WHERE `metric_name = 'ros2.topic.messages_captured'` | — |
| `messages_filtered` | `value` WHERE `metric_name = 'ros2.topic.messages_filtered'` | — |
| `action_servers_count` | `value` WHERE `metric_name = 'ros2.action_servers.count'` | — |
| `services_count` | `value` WHERE `metric_name = 'ros2.services.count'` | — |
| `queued_goals` | `value` WHERE `metric_name = 'ros2.action.queued_goals'` | — |
| `active_goals` | `value` WHERE `metric_name = 'ros2.action.active_goals'` | — |
| `completion_rate` | `value` WHERE `metric_name = 'ros2.action.completion_rate'` | Hz |
| `cpu_usage` | `value` WHERE `metric_name = 'system.cpu.utilization'` | % |
| `memory_usage` | `value` WHERE `metric_name = 'system.memory.utilization'` | % |
| `memory_bytes` | `value` WHERE `metric_name = 'system.memory.usage'` | B |
| `disk_usage` | `value` WHERE `metric_name = 'system.filesystem.utilization'` | % |
| `disk_bytes` | `value` WHERE `metric_name = 'system.filesystem.usage'` | B |
| `disk_io` | `value` WHERE `metric_name = 'system.disk.io'` | B/s |
| `disk_iops` | `value` WHERE `metric_name = 'system.disk.operations'` | ops/s |
| `network_io` | `value` WHERE `metric_name = 'system.network.io'` | B/s |
| `network_packets` | `value` WHERE `metric_name = 'system.network.packets'` | packets/s |
| `network_latency` | `value` WHERE `metric_name = 'system.network.latency'` | ms |
| `network_jitter` | `value` WHERE `metric_name = 'system.network.jitter'` | ms |
| `packet_loss` | `value` WHERE `metric_name = 'system.network.packet_loss'` | % |
| `temperature` | `value` WHERE `metric_name = 'system.temperature'` | °C |
| `battery_charge` | `value` WHERE `metric_name = 'system.battery.charge'` | % |
| `battery_voltage` | `value` WHERE `metric_name = 'system.battery.voltage'` | V |
| `battery_current` | `value` WHERE `metric_name = 'system.battery.current'` | A |
| `battery_temperature` | `value` WHERE `metric_name = 'system.battery.temperature'` | °C |
| `process_cpu` | `value` WHERE `metric_name = 'process.cpu.utilization'` | % |
| `process_memory` | `value` WHERE `metric_name = 'process.memory.usage'` | B |

  </TabItem>
  <TabItem value="otel-clickhouse" label="otel-clickhouse">

| ROSQL field | Resolved as | Storage unit |
|-------------|------------|-------------|
| `metric_name` | `MetricName` | — |
| `metric_value` | `Value` | varies |
| `publish_rate` | `Value` WHERE `MetricName = 'ros2.topic.message_rate'` | Hz |
| `bandwidth` | `Value` WHERE `MetricName = 'ros2.topic.bandwidth'` | B/s |
| `messages_received` | `Value` WHERE `MetricName = 'ros2.topic.messages_received'` | — |
| `messages_captured` | `Value` WHERE `MetricName = 'ros2.topic.messages_captured'` | — |
| `messages_filtered` | `Value` WHERE `MetricName = 'ros2.topic.messages_filtered'` | — |
| `action_servers_count` | `Value` WHERE `MetricName = 'ros2.action_servers.count'` | — |
| `services_count` | `Value` WHERE `MetricName = 'ros2.services.count'` | — |
| `queued_goals` | `Value` WHERE `MetricName = 'ros2.action.queued_goals'` | — |
| `active_goals` | `Value` WHERE `MetricName = 'ros2.action.active_goals'` | — |
| `completion_rate` | `Value` WHERE `MetricName = 'ros2.action.completion_rate'` | Hz |
| `cpu_usage` | `Value` WHERE `MetricName = 'system.cpu.utilization'` | % |
| `memory_usage` | `Value` WHERE `MetricName = 'system.memory.utilization'` | % |
| `memory_bytes` | `Value` WHERE `MetricName = 'system.memory.usage'` | B |
| `disk_usage` | `Value` WHERE `MetricName = 'system.filesystem.utilization'` | % |
| `disk_bytes` | `Value` WHERE `MetricName = 'system.filesystem.usage'` | B |
| `disk_io` | `Value` WHERE `MetricName = 'system.disk.io'` | B/s |
| `disk_iops` | `Value` WHERE `MetricName = 'system.disk.operations'` | ops/s |
| `network_io` | `Value` WHERE `MetricName = 'system.network.io'` | B/s |
| `network_packets` | `Value` WHERE `MetricName = 'system.network.packets'` | packets/s |
| `network_latency` | `Value` WHERE `MetricName = 'system.network.latency'` | ms |
| `network_jitter` | `Value` WHERE `MetricName = 'system.network.jitter'` | ms |
| `packet_loss` | `Value` WHERE `MetricName = 'system.network.packet_loss'` | % |
| `temperature` | `Value` WHERE `MetricName = 'system.temperature'` | °C |
| `battery_charge` | `Value` WHERE `MetricName = 'system.battery.charge'` | % |
| `battery_voltage` | `Value` WHERE `MetricName = 'system.battery.voltage'` | V |
| `battery_current` | `Value` WHERE `MetricName = 'system.battery.current'` | A |
| `battery_temperature` | `Value` WHERE `MetricName = 'system.battery.temperature'` | °C |
| `process_cpu` | `Value` WHERE `MetricName = 'process.cpu.utilization'` | % |
| `process_memory` | `Value` WHERE `MetricName = 'process.memory.usage'` | B |

  </TabItem>
</Tabs>

### From `topic_messages` (odom / position data)

ROSQL geospatial operators (`WITHIN`) and `JOINT DEVIATION` extract nested values from the `fields` JSONB column using these conventions:

| ROSQL path | `fields` JSON path | Description |
|---|---|---|
| `position.latitude` | `fields->>'position.latitude'` | GPS latitude (degrees) |
| `position.longitude` | `fields->>'position.longitude'` | GPS longitude (degrees) |
| `pose.pose.position.x` | `fields->>'pose.pose.position.x'` | Local frame X (metres) |
| `pose.pose.position.y` | `fields->>'pose.pose.position.y'` | Local frame Y (metres) |
| `position[N]` | `fields->'position'->>N` | Joint position array element N (radians) |

These match the standard `nav_msgs/Odometry` and `sensor_msgs/JointState` ROS2 message layouts.

---

## Topic aliases

| ROSQL source | Resolves to |
|-------------|------------|
| `FROM odom` | `FROM topics WHERE topic_name = '/odom'` |
| `FROM joint_states` | `FROM topics WHERE topic_name = '/joint_states'` |
| `FROM battery` | `FROM topics WHERE topic_name = '/battery_state'` |
| `FROM cmd_vel` | `FROM topics WHERE topic_name = '/cmd_vel'` |
| `FROM imu` | `FROM topics WHERE topic_name = '/imu/data'` |
