Skip to content

Commit 9fa01f2

Browse files
authored
Add back segment paths to the inspect command (#4600)
Signed-off-by: Nicholas Gates <nick@nickgates.com>
1 parent 15d19fe commit 9fa01f2

3 files changed

Lines changed: 123 additions & 61 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vortex-tui/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ flatbuffers = { workspace = true }
2222
futures = { workspace = true, features = ["executor"] }
2323
humansize = { workspace = true }
2424
indicatif = { workspace = true, features = ["futures"] }
25+
itertools = { workspace = true }
2526
parquet = { workspace = true, features = ["arrow", "async"] }
2627
ratatui = { workspace = true }
2728
taffy = { workspace = true }

vortex-tui/src/inspect.rs

Lines changed: 121 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
// SPDX-License-Identifier: Apache-2.0
22
// SPDX-FileCopyrightText: Copyright the Vortex contributors
33

4+
use std::collections::VecDeque;
45
use std::fs::File;
56
use std::io::{Read, Seek, SeekFrom};
67
use std::path::PathBuf;
8+
use std::sync::Arc;
79

810
use flatbuffers::root;
11+
use itertools::Itertools;
912
use vortex::buffer::{Alignment, ByteBuffer};
10-
use vortex::error::{VortexResult, vortex_bail, vortex_err};
11-
use vortex::file::{EOF_SIZE, MAGIC_BYTES, MAX_FOOTER_SIZE, VERSION};
13+
use vortex::error::{VortexExpect, VortexResult, vortex_bail, vortex_err};
14+
use vortex::file::{EOF_SIZE, Footer, MAGIC_BYTES, MAX_FOOTER_SIZE, VERSION, VortexOpenOptions};
1215
use vortex::flatbuffers::footer as fb;
16+
use vortex::layout::LayoutRef;
1317

1418
#[derive(Debug, clap::Parser)]
1519
pub struct InspectArgs {
@@ -75,19 +79,18 @@ pub fn exec_inspect(args: InspectArgs) -> anyhow::Result<()> {
7579
return Ok(());
7680
}
7781

78-
let postscript = match inspector.read_postscript(eof.postscript_size) {
82+
match inspector.read_postscript(eof.postscript_size) {
7983
Ok(ps) => {
8084
ps.display();
81-
ps
8285
}
8386
Err(e) => {
8487
eprintln!("\nError reading postscript: {}", e);
8588
return Ok(());
8689
}
8790
};
8891

89-
match inspector.read_footer_segments(&postscript) {
90-
Ok(footer) => footer.display(),
92+
match inspector.read_footer() {
93+
Ok(footer) => FooterSegments(footer).display(),
9194
Err(e) => {
9295
eprintln!("\nError reading footer segments: {}", e);
9396
}
@@ -99,6 +102,7 @@ pub fn exec_inspect(args: InspectArgs) -> anyhow::Result<()> {
99102
}
100103

101104
struct VortexInspector {
105+
path: PathBuf,
102106
file: File,
103107
file_size: u64,
104108
}
@@ -112,7 +116,11 @@ impl VortexInspector {
112116
.seek(SeekFrom::End(0))
113117
.map_err(|e| vortex_err!("Failed to get file size: {}", e))?;
114118

115-
Ok(Self { file, file_size })
119+
Ok(Self {
120+
path,
121+
file,
122+
file_size,
123+
})
116124
}
117125

118126
fn read_eof(&mut self) -> VortexResult<EofInfo> {
@@ -197,45 +205,11 @@ impl VortexInspector {
197205
})
198206
}
199207

200-
fn read_footer_segments(
201-
&mut self,
202-
postscript: &PostscriptInfo,
203-
) -> VortexResult<FooterSegments> {
204-
// Read footer segment
205-
206-
let mut footer_bytes = vec![0u8; postscript.footer.length as usize];
207-
self.file
208-
.seek(SeekFrom::Start(postscript.footer.offset))
209-
.map_err(|e| vortex_err!("Failed to seek to footer: {}", e))?;
210-
self.file
211-
.read_exact(&mut footer_bytes)
212-
.map_err(|e| vortex_err!("Failed to read footer: {}", e))?;
213-
214-
let footer_buffer = ByteBuffer::from(footer_bytes);
215-
let fb_footer = root::<fb::Footer>(&footer_buffer)
216-
.map_err(|e| vortex_err!("Failed to parse footer flatbuffer: {}", e))?;
217-
218-
let segment_count = fb_footer
219-
.segment_specs()
220-
.map(|segs| segs.len())
221-
.unwrap_or(0);
222-
223-
let mut segments = Vec::new();
224-
if let Some(fb_segments) = fb_footer.segment_specs() {
225-
for seg in fb_segments {
226-
segments.push(SegmentInfo {
227-
offset: seg.offset(),
228-
length: seg.length(),
229-
alignment: Alignment::from_exponent(seg.alignment_exponent()),
230-
});
231-
}
232-
}
233-
234-
Ok(FooterSegments {
235-
segment_count,
236-
total_data_size: segments.iter().map(|s| s.length as u64).sum(),
237-
segments,
238-
})
208+
fn read_footer(&mut self) -> VortexResult<Footer> {
209+
Ok(VortexOpenOptions::file()
210+
.open_blocking(&self.path)?
211+
.footer()
212+
.clone())
239213
}
240214
}
241215

@@ -263,11 +237,7 @@ struct PostscriptInfo {
263237
}
264238

265239
#[derive(Debug)]
266-
struct FooterSegments {
267-
segment_count: usize,
268-
segments: Vec<SegmentInfo>,
269-
total_data_size: u64,
270-
}
240+
struct FooterSegments(Footer);
271241

272242
impl EofInfo {
273243
fn display(&self) {
@@ -319,17 +289,107 @@ impl PostscriptInfo {
319289
impl FooterSegments {
320290
fn display(&self) {
321291
println!("\n=== Footer Segments ===");
322-
println!("Total segments: {}", self.segment_count);
323-
println!("Total data size: {} bytes", self.total_data_size);
324-
325-
if !self.segments.is_empty() {
326-
println!("\nSegment details:");
327-
for (i, segment) in self.segments.iter().enumerate() {
328-
println!(
329-
" [{}] offset={}, length={}, alignment={}",
330-
i, segment.offset, segment.length, segment.alignment
331-
);
292+
println!("Total segments: {}", self.0.segment_map().len());
293+
let total_size = self
294+
.0
295+
.segment_map()
296+
.iter()
297+
.map(|s| s.length as u64)
298+
.sum::<u64>();
299+
println!("Total data size: {} bytes", total_size);
300+
301+
println!("\nSegment details:\n");
302+
303+
let segment_map = self.0.segment_map().clone();
304+
if segment_map.is_empty() {
305+
println!("<no segments>");
306+
return;
307+
}
308+
309+
let mut segment_paths: Vec<Option<Vec<Arc<str>>>> = vec![None; segment_map.len()];
310+
let root_layout = self.0.layout().clone();
311+
312+
let mut queue =
313+
VecDeque::<(Vec<Arc<str>>, LayoutRef)>::from_iter([(Vec::new(), root_layout)]);
314+
while !queue.is_empty() {
315+
let (path, layout) = queue.pop_front().vortex_expect("queue is not empty");
316+
for segment in layout.segment_ids() {
317+
segment_paths[*segment as usize] = Some(path.clone());
332318
}
319+
320+
for (child_layout, child_name) in layout
321+
.children()
322+
.vortex_expect("Failed to deserialize children")
323+
.into_iter()
324+
.zip(layout.child_names())
325+
{
326+
let child_path = path.iter().cloned().chain([child_name]).collect();
327+
queue.push_back((child_path, child_layout));
328+
}
329+
}
330+
331+
// Find the largest values for formatting
332+
let max_offset = segment_map.last().vortex_expect("non-empty").offset;
333+
let max_length = segment_map
334+
.iter()
335+
.map(|s| s.length)
336+
.max()
337+
.vortex_expect("non-empty");
338+
let max_alignment = segment_map
339+
.iter()
340+
.map(|s| s.alignment)
341+
.max()
342+
.vortex_expect("non-empty");
343+
344+
// Calculate all widths
345+
let offset_width = max_offset.to_string().len();
346+
let end_width = (max_offset + max_length as u64).to_string().len();
347+
let length_width = max_length.to_string().len().max(6);
348+
let alignment_width = max_alignment.to_string().len().max(5);
349+
let index_width = segment_paths.len().to_string().len();
350+
351+
// Print header
352+
println!(
353+
"{:>index_w$} {:>offset_w$}..{:<end_w$} {:>length_w$} {:>align_w$} Path",
354+
"#",
355+
"Start",
356+
"End",
357+
"Length",
358+
"Align",
359+
index_w = index_width,
360+
offset_w = offset_width,
361+
end_w = end_width,
362+
length_w = length_width,
363+
align_w = alignment_width,
364+
);
365+
366+
for (i, name) in segment_paths.iter().enumerate() {
367+
let segment = &segment_map[i];
368+
let end_offset = segment.offset + segment.length as u64;
369+
370+
print!(
371+
"{:>index_w$} {:>offset_w$}..{:<end_w$} ",
372+
i,
373+
segment.offset,
374+
end_offset,
375+
index_w = index_width,
376+
offset_w = offset_width,
377+
end_w = end_width,
378+
);
379+
print!(
380+
"{:>length_w$} {:>align_w$} ",
381+
segment.length,
382+
*segment.alignment,
383+
length_w = length_width,
384+
align_w = alignment_width,
385+
);
386+
println!(
387+
"{}",
388+
match name.as_ref() {
389+
Some(path) => format!("{}", path.iter().format(".")),
390+
None => "<missing>".to_string(),
391+
}
392+
);
333393
}
334394
}
335395
}

0 commit comments

Comments
 (0)