Skip to main content
Version: 0.5

Quickstart

Get ROSQL running against your ROS2 telemetry data in under 5 minutes.

Install the CLI

curl -fsSL https://rosql.org/install.sh | sh

Linux x86_64, Linux arm64, macOS Intel, and macOS Apple Silicon. Installs to ~/.local/bin/ with the Parquet backend included.

Your first query — no setup required

Query our public demo dataset instantly. No data source, no credentials, no configuration:

rosql query "FROM traces WHERE status = 'ERROR' LIMIT 5" \
--backend parquet \
--url s3://robotops-production-rosql-demo/data
╭──────────────────┬────────────────┬──────────────────┬────────────────────────────────┬──────────────┬────────────┬─────────────╮
│ timestamp │ trace_id │ span_id │ span_name │ service_name │ duration │ status_code │
├──────────────────┼────────────────┼──────────────────┼────────────────────────────────┼──────────────┼────────────┼─────────────┤
│ 1776452455875402 │ trace-amr01-m1 │ span-a01-m1-root │ /navigate_to_pose │ robot-amr-01 │ 7000000000 │ OK │
│ 1776452455975402 │ trace-amr01-m1 │ span-a01-m1-bt │ /bt_navigator/navigate │ robot-amr-01 │ 6900000000 │ OK │
│ 1776452456075402 │ trace-amr01-m1 │ span-a01-m1-ctrl │ /controller_server/follow_path │ robot-amr-01 │ 6800000000 │ OK │
╰──────────────────┴────────────────┴──────────────────┴────────────────────────────────┴──────────────┴────────────┴─────────────╯

The demo dataset is a simulated AMR (robot-amr-01) running three navigation missions — one successful, one battery-critical abort, one timeout. Updated on every ROSQL release.

Use --format json for programmatic consumption, or --format csv for export.

Query your own data — local Parquet files

Point ROSQL at a directory of Parquet files from the demo-agent or any OTel exporter:

rosql query "FROM traces WHERE status = 'ERROR' SINCE 1 hour ago" \
--backend parquet \
--url ./telemetry/robotops_demo_agent/20260403-141530/

The --url directory must follow the demo-agent output layout:

<url>/
traces/ *.parquet → otel_traces
logs/ *.parquet → otel_logs
metrics/ *.parquet → otel_metrics
topic_messages/ *.parquet → topic_messages
mcap_metadata/ *.parquet → mcap_metadata

Query a private S3 bucket

rosql query "FROM traces WHERE status = 'ERROR' SINCE 1 hour ago" \
--backend parquet \
--url s3://my-bucket/robot-01/robotops_demo_agent/20260403-141530/

Set standard AWS environment variables for credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION). See Driver Support → for the full credential reference.

Your first query — PostgreSQL

If you have an OTel-compatible PostgreSQL database:

rosql query "FROM traces WHERE status = 'ERROR' SINCE 1 hour ago" \
--backend postgres \
--url postgresql://user:pass@localhost:5432/telemetry

Cross-signal correlation

DURING correlates events across data sources in a single query.

Find navigation failures that happened while the battery was critically low:

SELECT trace_id, span_name, service_name, duration, status_code, span_attributes
FROM traces
WHERE status = 'ERROR' AND action_name = '/navigate_to_pose'
DURING(
FROM topics WHERE topic_name = '/battery_state'
AND fields['percentage'] < 15
)
rosql query "SELECT trace_id, span_name, service_name, duration, status_code, span_attributes
FROM traces
WHERE status = 'ERROR' AND action_name = '/navigate_to_pose'
DURING(
FROM topics WHERE topic_name = '/battery_state'
AND fields['percentage'] < 15
)" \
--backend parquet \
--url s3://robotops-production-rosql-demo/data

Message causality

Trace the full causality chain from any span ID:

TRACE 'trace-001'

This walks parent_span_id → span_id recursively via a CTE and returns all spans in the causality chain in traversal order — something plain SQL has no primitive for.

[
{ "span_id": "span-001-root", "span_name": "/navigate_to_pose", "ros.node": "/bt_navigator", "duration": 8000000000 },
{ "span_id": "span-001-bt", "span_name": "/bt_navigator/navigate", "ros.node": "/bt_navigator", "duration": 7900000000 },
{ "span_id": "span-001-ctrl", "span_name": "/controller_server/follow_path","ros.node": "/controller_server", "duration": 7800000000 },
{ "span_id": "span-001-costmap", "span_name": "/local_costmap_node/update", "ros.node": "/local_costmap_node", "duration": 500000000 }
]

Compile to SQL

Inspect what ROSQL generates (no DB or --url needed):

rosql compile "FROM traces WHERE duration > 500 ms" --backend parquet

Output:

{
"backend": "parquet",
"ok": true,
"sql": "SELECT * FROM \"otel_traces\" WHERE \"duration\" > 500000000"
}

As a Rust library

cargo add rosql
use rosql::{parse, drivers::{sql::SqlBackend, ExecOptions}};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Parquet backend — local or S3
let backend = SqlBackend::from_parquet("s3://robotops-production-rosql-demo/data").await?;

// Or PostgreSQL
// let backend = SqlBackend::new("postgresql://user:pass@localhost/telemetry").await?;

let query = parse("
SELECT trace_id, span_name, service_name, duration, status_code
FROM traces WHERE status = 'ERROR' AND action_name = '/navigate_to_pose'
DURING(
FROM topics WHERE topic_name = '/battery_state' AND fields['percentage'] < 15
)
")?;
let result = backend.execute(&query, &ExecOptions::default()).await?;
println!("{}", serde_json::to_string_pretty(&result)?);
Ok(())
}

Next steps