Skip to content

Commit ad593f9

Browse files
committed
expose RRD manifests all through the CLI
1 parent a37a752 commit ad593f9

File tree

5 files changed

+93
-28
lines changed

5 files changed

+93
-28
lines changed

crates/top/rerun/src/commands/rrd/filter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ impl FilterCommand {
176176
.join()
177177
.map_err(|err| anyhow::anyhow!("Unknown error: {err:?}"))??; // NOLINT: there is no `Display` for this `err`
178178

179-
let rrds_in_size = rx_size_bytes.recv().ok();
179+
let rrds_in_size = rx_size_bytes.recv().ok().map(|(size, _footers)| size);
180180
let size_reduction =
181181
if let (Some(rrds_in_size), rrd_out_size) = (rrds_in_size, rrd_out_size) {
182182
format!(

crates/top/rerun/src/commands/rrd/merge_compact.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ fn merge_and_compact(
321321

322322
rrd_out.flush().context("couldn't flush output")?;
323323

324-
let rrds_in_size = rx_size_bytes.recv().ok();
324+
let rrds_in_size = rx_size_bytes.recv().ok().map(|(size, _footers)| size);
325325
let num_chunks_reduction = format!(
326326
"-{:3.3}%",
327327
100.0 - num_chunks_after as f64 / (num_chunks_before as f64 + f64::EPSILON) * 100.0

crates/top/rerun/src/commands/rrd/print.rs

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use anyhow::Context as _;
22
use arrow::array::RecordBatch;
33
use itertools::Itertools as _;
44

5+
use re_arrow_util::RecordBatchExt as _;
56
use re_byte_size::SizeBytes as _;
67
use re_log_types::{LogMsg, SetStoreInfo};
78
use re_sdk::EntityPath;
@@ -43,13 +44,17 @@ pub struct PrintCommand {
4344
#[clap(long, default_missing_value="true", num_args=0..=1)]
4445
full_metadata: Option<bool>,
4546

46-
/// Transpose record batches before printing them?
47-
#[clap(long, default_missing_value="true", num_args=0..=1)]
48-
transposed: Option<bool>,
49-
5047
/// Show only chunks belonging to this entity.
5148
#[clap(long)]
5249
entity: Option<String>,
50+
51+
/// If true, displays all the parsed footers at the end.
52+
#[clap(long, default_missing_value="true", num_args=0..=1)]
53+
footers: Option<bool>,
54+
55+
/// Transpose record batches before printing them?
56+
#[clap(long, default_missing_value="true", num_args=0..=1)]
57+
transposed: Option<bool>,
5358
}
5459

5560
impl PrintCommand {
@@ -60,15 +65,17 @@ impl PrintCommand {
6065
verbose,
6166
migrate,
6267
full_metadata,
63-
transposed,
6468
entity,
69+
footers,
70+
transposed,
6571
} = self;
6672
let continue_on_error = continue_on_error.unwrap_or(true);
6773

6874
let migrate = migrate.unwrap_or(true);
69-
let transposed = transposed.unwrap_or(false);
7075
let full_metadata = full_metadata.unwrap_or(false);
7176
let entity = entity.map(|e| EntityPath::parse_forgiving(&e));
77+
let footers = footers.unwrap_or(false);
78+
let transposed = transposed.unwrap_or(false);
7279

7380
let options = Options {
7481
verbose,
@@ -87,7 +94,7 @@ impl PrintCommand {
8794
);
8895
}
8996

90-
let (rx, _) = read_rrd_streams_from_file_or_stdin(&path_to_input_rrds);
97+
let (rx, rx_done) = read_rrd_streams_from_file_or_stdin(&path_to_input_rrds);
9198

9299
for (_source, res) in rx {
93100
let mut is_success = true;
@@ -113,6 +120,35 @@ impl PrintCommand {
113120
}
114121
}
115122

123+
if footers {
124+
for (_, rrd_manifests) in rx_done {
125+
for (source, mut rrd_manifest) in rrd_manifests? {
126+
// Just to be nice: this will display the origin of the data in the header.
127+
rrd_manifest
128+
.data
129+
.schema_metadata_mut()
130+
.insert("rerun:source".to_owned(), source.to_string());
131+
132+
// Drop all per-entity and/or per-component columns to keep things readable.
133+
//
134+
// TODO(cmc): more config flags for columns to show etc.
135+
let filtered = rrd_manifest
136+
.data
137+
.filter_columns_by(|f| f.name().starts_with("chunk_"))?;
138+
139+
let formatted = re_arrow_util::format_record_batch_opts(
140+
&filtered,
141+
&re_arrow_util::RecordBatchFormatOpts {
142+
max_cell_content_width: 32,
143+
..Default::default()
144+
},
145+
);
146+
147+
println!("{formatted}");
148+
}
149+
}
150+
}
151+
116152
Ok(())
117153
}
118154
}

crates/top/rerun/src/commands/stdio.rs

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crossbeam::channel;
55
use itertools::Itertools as _;
66

77
use re_chunk::external::crossbeam;
8+
use re_log_encoding::RrdManifest;
89

910
// ---
1011

@@ -30,17 +31,19 @@ impl std::fmt::Display for InputSource {
3031
/// * The first channel contains both the successfully decoded data, if any, as well as any
3132
/// errors faced during processing.
3233
/// * The second channel, which will fire only once, after all processing is done, indicates the
33-
/// total number of bytes processed.
34+
/// total number of bytes processed, and returns all RRD manifests that were parsed from footers
35+
/// in the underlying stream.
3436
///
3537
/// This function is best-effort: it will try to make progress even in the face of errors.
3638
/// It is up to the user to decide whether and when to stop.
3739
///
3840
/// This function is capable of decoding multiple independent recordings from a single stream.
41+
#[expect(clippy::type_complexity)] // internal private API for the CLI impl
3942
pub fn read_rrd_streams_from_file_or_stdin(
4043
paths: &[String],
4144
) -> (
4245
channel::Receiver<(InputSource, anyhow::Result<re_log_types::LogMsg>)>,
43-
channel::Receiver<u64>,
46+
channel::Receiver<(u64, anyhow::Result<Vec<(InputSource, RrdManifest)>>)>,
4447
) {
4548
read_any_rrd_streams_from_file_or_stdin::<re_log_types::LogMsg>(paths)
4649
}
@@ -55,7 +58,8 @@ pub fn read_rrd_streams_from_file_or_stdin(
5558
/// * The first channel contains both the successfully decoded data, if any, as well as any
5659
/// errors faced during processing.
5760
/// * The second channel, which will fire only once, after all processing is done, indicates the
58-
/// total number of bytes processed.
61+
/// total number of bytes processed, and returns all RRD manifests that were parsed from footers
62+
/// in the underlying stream.
5963
///
6064
/// This function is best-effort: it will try to make progress even in the face of errors.
6165
/// It is up to the user to decide whether and when to stop.
@@ -68,25 +72,27 @@ pub fn read_rrd_streams_from_file_or_stdin(
6872
// TODO(ab): For pre-0.25 legacy data with `StoreId` missing their application id, the migration
6973
// in `Decoder` requires `SetStoreInfo` to arrive before the corresponding `ArrowMsg`. Ideally
7074
// this tool would cache orphan `ArrowMsg` until a matching `SetStoreInfo` arrives.
75+
#[expect(clippy::type_complexity)] // internal private API for the CLI impl
7176
pub fn read_raw_rrd_streams_from_file_or_stdin(
7277
paths: &[String],
7378
) -> (
7479
channel::Receiver<(
7580
InputSource,
7681
anyhow::Result<re_protos::log_msg::v1alpha1::log_msg::Msg>,
7782
)>,
78-
channel::Receiver<u64>,
83+
channel::Receiver<(u64, anyhow::Result<Vec<(InputSource, RrdManifest)>>)>,
7984
) {
8085
read_any_rrd_streams_from_file_or_stdin::<re_protos::log_msg::v1alpha1::log_msg::Msg>(paths)
8186
}
8287

88+
#[expect(clippy::type_complexity)] // internal private API for the CLI impl
8389
fn read_any_rrd_streams_from_file_or_stdin<
8490
T: re_log_encoding::DecoderEntrypoint + Send + 'static,
8591
>(
8692
paths: &[String],
8793
) -> (
8894
channel::Receiver<(InputSource, anyhow::Result<T>)>,
89-
channel::Receiver<u64>,
95+
channel::Receiver<(u64, anyhow::Result<Vec<(InputSource, RrdManifest)>>)>,
9096
) {
9197
let path_to_input_rrds = paths
9298
.iter()
@@ -95,26 +101,32 @@ fn read_any_rrd_streams_from_file_or_stdin<
95101
.collect_vec();
96102

97103
// TODO(cmc): might want to make this configurable at some point.
98-
let (tx, rx) = crossbeam::channel::bounded(100);
99-
let (tx_size_bytes, rx_size_bytes) = crossbeam::channel::bounded(1);
104+
let (tx_msgs, rx_msgs) = crossbeam::channel::bounded(100);
105+
let (tx_metadata, rx_metadata) = crossbeam::channel::bounded(1);
100106

101107
_ = std::thread::Builder::new()
102108
.name("rerun-rrd-in".to_owned())
103109
.spawn(move || {
110+
let mut rrd_manifests = Ok(Vec::new());
104111
let mut size_bytes = 0;
105112

106113
if path_to_input_rrds.is_empty() {
107114
// stdin
108115

116+
let source = InputSource::Stdin;
109117
let stdin = std::io::BufReader::new(std::io::stdin().lock());
110118
let mut decoder = re_log_encoding::Decoder::decode_lazy(stdin);
111119

112120
for res in &mut decoder {
113121
let res = res.context("couldn't decode message from stdin -- skipping");
114-
tx.send((InputSource::Stdin, res)).ok();
122+
tx_msgs.send((source.clone(), res)).ok();
115123
}
116124

117125
size_bytes += decoder.num_bytes_processed();
126+
rrd_manifests = decoder
127+
.rrd_manifests()
128+
.context("couldn't decode footers")
129+
.map(|manifests| manifests.into_iter().map(|m| (source.clone(), m)).collect());
118130
} else {
119131
// file(s)
120132

@@ -124,28 +136,35 @@ fn read_any_rrd_streams_from_file_or_stdin<
124136
{
125137
Ok(file) => file,
126138
Err(err) => {
127-
tx.send((InputSource::File(rrd_path.clone()), Err(err)))
139+
tx_msgs
140+
.send((InputSource::File(rrd_path.clone()), Err(err)))
128141
.ok();
129142
continue;
130143
}
131144
};
132145

146+
let source = InputSource::File(rrd_path.clone());
133147
let rrd_file = std::io::BufReader::new(rrd_file);
134-
let mut messages = re_log_encoding::Decoder::decode_lazy(rrd_file);
135-
136-
for res in &mut messages {
148+
let mut decoder = re_log_encoding::Decoder::decode_lazy(rrd_file);
149+
for res in &mut decoder {
137150
let res = res.context("decode rrd message").with_context(|| {
138151
format!("couldn't decode message {rrd_path:?} -- skipping")
139152
});
140-
tx.send((InputSource::File(rrd_path.clone()), res)).ok();
153+
tx_msgs.send((source.clone(), res)).ok();
141154
}
142155

143-
size_bytes += messages.num_bytes_processed();
156+
size_bytes += decoder.num_bytes_processed();
157+
rrd_manifests = decoder
158+
.rrd_manifests()
159+
.context("couldn't decode footers")
160+
.map(|manifests| {
161+
manifests.into_iter().map(|m| (source.clone(), m)).collect()
162+
});
144163
}
145164
}
146165

147-
tx_size_bytes.send(size_bytes).ok();
166+
tx_metadata.send((size_bytes, rrd_manifests)).ok();
148167
});
149168

150-
(rx, rx_size_bytes)
169+
(rx_msgs, rx_metadata)
151170
}

docs/content/reference/cli.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -537,12 +537,15 @@ Example: `rerun rrd print /my/recordings/*.rrd`
537537
* `--full-metadata <FULL_METADATA>`
538538
> If true, includes `rerun.` prefixes on keys.
539539
540-
* `--transposed <TRANSPOSED>`
541-
> Transpose record batches before printing them?
542-
543540
* `--entity <ENTITY>`
544541
> Show only chunks belonging to this entity.
545542
543+
* `--footers <FOOTERS>`
544+
> If true, displays all the parsed footers at the end.
545+
546+
* `--transposed <TRANSPOSED>`
547+
> Transpose record batches before printing them?
548+
546549
## rerun rrd route
547550

548551
Manipulates the metadata of log message streams without decoding the payloads.
@@ -576,6 +579,13 @@ Note: Because the payload of the messages is never decoded, no migration or veri
576579
>
577580
> When this flag is set and multiple input .rdd files are specified, blueprint activation commands will be dropped from the resulting output.
578581
582+
* `--recompute-manifests <RECOMPUTE_MANIFESTS>`
583+
> If set, this will compute an RRD footer with the appropriate manifest for the routed data.
584+
>
585+
> By default, `rerun rrd route` will always drop all existing RRD manifests when routing data, as doing so invalidates their contents. This flag makes it possible to recompute an RRD manifest for the routed data, but beware that it has to decode the data, which means it is A) much slower and B) will migrate the data to the latest Sorbet specification automatically.
586+
>
587+
> [Default: `false`]
588+
579589
## rerun rrd stats
580590

581591
Compute important statistics for one or more .rrd/.rbl files/streams.

0 commit comments

Comments
 (0)