@@ -147,9 +147,9 @@ The values above are the defaults, and they matter:
147147 everything twice, under two different Tags.
148148- **High-rate sensor types are excluded**, by type rather than by name (a camera topic is
149149 not reliably called anything in particular). One 640×480 `sensor_msgs/msg/Image` is
150- roughly 900 kB of JSON numbers; at 30 Hz nothing downstream is sized for it. Override
151- the list deliberately if you want them — DC will not stop you, but read the next
152- section first.
150+ 900 kB on the wire and roughly 3.7 MB once every pixel byte is a JSON number; at 30 Hz
151+ nothing downstream is sized for it. Override the list deliberately if you want them —
152+ DC will not stop you, but read the next section first.
153153
154154To replace a list, write the replacement; note that an *empty* YAML list (`[]`) cannot be
155155loaded by rclcpp (it has no inferable element type), so use a pattern that matches
@@ -198,38 +198,68 @@ acknowledged (see [Destinations](./destinations.md#delivery-guarantees)).
198198
199199# ## How much data is this?
200200
201- Measured against a TurtleBot3-Waffle-shaped workload — the topic mix
202- ` dc_simulation` ' s warehouse world bridges out, at the rates those sensors actually run
203- (IMU 100 Hz, odom/tf/joint_states 50 Hz, camera 30 Hz, cmd_vel 20 Hz, lidar 10 Hz,
204- battery 1 Hz) — collected into a `file` Destination:
201+ These figures come off a **simulated robot**, not a spreadsheet :
202+ ` tools/sim/scripts/measure_raw_volume.sh` boots `dc_simulation`'s warehouse world, drives
203+ the TurtleBot3-Waffle in a slow circle, points a Bridge in raw mode at its live topics,
204+ and reports what a `file` Destination stored, per Tag. Everything below is one run of it —
205+ re-run it after anything that changes what a Record costs. It is a local tool, not a CI
206+ gate; its header explains the three configurations and why its rates are counted in
207+ *simulated* seconds.
205208
206- | Configuration | Stored |
209+ Rates are that world's Waffle's : IMU 200 Hz, odometry and TF 30 Hz (the `DiffDrive`
210+ plugin), lidar and both RGBD cameras 5 Hz. `/cmd_vel` and `/clock` are left out — the
211+ first is the benchmark's own driving, the second exists only because the robot is
212+ simulated.
213+
214+ | Configuration | Shipped |
207215|---|---|
208- | **Defaults** (10 Hz cap, sensor types excluded) | 19 kB/s — **67 MB/hour, 1.6 GB/day** |
209- | …plus `scan` and `tf` re-enabled | ≈ 4.3 GB/day |
210- | …plus the 640×480 camera re-enabled | ≈ **1.6 TB/day** |
216+ | **Defaults** (10 Hz cap, sensor types excluded) | 24 kB/s — **86 MB/hour, 2.1 GB/day** |
217+ | …plus `scan` and `tf` re-enabled | 69 kB/s — **5.9 GB/day** |
218+ | …plus both 1280×720 cameras re-enabled | ≈ **9.5 TB/day**, if anything could carry it |
211219
212220Per-Record cost, which is what to multiply by your own topics' rates :
213221
214- | Message | Bytes per Record |
215- |---|---|
216- | `geometry_msgs/msg/Twist` | 202 |
217- | `sensor_msgs/msg/JointState` (2 joints) | 326 |
218- | `sensor_msgs/msg/Imu` | 488 |
219- | `nav_msgs/msg/Odometry` | 605 |
220- | `tf2_msgs/msg/TFMessage` (2 transforms) | 547 |
221- | `sensor_msgs/msg/LaserScan` (360 ranges + intensities) | 2 585 |
222- | `sensor_msgs/msg/Image` (640×480 `rgb8`) | **1 843 521** |
223-
224- The last row is the whole argument for the default type exclusions : one VGA frame costs
225- as much as ~3 000 odometry Records, and JSON roughly **doubles** a byte array (every
226- pixel byte becomes `"0,"`).
227-
228- **The size cap will not save you from a camera** — and it should not be asked to. That
229- same 640×480 frame is 921 600 bytes on the wire, *under* the default
230- ` max_message_size_bytes` of 1 MiB, so it passes the size gate and lands as 1.8 MB of
231- JSON. It is `exclude_types` that keeps images out, and removing that list removes the
232- protection entirely.
222+ | Topic (message) | Publishes at | Bytes per Record |
223+ |---|---|---|
224+ | `/joint_states` (`JointState`, 2 joints) | 1 000 Hz † | 355 |
225+ | `/tf` (`TFMessage`, 1 transform) | 30 Hz | 406 |
226+ | `…/camera_info` (`CameraInfo`) | 5 Hz | 581 |
227+ | `/odom` (`Odometry`) | 30 Hz | 669 |
228+ | `/imu` (`Imu`) | 200 Hz | 767 |
229+ | `/scan` (`LaserScan`, 360 ranges + intensities) | 5 Hz | 7 878 |
230+ | `…/image_raw` (`Image`, 1280×720 `rgb8`) | 5 Hz | **10 981 032** |
231+
232+ † the simulator's `JointStatePublisher` runs every physics step; a real driver is far
233+ slower. It makes no difference to the total, which is the point of the next paragraph.
234+
235+ **The rate cap is what makes the first row affordable, not the topic list.** A topic
236+ publishing faster than `max_rate_hz` contributes `bytes_per_record × 10` per second no
237+ matter how fast it actually runs, so `joint_states` at 1 000 Hz and `imu` at 200 Hz cost
238+ 3.6 kB/s and 7.7 kB/s respectively. Only the topics *below* the cap — the 5 Hz sensors —
239+ bill at their real rate, which is why re-enabling one 5 Hz lidar (7.9 kB per Record, 40
240+ kB/s, 3.5 GB/day) nearly triples the total on its own.
241+
242+ The last row is the whole argument for the default type exclusions : one 1280×720 frame
243+ costs as much as ~16 000 odometry Records. JSON does not merely double a byte array —
244+ ` 10 981 032` bytes for a 2 764 800-byte frame is **four times** the wire size, because
245+ most pixel values print as three digits and a comma.
246+
247+ Two cameras at 5 Hz is 110 MB/s of Records, and **nothing in the pipeline carries that**,
248+ which is why that row says "if anything could carry it" : in the run it comes from, the
249+ Shipper refused 475 of the 894 Records offered to it (`dropped … 475 shipper`, 53 %). The
250+ collapse was not confined to the images either — `imu` arrived at 4 Hz instead of 200 and
251+ ` odom` at 0.7 Hz instead of 30, because the Bridge spent the window serializing frames.
252+ Collecting a camera raw does not cost you a camera's worth of storage; it costs you the
253+ rest of your collection.
254+
255+ **The size cap will not save you from a camera** — and it should not be asked to. A
256+ 640×480 `rgb8` frame is 921 600 bytes on the wire, *under* the default
257+ ` max_message_size_bytes` of 1 MiB, so it passes the size gate and lands as megabytes of
258+ JSON. (This world's 1280×720 frames are three times that and the default cap does drop
259+ them whole — which is why the benchmark's camera profile has to lift it to measure
260+ anything at all. Relying on that is a collection policy that silently switches on the day
261+ someone fits a smaller sensor.) It is `exclude_types` that keeps images out, and removing
262+ that list removes the protection entirely.
233263
234264The tempting fix — lower the size cap until frames stop fitting — is a bad trade, because
235265**the two limits fail differently**:
0 commit comments