baglint
Validates the contents of MCAP recordings against a declarative specification. Checks for missing topics, recording gaps and rate violations, and returns a non-zero exit status when a recording does not satisfy the specification.
Validation of the MCAP container itself, such as chunk CRCs and index
integrity, is out of scope. Use mcap doctor for that.
Requirements
- Python 3.10 or later
Installation
$ python3 -m venv .venv
$ .venv/bin/pip install -e .
When ROS 2 is sourced, /opt/ros/$ROS_DISTRO/lib/python3.*/site-packages is on
PYTHONPATH and is inherited by the virtual environment, which causes ROS
pytest plugins to load during test runs. Clear it when invoking the tool:
$ env -u PYTHONPATH .venv/bin/baglint --help
Usage
$ baglint BAG [-s SPEC] [-f {text,json}] [--strict]
$ baglint BAG --init [--margin FRACTION]
| Option | Description |
|---|---|
-s, --spec |
Specification file to validate against |
-f, --format |
Output format, text (default), json or html |
--strict |
Exit non-zero on WARN findings as well as FAIL |
--init |
Print a specification generated from the recording instead of validating it |
--margin |
With --init, the fraction below the observed rate at which to set min_rate. Default 0.1 |
--version |
Print version and exit |
Without -s, no checks run and nothing is validated.
Generating a specification
--init writes a specification describing a recording, which is the practical
way to produce a first one:
$ baglint good_run.mcap --init > spec.yaml
$ baglint run_028.mcap --spec spec.yaml
min_rate is set below each topic's observed mean rate by --margin, and
max_gap_ms to twice the worst interval observed. Topics with fewer than ten
messages are generated as presence-only, since a mean rate over so few samples
describes the recording length rather than the publisher.
The bounds describe the recording they came from, defects included. Generate from a run that is known to be good, and treat the result as a starting point.
Specification
Topic keys accept glob patterns. The first matching entry applies, so list specific topics before wildcards.
topics:
/joint_states:
min_rate: 490
max_gap_ms: 10
/camera/*:
min_rate: 25
/diagnostics:
required: false
| Key | Type | Description |
|---|---|---|
min_rate |
float | Minimum mean publication rate in Hz, measured over the topic's own span |
max_gap_ms |
float | Maximum permitted interval between consecutive messages |
required |
bool | Whether a topic named literally must be present. Default true |
check_stamps |
bool | Validate header.stamp ordering. Default false, as it deserializes payloads |
Transforms
Required frame pairs are checked for availability across the recording:
transforms:
required:
- [camera_link, base_link]
max_stale_ms: 500
Each pair is resolved through the transform tree, in either direction, since a
transform can be inverted. /tf_static is treated as valid for the whole
recording. A dynamic edge is usable from one update to the next and for
max_stale_ms after its last, which defaults to 500. Reading transforms
requires deserializing /tf, so it happens only when required is non-empty.
Output
$ baglint run_028.mcap --spec examples/demo_spec.yaml
run_028.mcap
6 topics · 86,095 messages · 2m00s
FAIL /camera/image
rate 24.0 Hz below minimum 25 Hz [log_time]
FAIL /diagnostics
only 1 message(s); too few to measure a rate against the 1 Hz minimum [log_time]
FAIL /imu
1 stamp(s) moved backwards (worst 190.0 ms at 50.00 s) [header.stamp]
FAIL /joint_states
2 missing interval(s) >10 ms (worst 112.0 ms at 88.50 s) [log_time]
FAIL /scan
required by spec but no messages in bag
FAIL /tf
camera_link -> base_link unavailable for 13.52 s across 1 interval(s) (worst 13.52 s at 60.48 s) [header.stamp]
WARN /imu
18 message(s) repeated the previous stamp [header.stamp]
WARN /imu
6 message(s) carried a zero stamp [header.stamp]
8 findings: 6 FAIL, 2 WARN
To regenerate the recording used above:
$ python examples/make_demo_bag.py run_028.mcap
--format html writes a self-contained report with one track per topic on a
shared time axis, showing where each defect falls and whether defects on
different topics coincide:
$ baglint run_028.mcap --spec examples/demo_spec.yaml --format html > report.html
Grey marks when a topic was recorded, red a failure, amber a warning. A topic struck through was never recorded at all. The report embeds its styles and fetches nothing, so it can be published as a CI artifact.
--format json emits the same findings for machine consumption. Each carries a
stable code, intended for filtering and baselining in CI:
| Code | Level | Meaning |
|---|---|---|
gap |
FAIL | Consecutive messages exceeded max_gap_ms |
rate_below_min |
FAIL | Mean rate fell below min_rate |
rate_unmeasurable |
FAIL | Fewer than two messages, so no rate can be computed |
missing_topic |
FAIL | A required topic carried no messages |
stamp_backwards |
FAIL | A header.stamp preceded the stamp before it |
stamp_duplicate |
WARN | A message repeated the previous header.stamp |
stamp_unset |
WARN | A message carried a zero header.stamp |
stamp_unavailable |
WARN | check_stamps was set on a message type without a header |
tf_unavailable |
FAIL | A required frame pair was unresolvable for some span |
tf_frame_missing |
FAIL | A required frame never appeared in the transform tree |
Message timestamps
Each message carries two timestamps, and findings state which one was used:
| Timestamp | Set by | A defect indicates |
|---|---|---|
log_time |
the recorder, on write | the recorder stalled: disk I/O, CPU starvation, a terminated node |
header.stamp |
the publisher, at sample time | the sensor or its driver misbehaved |
Gap and rate checks read log_time. Stamp checks read header.stamp and are
enabled per topic with check_stamps, which is off by default because it
requires deserializing every message on that topic:
topics:
/imu:
check_stamps: true
A stamp that moves backwards is reported as a failure. Downstream consumers
such as tf2, message_filters and the SLAM backends assume a non-decreasing
stamp sequence per topic, and do not report the violation themselves. Duplicate and zero stamps
are reported as warnings.
A zero stamp is treated as never populated rather than as a timestamp, so it does not become the baseline for the messages that follow it.
Exit codes
| Code | Condition |
|---|---|
| 0 | No FAIL findings. With --strict, no WARN findings either |
| 1 | At least one finding at or above the failing level |
| 2 | Invalid arguments, unreadable recording, or malformed specification |
Development
$ make install
$ make test
Tests construct MCAP recordings with injected defects at known offsets. See
tests/fixtures.py.
Metadata
Release files for baglint 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| baglint-0.2.1.tar.gz | 270.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| baglint-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 296.8 kB
Release files / baglint-0.2.1.tar.gz
| Download URL | baglint-0.2.1.tar.gz |
|---|---|
| Size | 270.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
06daa2976d34b6a4854f3d74ed395df823aa6b5fca66131f00e3f15829fa804d
|
|
BLAKE2b-256 checksum How to use checksums |
ccb113d83ea5250e65456effefe314c74c005f5d89bec8df32341672d266def9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / baglint-0.2.1-py3-none-any.whl
| Download URL | baglint-0.2.1-py3-none-any.whl |
|---|---|
| Size | 26.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a76271f8a20d3340c4b6a6e831e8f2bffbe0c14eb8cb8cbd8f90425452cdc093
|
|
BLAKE2b-256 checksum How to use checksums |
824802192469fd1552682262e9fa13cfff086ce6de9927c6d32d0b0988ebc780
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|