Skip to main content
Version: 0.4

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
{
"columns": ["timestamp", "trace_id", "span_id", "span_name_col", "service_name", "duration", "status_code"],
"rows": [
[1776452455875402, "trace-amr01-m1", "span-a01-m1-root", "/navigate_to_pose", "robot-amr-01", 7000000000, "OK"]
],
"metadata": { "rows_returned": 3, "elapsed_ms": 353 }
}

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.

Query your own data

The easiest way to get Parquet telemetry files to query is to run the Robot Ops demo-agent, which exports ROS2 traces, logs, metrics, and topic data as Parquet files in the expected layout.

Alternatively, if you use the Robot Ops platform, your telemetry is already available to query — no local files needed.

Once the demo-agent has run, you'll have a directory like:

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

You can also query Parquet files directly from 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/

See Driver Support → for S3 credential setup and all backend options.

Cross-signal correlation

The killer feature: 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_col, 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
)
SINCE 6 hours ago
rosql query "SELECT trace_id, span_name_col, 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
)
SINCE 6 hours ago" \
--backend parquet \
--url ./telemetry/robotops_demo_agent/20260403-141530/

Message causality

Trace the full causality chain from any span ID:

TRACE 'a3f1c9d2e8b04f7a'

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

Compile to SQL

Inspect what ROSQL generates — no data source 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 path or s3://
let backend = SqlBackend::from_parquet("./telemetry/robotops_demo_agent/20260403-141530/").await?;

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

let query = parse("
SELECT trace_id, span_name_col, 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
)
SINCE 6 hours ago
")?;
let result = backend.execute(&query, &ExecOptions::default()).await?;
println!("{}", serde_json::to_string_pretty(&result)?);
Ok(())
}

Next steps