Elodin CLI
Command-Line Help
This document contains the help content for the Elodin command-line programs.
Command Overview:
elodin↴elodin editor↴elodin run↴elodin-db↴elodin-db run↴elodin-db lua↴elodin-db gen-cpp↴elodin-db fix-timestamps↴elodin-db merge↴elodin-db prune↴elodin-db compact↴elodin-db truncate↴elodin-db time-align↴elodin-db drop↴elodin-db info↴elodin-db query↴elodin-db export↴elodin-db export-videos↴
elodin
Usage: elodin [OPTIONS] [COMMAND]
Subcommands
editor— Launch the Elodin editor (default)run— Run an Elodin simulation in headless modemonte-carlo— Run Monte Carlo campaigns
Options
-
-u,--url <URL>Default value:
https://app.elodin.systems
elodin editor
Launch the Elodin editor (default)
Usage: elodin editor [--kdl KDL-PATH] [addr/path]
Arguments
<addr/path>— Optional connection target or simulation to run. Can be:- A socket address (e.g.,
127.0.0.1:2240) to connect to a running Elodin DB - A Python file (e.g.,
main.py) to run a simulation - A TOML file (e.g.,
s10.toml) to run from a plan - A directory containing
main.pyors10.toml
- A socket address (e.g.,
--kdl <PATH>— Optional parameter that will load a specific schematic KDL after connecting to a database.
Environment
BLOCKADE_API_KEY— Optional. Enables Skybox AI generation from the command palette (Skybox...→Generate Skybox...). Existing cached skyboxes and KDLskybox name="..."activation do not require this key.ELODIN_ASSETS— Optional. Overrides the asset root. Skybox assets are read from and generated into$ELODIN_ASSETS/skyboxes; otherwise./assets/skyboxesis used.
elodin run
Run an Elodin simulation in headless mode (not available on Windows)
Usage: elodin run [addr/path]
Arguments
<addr/path>— Simulation to run. Can be:- A Python file (e.g.,
main.py) - A TOML file (e.g.,
s10.toml) - A directory containing
main.pyors10.toml
- A Python file (e.g.,
For campaigns, use elodin monte-carlo run (below).
elodin monte-carlo
Run a simulation campaign with a bounded worker pool. Each worker owns a
deterministic resource slot (DB port and user-defined SITL ports), and the
runner recycles those slots across arbitrarily many runs. The campaign pins a
shared ELODIN_CACHE_DIR so large Cranelift constants are mapped once across
workers. Concurrency comes from --workers N (or workers = N in
campaign.toml): exactly N runs execute at once regardless of how many
processes each run spawns. When neither is set, the runner sizes itself from
logical cores; S10_MAX_INFLIGHT remains a low-level escape hatch for
budgeting by process count instead of run count. Only a numeric
S10_MAX_INFLIGHT overrides campaign.toml's workers; off (or an
unparsable value) disables admission limiting without overriding the
configured worker count.
Before anything launches, the runner takes an exclusive lock on the out dir
(campaign.lock) so two campaigns cannot interleave in the same output,
validates the entire static port plan for every worker (u16 overflow,
cross-name collisions), warns when planned ports fall inside the kernel
ephemeral range, raises its own file-descriptor limit, and reaps prior
campaign-scoped cgroups plus campaign-marked processes still bound to a
campaign port. Foreign port owners block startup with pid/name details instead
of being killed. Each run preflight-probes its static ports and reports
squatters by pid/name (port 20034 already bound by pid 320389 (weaverd)). On timeout the
runner tears the run down via its cgroup when one is available and via
per-recipe process groups otherwise; on Linux hosts without a delegated cgroup
(e.g. a plain ssh session) the campaign transparently re-executes itself under
systemd-run --user --scope — for both run and resume (opt out with
--no-self-scope).
Every worker slot also reserves db_port + 1 for elodin-db's always-on asset
server (the headless sensor-camera renderer fetches scene assets from it):
the assets port is validated, preflight-probed, and exported as
ELODIN_MC_PORT_DB_ASSETS, and a db_port = "auto" allocation always yields
a consecutive db/assets port pair.
Usage: elodin monte-carlo <COMMAND>
Subcommands
quickstart— Scaffold a runnable campaign (spec + campaign + hooks) from a simulationtemplate— Generate a starter plan or sampling spec from declared simulation paramssample— Materialize a sampling spec into a plan CSVrun— Execute a campaignresume— Re-run missing or failed runs from a previous campaignreport— Rebuild campaign reports
elodin monte-carlo quickstart
The fastest way to get a new simulation into a Monte Carlo campaign. Reads the
sim's declared params (python SIM.py params) and writes a ready-to-run
skeleton you then edit:
Prerequisite: the simulation must declare its tunable parameters with
el.monte_carlo.params_spec(...) before running quickstart — that declaration
is what populates spec.toml. A sim with no declared params produces an empty
[monte_carlo.variables] (quickstart prints a warning). Declare them in the
sim, for example:
return
This generates:
campaigns/drone/
spec.toml # one variable per param: uniform[min,max] when bounds are
# declared, else fixed at the default
campaign.toml # worker pool + post_run/post_campaign hooks
hooks/score.py # post_run: records result.json metrics, marks pass
hooks/gate.py # post_campaign: raises when any run failed (CI gate)
Each el.monte_carlo.Param(..., min=..., max=...) becomes a uniform
variable; params without bounds are emitted as fixed. Edit the ranges and the
hooks/score.py pass criterion, then run the printed elodin monte-carlo run
command. The campaign keeps the default exit code (0 even with partial
failures); hooks/gate.py is what turns a failure count into a non-zero exit
for a gated pipeline.
elodin monte-carlo run
Key options:
--workers <N>: run exactly N runs at once. Wins overS10_MAX_INFLIGHTandcampaign.toml'sworkers. Default: sized from logical cores.--plan <PLAN.csv>: materialized one-row-per-run plan. If a siblingspec.tomlis newer than the plan, the runner warns that the plan is stale.--spec <SPEC.toml>: sampling spec; sampled into a plan before execution.--campaign <CAMPAIGN.toml>: workers, resource slots, hooks, retries, timeouts, retention, quality gates, scratch dir,[[build]]steps, and a campaign-wide[env]table.--scratch-dir <DIR|auto>: run per-run IO (including the embedded DB) on a fast scratch filesystem; each run's surviving artifacts move to--out(sparse-aware) as it finishes.autopicks/dev/shmwhen present. Use this when the artifact volume cannot sustainworkers xDB write IOPS (network/EBS-class disks). Whenautofinds no/dev/shm, the campaign logs that per-run IO is staying on the artifact volume. If a run's final move fails (e.g. the artifact volume fills up), the run is marked failed with the destination error and its scratch copy is preserved — the campaign never deletes a scratch tree that still holds run artifacts, and logs the path to recover them from. The scratch location is deterministic per out dir, soresumefinds preserved passed runs and finishes the move instead of re-running them (a freshrunclears the campaign's scratch tree first).--cache-dir <DIR>: override the compile cache. The default lives in~/.cache/elodin/monte-carlo/const-cache(content-addressed, shared across campaigns) so--cleanand fresh out dirs never cause a compile storm.--strict-ports: error (instead of warn) when planned ports fall inside the kernel ephemeral range (/proc/sys/net/ipv4/ip_local_port_range).--no-self-scope: do not re-exec undersystemd-run --user --scopewhen no delegated cgroup is available.--runtime-threads <N>: override the orchestrator I/O thread pool. When unset (or0) it is auto-sized from the worker budget, capped at logical cores.--memory-probe: enable expensive shared-constant PSS sampling andmemory.json/processes.csvoutput. Leave this off for scaling benchmarks.--keep-existing: do not reap prior campaign cgroups or campaign-marked processes bound to campaign ports at startup.--fail-on-errors: exit non-zero when any run failed or missed scoring. Off by default so exploratory campaigns can finish with partial failures. Also configurable asfail_on_run_errors = trueincampaign.toml. For CI gates, prefer apost_campaignhook that raises onsummary.failed(seeexamples/apollo-lander/hooks/ci_gate.py) instead of relying on this flag.--post-run <HOOK.py>/--post-campaign <HOOK.py>: plain-Python lifecycle hooks.--clean: pruneruns/directories that are not part of the active plan.- Campaigns always display a live progress TUI with aggregate counts and active worker progress while they run.
campaign.toml additions beyond the flags above:
[resources.ports]values may be a numeric base (shifted byworker_id * port_stride, validated up front for every worker) or"auto"(allocated dynamically per run, collision-free by construction). Sims read them viael.monte_carlo.port("name")/ELODIN_MC_PORT_<NAME>either way.db_portalso accepts"auto"(allocated together with itsdb_port + 1assets port). Placing a named port ondb_port + 1is rejected — that port always belongs to the asset server.[env]: extra environment variables for every run's processes.[[build]]: any number of one-time build steps run before workers start.[retention]:keep_run_db = "always" | "never" | "on-fail", plusprune_on_pass/prune_on_failglob lists (relative to the run dir) removed after scoring, andcompact_run_db(defaulttrue) which truncates kept DBs' preallocated files to their real size.[quality]:max_behind_deadline_frac/max_real_time_factormark runs whose real-time pacing degraded asdegraded(they are excluded from passes;fail_on_degraded = truealso counts them towardfail_on_run_errors). Use this to keep oversubscribed campaigns from silently ingesting load-skewed samples.
The runner also injects machine-sympathy defaults into every run when the user
has not set them: OMP_NUM_THREADS / OPENBLAS_NUM_THREADS /
MKL_NUM_THREADS and XLA CPU thread flags sized to max(1, cores / workers),
so N concurrent sims do not each size their thread pools to every core.
Simulations that ingest parameters from a file (rather than via
el.monte_carlo.params(...)) can configure [params_delivery] in
campaign.toml: the runner writes each run's sampled params to a JSON/TOML file
and sets the env vars the simulation expects (with {seed} / {db_path} /
{run_id} / {run_dir} placeholders).
Outputs include per-run databases under runs/, results.csv, perf.csv,
resources.csv, campaign_summary.txt, and summary.json. With
--memory-probe, the runner also writes memory.json and processes.csv.
Each run's child process output is captured in runs/<run_id>/logs/, and the
per-run simulation timing snapshot is written to runs/<run_id>/sim_summary.json
for the final campaign rollup. Every failed, invalid, or degraded run carries a
one-line machine-readable failure_reason in results.csv / metrics.json
(timeouts, which leaf recipe failed and how, readiness-gate timeouts, port
conflicts with the owning pid), echoed on the live [failed] reporter line.
Setup-only failures (a run whose port preflight failed, so no process ever
spawned) skip the post_run hook entirely — there is no run database to score.
Real-time-paced runs also record behind_deadline_frac, real_time_factor,
and drift_resets, with worst-run callouts in the campaign summary.
Python Simulation Subcommands
When you run a simulation Python file directly (for example python examples/drone/main.py ...), the embedded simulation CLI supports additional subcommands:
run(default): run the simulation normally.bench: run a fixed-tick benchmark and print runtime metrics.params: print the simulation's declared Monte Carlo parameter schema as JSON.
bench options
--ticks <N>: number of ticks to run (default:1000)--profile: enable full profiling output (including HLO/graph analysis output)--detail: include per-phase timing breakdown such as upload, kernel, and download times
Examples
# CPU benchmark
# GPU benchmark with detailed timing
ELODIN_BACKEND=jax-gpu
# Full profiling output
elodin-db
Usage: elodin-db <COMMAND>
Subcommands
run— Run the Elodin database serverlua— Run a Lua script or launch a REPLgen-cpp— Generate C++ header filesfix-timestamps— Fix monotonic timestamps in a databasemerge— Merge two databases into one with optional prefixesprune— Remove empty components from a databasecompact— Truncate preallocated (sparse) database files to their real sizetruncate— Clear all data from a database, preserving schemastime-align— Align component timestamps to a target timestampdrop— Drop (delete) components from a databaseinfo— Display information about a databasequery— Run an EQL or SQL query and print results (table, CSV, parquet, or arrow-ipc)export— Export database contents to parquet, arrow-ipc, or csv filesexport-videos— Export video message logs to MP4 files
elodin-db run
Run the Elodin database server
Usage: elodin-db run [OPTIONS] [ADDR] [PATH]
Arguments
-
<ADDR>— Address to bind the server toDefault value:
[::]:2240 -
<PATH>— Path to the data directory. If not specified, uses the system default data directory.
Options
-
--log-level <LOG_LEVEL>— Log level (error, warn, info, debug, trace)Default value:
info -
--start-timestamp <TIMESTAMP>— Start timestamp in microseconds -
--config <PATH>— Path to a Lua configuration file to run on startup -
--http-addr <ADDR>— Address to bind the HTTP server to (enables HTTP API) -
--replay— Replay recorded data as live telemetry. The database advanceslast_updatedwith playback so connected editors see data "arriving" over time. Requires an existing database with recorded data. -
--follows <ADDR>— Follow another elodin-db instance, replicating all components, messages, and metadata over a single TCP connection. The local instance still accepts its own connections and data writers. -
--follow-packet-size <BYTES>— Target packet size for follow streaming. Small updates are buffered to this size before sending, reducing network overhead when the source has many components.Default value:
1500
elodin-db lua
Run a Lua script or launch an interactive REPL for querying and interacting with the database.
Usage: elodin-db lua [OPTIONS] [CONFIG] [-- <LUA_ARGS>...]
Arguments
-
<CONFIG>— Path to a Lua script to execute. If not provided, launches an interactive REPL. -
<LUA_ARGS>— Arguments to pass to the Lua script (available via theargtable)
Options
--db <PATH>— Path to an Elodin database directory
REPL Commands
When running in interactive mode, the following commands are available:
:sql [addr]— Connect to a database and enter SQL mode (default:localhost:2240):exit— Exit SQL mode or quit the REPL:helpor:h— Show help information
Lua API
connect(addr)— Connect to a database server, returns a ClientClient:sql(query)— Execute a SQL query and print resultsClient:get_time_series(component_id, start, stop)— Get time series dataClient:stream(stream)— Stream data from the databaseClient:get_msgs(msg_id, start, stop)— Get messages by IDClient:save_archive(path, format)— Save database to arrow-ipc or parquet files
elodin-db gen-cpp
Generate C++ header files for the Elodin database protocol. Outputs to stdout.
Usage: elodin-db gen-cpp
This command generates C++ header files containing type definitions and serialization code for interacting with the Elodin database from C++ applications.
elodin-db fix-timestamps
Fix monotonic timestamps in a database. This is useful when a database contains timestamps from different clock sources (wall-clock vs monotonic) that need to be normalized.
Usage: elodin-db fix-timestamps [OPTIONS] <PATH>
Arguments
<PATH>— Path to the database directory
Options
-
--dry-run— Show what would be changed without modifying the database -
-y,--yes— Skip the confirmation prompt -
--reference <REFERENCE>— Clock to use as reference when computing offsetsDefault value:
wall-clockPossible values:
wall-clock,monotonic
Example
# Preview changes without modifying
# Apply fixes using wall-clock as reference
# Apply fixes using monotonic clock as reference
elodin-db merge
Merge two databases into one with optional prefixes. This enables viewing simulation and real-world telemetry data simultaneously in the Elodin Editor.
Usage: elodin-db merge [OPTIONS] <DB1> <DB2> --output <OUTPUT>
Arguments
-
<DB1>— Path to the first source database -
<DB2>— Path to the second source database
Options
-
-o,--output <PATH>— Path for the merged output database (required) -
--prefix1 <PREFIX>— Prefix to apply to first database component names (e.g.,sim) -
--prefix2 <PREFIX>— Prefix to apply to second database component names (e.g.,truth) -
--dry-run— Show what would be merged without creating output -
-y,--yes— Skip the confirmation prompt -
--align1 <MICROSECONDS>— Alignment timestamp (in microseconds) for an event in DB1 -
--align2 <MICROSECONDS>— Alignment timestamp (in microseconds) for the same event in DB2. DB2 is shifted to align its anchor with DB1's anchor.
Component Naming
When prefixes are applied, component names are transformed using an underscore separator:
rocket.velocitywith prefixsimbecomessim_rocket.velocityrocket.velocitywith prefixtruthbecomestruth_rocket.velocity
Time Alignment
The --align1 and --align2 options allow you to align two databases based on a common event (e.g., launch, ignition, or simply the start of recording). Both options must be provided together. Timestamps are specified in microseconds for precise alignment.
When alignment is specified:
- DB1 is never shifted - it serves as the reference
- DB2 is shifted to align its anchor (
--align2) with DB1's anchor (--align1) - The shift can be forward (positive offset) or backward (negative offset)
This is particularly useful for aligning:
- A simulation database (monotonic timestamps starting at 0) with real-world telemetry (wall-clock timestamps)
- Two recordings of the same event captured with different clock sources
Example
# Basic merge with prefixes
# Merge with time alignment (align "launch" event at 15s in sim with 45s in flight)
# Timestamps are in microseconds: 15s = 15000000, 45s = 45000000
# DB2 (flight) is shifted backward by 30s to align
# Align wall-clock timestamps to monotonic (start DB2 at 0)
# DB2 starts at 4884937s (4884937000000us), align with DB1's start at 0
# DB2 is shifted backward by ~4.8M seconds
# Preview merge without creating output
elodin-db prune
Remove empty components from a database. Empty components are those that have been registered but contain no data entries.
Usage: elodin-db prune [OPTIONS] <PATH>
Arguments
<PATH>— Path to the database directory
Options
-
--dry-run— Show what would be pruned without modifying the database -
-y,--yes— Skip the confirmation prompt
Example
# Preview what would be pruned
# Prune empty components
elodin-db compact
Truncate a database's preallocated (sparse) storage files to their committed length. Elodin DB preallocates each component's data/index files as 8 GB sparse files, so a recorded database's apparent size can be hundreds of gigabytes while its real size is under one — and anything that walks it naively (rsync, tar, S3 upload, CI artifact collection) processes the apparent size. After compaction, apparent size matches real size.
Compacted databases stay fully readable (open, export, query, replay, editor playback). Further writes need the headroom that compaction removed, so only compact databases that are done recording, and never one that a live server has open. Monte Carlo campaigns compact retained run databases automatically ([retention] compact_run_db, on by default).
Usage: elodin-db compact [OPTIONS] <PATH>
Arguments
<PATH>— Path to the database directory
Options
--dry-run— Show how much apparent size would be reclaimed without modifying
Example
# Preview reclaimable space
# Truncate preallocated files to their real size
elodin-db truncate
Clear all data from a database while preserving component schemas and metadata. This effectively resets the database to an empty state, ready for fresh data collection.
Usage: elodin-db truncate [OPTIONS] <PATH>
Arguments
<PATH>— Path to the database directory
Options
-
--dry-run— Show what would be truncated without modifying the database -
-y,--yes— Skip the confirmation prompt
What is Preserved
When truncating a database, the following are preserved:
- Component schemas (data type definitions)
- Component metadata (names, IDs)
- Message log metadata
What is Removed
- All time-series data entries
- All message log entries
Example
# Preview what would be truncated
# Truncate all data (requires confirmation)
# Truncate without confirmation prompt
elodin-db time-align
Align component timestamps to a target timestamp. This is useful when a database contains components that were recorded at the same real-world moment but have different timestamp offsets.
Usage: elodin-db time-align [OPTIONS] --timestamp <TIMESTAMP> <PATH>
Arguments
<PATH>— Path to the database directory
Options
-
--timestamp <SECONDS>— Target timestamp (in seconds) to align the first sample to (required) -
--all— Align all components in the database -
--component <NAME>— Align only a specific component by name -
--dry-run— Show what would be changed without modifying the database -
-y,--yes— Skip the confirmation prompt
Component Selection
You must specify either --all or --component:
--allaligns every component in the database, shifting each so its first timestamp matches the target--component <NAME>aligns only the named component
How It Works
For each selected component, the command:
- Finds the first (minimum) timestamp in the component
- Calculates the offset needed to shift that timestamp to the target
- Applies the offset to all timestamps in the component
Each component is aligned independently, so if two components have different starting timestamps, they will both end up with their first timestamp at the target, but the relative timing within each component is preserved.
Example
# Preview alignment of all components to t=0
# Align all components to start at t=0
# Align a specific component to t=0
# Align all components to start at t=10.5 seconds
elodin-db drop
Drop (delete) components from a database. Supports fuzzy name matching, glob patterns, and bulk removal with confirmation before deletion.
Usage: elodin-db drop [OPTIONS] <PATH>
Arguments
<PATH>— Path to the database directory
Options
-
--component <NAME>— Component name to match using fuzzy matching. All matching components will be dropped. -
--pattern <PATTERN>— Glob pattern to match component names. Supports*(any characters) and?(single character). -
--all— Drop all components in the database -
--dry-run— Show what would be dropped without modifying the database -
-y,--yes— Skip the confirmation prompt
Matching Modes
You must specify exactly one of --component, --pattern, or --all:
| Option | Behavior |
|---|---|
--component | Fuzzy match against component names (e.g., "rocket.vel" matches "rocket.velocity") |
--pattern | Glob pattern match (e.g., "rocket.*" matches all components starting with "rocket.") |
--all | Drop all components in the database |
Fuzzy Matching
When using --component, the command uses fuzzy matching to find components:
- Case-insensitive matching (unless pattern contains uppercase)
- Matches partial strings (e.g., "vel" matches "velocity")
- Results are ranked by match score, best matches first
Glob Pattern Matching
When using --pattern, the following wildcards are supported:
*— matches any sequence of characters?— matches exactly one character
Examples:
rocket.*— matches "rocket.velocity", "rocket.position", etc.*.velocity— matches "rocket.velocity", "drone.velocity", etc.comp?— matches "comp1", "comp2", but not "comp10"
Safety
This command permanently deletes data and cannot be undone. The command:
- Shows all matching components and their entry counts before deletion
- Requires explicit confirmation (unless
-yis passed) - Supports
--dry-runto preview what would be deleted
Example
# Preview what would be dropped using fuzzy match
# Drop components matching fuzzy pattern (with confirmation)
# Drop components matching glob pattern
# Drop all velocity components
# Drop all components (dangerous!)
elodin-db info
Display information about a database, including recording state, time step configuration, and metadata.
Usage: elodin-db info [PATH]
Arguments
<PATH>— Path to the database directory. If not provided, uses the standard location (~/.local/share/elodin/db/data).
Example
# Display info for the default database
# Display info for a specific database
elodin-db query
Run an EQL (Elodin Query Language) or raw SQL query against a database and print the result. Output can be a terminal table, CSV, or binary formats (arrow-ipc, parquet). Does not require a running database server.
Usage: elodin-db query [OPTIONS] --eql <EQL> | --sql <SQL> <DBFILE>
Arguments
<DBFILE>— Path to the database directory
Query source (required, mutually exclusive)
You must provide exactly one of:
--eql <EQL>— EQL query (e.g. a component name likerocket.world_pos, or an expression like(rocket.world_pos[0], rocket.world_pos[1]))--sql <SQL>— Raw SQL query
Options
-
--offset <N|DURATION>— Skip rows before returning results. Can be:- An integer: skip that many rows. Negative values count from the end (e.g.
-10= start 10 rows before the last). - A duration: e.g.
2.6s,340000ms,53000ns. Negative duration means from the end (e.g.-1s= start 1 second before the last entry).
- An integer: skip that many rows. Negative values count from the end (e.g.
-
--limit <N|DURATION>— Return at most this many rows or this duration. Same formats as--offset(e.g.10,2.6s,340000ms,53000ns). -
-f,--format <FORMAT>— Output format.Default value:
tablePossible values:
table,csv,arrow-ipc,parquetFor
arrow-ipcandparquet, output is binary; pipe to a file (e.g.... > out.arrow). -
--flatten— Flatten vector columns into separate scalar columns (e.g.vel→vel.0,vel.1, …). Uses component metadata for column names when available (e.g.q0,q1,x,y,z). -
--time-format <FORMAT>— How to show the time column (when present).Possible values:
omit,datetime,s(seconds),ms(milliseconds),us(microseconds; aliases:µs). If not set, defaults to seconds, or to the unit implied by a duration in--offsetor--limit(e.g.--limit 500ms→ time in ms). -
-v,--verbose— Print the SQL used (EQL conversion or raw SQL) to stderr. -
-p,--precision <N|full>— Decimal places for floating-point values in table/CSV. Use a number (default6) orfullto show all digits. When notfull, a note is printed to stderr suggesting--precision fullfor full data. -
--row-index— Add a first columnindexwith the 0-based row index in the full result set (useful with--offset/--limitto see which rows are shown).
Time column
For EQL queries, a time column is included by default (first column) unless --time-format omit is used. The time column header includes the unit (e.g. time (s), time (μs), time (UTC)). Elodin stores time in microseconds since epoch.
Example
# First 10 rows of a component as a table
# Last 10 rows, show EQL→SQL on stderr
# Tuple expression, flattened, with row index and full precision
# Raw SQL, output as CSV
# Duration-based slice: last 2.5 seconds of data, time in seconds
# Export slice to Parquet (binary; pipe to file)
elodin-db export
Export database contents to parquet, arrow-ipc, csv, or a Foxglove-compatible mcap file without requiring a running server. This is useful for analyzing telemetry data with external tools like pandas, DuckDB, or other data analysis frameworks, or for reviewing and sharing recordings in Foxglove.
The export runs in parallel across components and is dramatically faster than the historical single-threaded path; on a 20-core machine the customer's ~3 GB CSV export went from ~32 s (default formatting) to ~2.2 s (--csv-fast-floats), a 14× speedup.
Usage: elodin-db export [OPTIONS] --output <OUTPUT> <PATH>
Arguments
<PATH>— Path to the database directory
Options
-
-o,--output <PATH>— Output directory for exported files (required) -
--format <FORMAT>— Export formatDefault value:
parquetPossible values:
parquet,arrow-ipc,csv,mcap(alias:foxglove) -
--flatten— Flatten vector columns to separate columns (e.g.,vel_nedbecomesvel_ned.x,vel_ned.y,vel_ned.z) -
--pattern <PATTERN>— Filter components by glob pattern (e.g.,NavNED.*,*.velocity) -
--join— Group components by name prefix (everything before the last.) and emit one file per group. Components in a group are joined on time. See Component Joining below. -
--csv-fast-floats— CSV-only: formatf32andf64values viaryuinstead of Rust'sDisplay. Much faster (around 2x on float-heavy components) but produces a slightly different (still round-trippable) text format (e.g.0.0000001becomes1e-7,1.0stays1.0instead of1). Off by default so existing pipelines that consume the CSV see no format change. -
--mono-ns— Replace the time column with integer nanoseconds since unix epoch. The column is renamedtime_nsand changes type fromTimestamp(Microsecond)toInt64. Mutually exclusive with--mono-us. Applies to all formats (CSV, Parquet, Arrow IPC). -
--mono-us— Replace the time column with integer microseconds since unix epoch. The column is renamedtime_usand changes type fromTimestamp(Microsecond)toInt64. Mutually exclusive with--mono-ns. Applies to all formats. -
--include-private— Include components whose metadata hasprivate: true. Off by default — those components are skipped (see Private Components below). -
--all-assets— MCAP-only: attach every file under{db}/assets/instead of only schematic-referenced assets. -
--epoch-offset-us <i64>— MCAP-only: add this offset (µs) to every sample timestamp before writing MCAPlog_time/publish_time. When omitted and the earliest sample is pre-1970 (negative Unix µs), the exporter auto-rebases so earliest becomest=0and records the shift in metadata keyelodin.time_offset_us. The same auto-rebase runs if a requested offset would leave any sample pre-epoch (MCAPlog_timeis unsigned and cannot store absolute 1969-era times) — so--epoch-offset-us 0on Apollo-style data still preserves playback ordering rather than collapsing every sample tot=0. -
--max-embed-mb <u64>— MCAP-only: maximum GLB size (MiB) to base64-embed inside/sceneSceneUpdatemessages (default32). Larger GLBs are still attached to the MCAP but their model primitive is omitted entirely (no empty-datamodel). The viewport follow-entity's mesh is always embedded regardless of this limit.
Export Formats
| Format | Extension | Description |
|---|---|---|
parquet | .parquet | Columnar format with compression. Best for large datasets and analytics tools. |
arrow-ipc | .arrow | Arrow IPC format. Fast to read/write, good for streaming data between processes. |
csv | .csv | Plain text format. Universal compatibility but larger file sizes. |
mcap | .mcap | Foxglove-compatible MCAP recording (single file, zstd-compressed JSON channels). See Foxglove MCAP Export below. |
Foxglove MCAP Export
--format mcap (alias --format foxglove) writes a single {db_name}.mcap plus a generated {db_name}.foxglove-layout.json, ready to open in Foxglove or upload to the Foxglove Data Platform:
- Every component becomes a JSON channel (
drone.world_pos→/drone/world_pos) with fields named after the component'selement_names; dotted names nest (e.r→.e.r). - Pose components (
*.world_pos, 7 elements) additionally publishfoxglove.FrameTransformson/tf(world→ entity), driving the Foxglove 3D panel. - Schematic
object_3dmeshes/GLBs (including literal-pose entities),line_3dtrajectories (decimated, pixel-width scale-invariant lines), constantvector_arrows, andworld_mesh "globe"(swapped toearth.glbon the Earth frame) becomefoxglove.SceneUpdateentities — one topic per entity (/scene/<entity-id>), because Foxglove backfills only the latest message per topic when a 3D panel (re)mounts. Data-drivenvector_arrows (≤30 Hz) publish one topic each (/scene_dynamic/<name>). Multipleobject_3don the same entity get unique ids ({frame}-model,{frame}-model-2, …). Literal poses compose with GLBtranslate/rotate. - Viewport
near/farare honored in the 3D panelcameraState;faris clamped to ≥4× the camera distance (derived from the viewportposoffset, includingtranslate_world(x,y,z)-style method chains). Each 3D panel follows its ownlook_at/possubject and subscribes to every scene topic. - A schematic
coordinate lat=… lon=… alt=…node emits static world→NED/ENUanchor transforms and re-parents entities whoseobject_3ddeclaresframe="NED"/"ENU"under them. - Message logs export as
foxglove.CompressedVideo(native H.264 and sensor-camera RGBA re-encoded via openh264 when thevideo-exportfeature is on),foxglove.RawImage(sensor cameras without video-export),foxglove.Log(LogEntry streams → Log/RosOutpanel), or base64 JSON (other). - Schematic KDL files and referenced GLB assets travel along as MCAP attachments; DB and component metadata as MCAP metadata records (including
elodin.time_offset_uswhen timestamps are rebased). - The generated layout mirrors the Elodin schematic: tabs/splits map to Foxglove tabs/splits,
graphEQL expressions expand to Plot panel series,viewportbecomes a 3D panel following the vehicle,component_monitorbecomes Raw Messages,log_streambecomes a Log panel. SQLquery_plot, icons, thrusters, bloom/hdr, and non-globeworld_meshregions are skipped with a console note.
# Export and open locally (drag into app.foxglove.dev or the desktop app)
# One-shot upload + layout + view URL
Vector Column Handling
By default, vector columns (e.g., 3D positions, quaternions) are exported as fixed-size lists. When exporting to CSV without --flatten, these appear as JSON-like strings (e.g., [1.0, 2.0, 3.0]).
With --flatten, vector columns are split into separate scalar columns with element names as suffixes:
position(3D vector) →position.x,position.y,position.zquaternion(4D vector) →quaternion.q0,quaternion.q1,quaternion.q2,quaternion.q3
This is particularly useful for CSV export or when working with tools that don't support nested types.
Glob Pattern Filtering
The --pattern option supports standard glob wildcards:
*— matches any sequence of characters?— matches exactly one character
Examples:
rocket.*— export only components starting with "rocket."*.velocity— export only velocity componentsNavNED.*— export only NavNED components
Component Joining
By default each component is exported to its own file. With --join, components are grouped by their name prefix (everything before the last .) and joined on time into a single file per group:
| Component name | Group | Short name |
|---|---|---|
TARGETMESSAGE.POS_ECEF | TARGETMESSAGE | POS_ECEF |
TARGETMESSAGE.VEL_ECEF | TARGETMESSAGE | VEL_ECEF |
rocket.set_control | rocket | set_control |
tick | tick | tick |
The example above produces three output files: TARGETMESSAGE.csv (with time, POS_ECEF, VEL_ECEF columns), rocket.csv, and tick.csv. Components in a group with identical timestamp arrays are zipped onto a shared time axis; components with disjoint timestamps go through a sorted union with NULL fill for the missing rows. Composes with --flatten, --csv-fast-floats, --mono-ns/--mono-us, and --pattern.
Time Column Format
The default time column is a Timestamp(Microsecond) field named time rendered as ISO 8601 in CSV (e.g. 1970-01-01T00:00:00.019). The two --mono-* flags swap it for an integer column to make it directly comparable to user-stored monotonic timestamps:
| Flag | Column header | Type | Example value |
|---|---|---|---|
| (none, default) | time | Timestamp(Microsecond) | 1970-01-01T00:00:00.019 |
--mono-us | time_us | Int64 | 19000 |
--mono-ns | time_ns | Int64 | 19000000 |
The conversion is exact within the database's microsecond storage precision: time_ns = time_us * 1000. If your data has its own monotonic field in nanoseconds (e.g. TIME_MONOTONIC), --mono-ns makes the values comparable cell-for-cell — typically matching every row to within ~1 µs (any difference is sub-microsecond rounding from the on-disk storage).
Private Components
Components whose metadata contains "private": "true" are skipped during export by default. This lets simulation authors mark intermediate or sensitive components (e.g. internal Kalman filter state, scratch buffers, large covariance matrices) so that downstream pipelines never see them when re-running elodin-db export against a recorded DB. See the Python API reference for how to set this metadata key on a component.
When the flag is honored, the export prints a one-line skip message per component:
Skipping drone.estimate_covariance (private)
Pass --include-private to override the filter and export every component regardless of metadata (useful for forensic or full-fidelity exports).
Example
# Export all components to parquet (default format)
# Export to CSV with flattened vectors
# Export to Arrow IPC format
# Export only specific components using glob pattern
# Export velocity components as flattened CSV
# Export with maximum CSV throughput (ryu floats + integer-nanosecond time column)
# Group components by message family into one file per group
# Same, with flattened vectors and integer-microsecond time
# Include components flagged `private: true` in the export
elodin-db export-videos
Export video message logs to MP4 files. This command supports both H.264 streams stored as timestamped Annex B payloads and sensor_camera streams stored as raw RGBA frames. H.264 streams are muxed directly into MP4; sensor_camera frames are encoded to H.264 during export and then muxed into standards-compliant MP4 files (e.g. for playback in QuickTime, VLC, or other players).
Usage: elodin-db export-videos [OPTIONS] --output <OUTPUT> <PATH>
Arguments
<PATH>— Path to the database directory
Options
-
-o,--output <PATH>— Output directory for MP4 files (required) -
--pattern <PATTERN>— Filter message logs by name glob (e.g.test-*). If not set, all video message logs in the database are exported. -
--fps <FPS>— Default frame rate when the H.264 stream’s SPS (Sequence Parameter Set) has no timing info, or when asensor_camerastream has invalid FPS metadata. Validsensor_camerametadata uses the camera’s configuredfps.Default value:
30
How video gets into the database
Video is stored in Elodin DB as message logs: each frame is a timestamped binary payload. H.264 video logs store Annex B NAL units; sensor_camera logs store raw RGBA frame buffers and rely on the database's sensor_cameras metadata for width, height, and FPS. Typical ingestion paths:
- GStreamer + elodinsink: A GStreamer pipeline (e.g.
videotestsrc→x264enc→h264parse→elodinsink) sends H.264 frames over TCP to the database. Theelodinsinkplugin uses a configurable message name (e.g.test-video) that becomes the log name. See the Video Streaming Example in the repository (examples/video-stream/) for a full setup. - Sensor cameras:
world.sensor_camera(...)registers camera metadata and the headless render server writes raw RGBA frames into the database.export-videosdetects these streams fromsensor_camerasmetadata and encodes them during export. - Schematic: A
video_stream "name"entry in the schematic ties a video tile in the Elodin Editor to that message name; the same name is used when exporting withexport-videos.
For H.264 streams, resolution and frame rate are read from the SPS in the first keyframe and no re-encoding is done. For sensor_camera streams, resolution and frame rate come from the camera metadata and raw RGBA frames are encoded during export. Output files use fast-start layout for web and player compatibility.
Example
# Export all video streams to an output directory
# Export only streams matching a glob (e.g. names starting with "test-")
# Use a specific default FPS when SPS has no timing info