Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions vortex-array/public-api.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9938,12 +9938,98 @@ impl core::default::Default for vortex_array::display::DisplayOptions

pub fn vortex_array::display::DisplayOptions::default() -> Self

pub struct vortex_array::display::BufferExtractor

pub vortex_array::display::BufferExtractor::show_percent: bool

impl vortex_array::display::TreeExtractor for vortex_array::display::BufferExtractor

pub fn vortex_array::display::BufferExtractor::detail_lines(&self, array: &dyn vortex_array::DynArray, _ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub fn vortex_array::display::BufferExtractor::header_annotations(&self, array: &dyn vortex_array::DynArray, ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub struct vortex_array::display::DisplayArrayAs<'a>(pub &'a dyn vortex_array::DynArray, pub vortex_array::display::DisplayOptions)

impl core::fmt::Display for vortex_array::display::DisplayArrayAs<'_>

pub fn vortex_array::display::DisplayArrayAs<'_>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result

pub struct vortex_array::display::MetadataExtractor

impl vortex_array::display::TreeExtractor for vortex_array::display::MetadataExtractor

pub fn vortex_array::display::MetadataExtractor::detail_lines(&self, array: &dyn vortex_array::DynArray, _ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub fn vortex_array::display::MetadataExtractor::header_annotations(&self, array: &dyn vortex_array::DynArray, ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub struct vortex_array::display::NbytesExtractor

impl vortex_array::display::TreeExtractor for vortex_array::display::NbytesExtractor

pub fn vortex_array::display::NbytesExtractor::detail_lines(&self, array: &dyn vortex_array::DynArray, ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub fn vortex_array::display::NbytesExtractor::header_annotations(&self, array: &dyn vortex_array::DynArray, ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub struct vortex_array::display::StatsExtractor

impl vortex_array::display::TreeExtractor for vortex_array::display::StatsExtractor

pub fn vortex_array::display::StatsExtractor::detail_lines(&self, array: &dyn vortex_array::DynArray, ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub fn vortex_array::display::StatsExtractor::header_annotations(&self, array: &dyn vortex_array::DynArray, _ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub struct vortex_array::display::TreeContext

impl vortex_array::display::TreeContext

pub fn vortex_array::display::TreeContext::parent_total_size(&self) -> core::option::Option<u64>

pub struct vortex_array::display::TreeDisplay

impl vortex_array::display::TreeDisplay

pub fn vortex_array::display::TreeDisplay::default_display(array: vortex_array::ArrayRef) -> Self

pub fn vortex_array::display::TreeDisplay::new(array: vortex_array::ArrayRef) -> Self

pub fn vortex_array::display::TreeDisplay::with<E: vortex_array::display::TreeExtractor + 'static>(self, extractor: E) -> Self

pub fn vortex_array::display::TreeDisplay::with_boxed(self, extractor: alloc::boxed::Box<dyn vortex_array::display::TreeExtractor>) -> Self

impl core::fmt::Display for vortex_array::display::TreeDisplay

pub fn vortex_array::display::TreeDisplay::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result

pub trait vortex_array::display::TreeExtractor: core::marker::Send + core::marker::Sync

pub fn vortex_array::display::TreeExtractor::detail_lines(&self, array: &dyn vortex_array::DynArray, ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub fn vortex_array::display::TreeExtractor::header_annotations(&self, array: &dyn vortex_array::DynArray, ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

impl vortex_array::display::TreeExtractor for vortex_array::display::BufferExtractor

pub fn vortex_array::display::BufferExtractor::detail_lines(&self, array: &dyn vortex_array::DynArray, _ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub fn vortex_array::display::BufferExtractor::header_annotations(&self, array: &dyn vortex_array::DynArray, ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

impl vortex_array::display::TreeExtractor for vortex_array::display::MetadataExtractor

pub fn vortex_array::display::MetadataExtractor::detail_lines(&self, array: &dyn vortex_array::DynArray, _ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub fn vortex_array::display::MetadataExtractor::header_annotations(&self, array: &dyn vortex_array::DynArray, ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

impl vortex_array::display::TreeExtractor for vortex_array::display::NbytesExtractor

pub fn vortex_array::display::NbytesExtractor::detail_lines(&self, array: &dyn vortex_array::DynArray, ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub fn vortex_array::display::NbytesExtractor::header_annotations(&self, array: &dyn vortex_array::DynArray, ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

impl vortex_array::display::TreeExtractor for vortex_array::display::StatsExtractor

pub fn vortex_array::display::StatsExtractor::detail_lines(&self, array: &dyn vortex_array::DynArray, ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub fn vortex_array::display::StatsExtractor::header_annotations(&self, array: &dyn vortex_array::DynArray, _ctx: &vortex_array::display::TreeContext) -> alloc::vec::Vec<alloc::string::String>

pub mod vortex_array::dtype

pub use vortex_array::dtype::half
Expand Down
54 changes: 54 additions & 0 deletions vortex-array/src/display/extractor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

use crate::DynArray;

/// Context threaded through tree traversal for percentage calculations etc.
pub struct TreeContext {
/// Stack of ancestor nbytes values. `None` entries reset the percentage root
/// (e.g. for chunked arrays where each chunk is its own root).
pub(crate) ancestor_sizes: Vec<Option<u64>>,
}

impl TreeContext {
pub(crate) fn new() -> Self {
Self {
ancestor_sizes: Vec::new(),
}
}

/// The total size used as the denominator for percentage calculations.
/// Returns `None` if there is no ancestor (i.e., this node is the root or
/// a chunk boundary reset the percentage root).
pub fn parent_total_size(&self) -> Option<u64> {
self.ancestor_sizes.last().cloned().flatten()
}

pub(crate) fn push(&mut self, size: Option<u64>) {
self.ancestor_sizes.push(size);
}

pub(crate) fn pop(&mut self) {
self.ancestor_sizes.pop();
}
}

/// Trait for contributing display information to tree nodes.
///
/// Each extractor represents one "dimension" of display (e.g., nbytes, stats, metadata, buffers).
/// Extractors are composable: you can combine any number of them via [`TreeDisplay::with`].
///
/// [`TreeDisplay::with`]: super::TreeDisplay::with
pub trait TreeExtractor: Send + Sync {
/// Annotations appended to the header line (e.g., `nbytes=10 B (100.00%)`).
fn header_annotations(&self, array: &dyn DynArray, ctx: &TreeContext) -> Vec<String> {
let _ = (array, ctx);
vec![]
}

/// Additional detail lines shown below the header (e.g., `metadata: EmptyMetadata`).
fn detail_lines(&self, array: &dyn DynArray, ctx: &TreeContext) -> Vec<String> {
let _ = (array, ctx);
vec![]
}
}
219 changes: 219 additions & 0 deletions vortex-array/src/display/extractors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

use std::fmt::Write;
use std::fmt::{self};

use humansize::DECIMAL;
use humansize::format_size;

use crate::DynArray;
use crate::display::extractor::TreeContext;
use crate::display::extractor::TreeExtractor;
use crate::expr::stats::Stat;
use crate::expr::stats::StatsProvider;

/// Display wrapper for array statistics in compact format.
///
/// Produces output like ` [nulls=3, min=5, max=100]` (with leading space).
pub(crate) struct StatsDisplay<'a>(pub(crate) &'a dyn DynArray);

impl fmt::Display for StatsDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let stats = self.0.statistics();
let mut first = true;

let mut sep = |f: &mut fmt::Formatter<'_>| -> fmt::Result {
if first {
first = false;
f.write_str(" [")
} else {
f.write_str(", ")
}
};

// Null count or validity fallback
if let Some(nc) = stats.get(Stat::NullCount) {
if let Ok(n) = usize::try_from(&nc.clone().into_inner()) {
sep(f)?;
write!(f, "nulls={}", n)?;
} else {
sep(f)?;
write!(f, "nulls={}", nc)?;
}
} else if self.0.dtype().is_nullable() {
match self.0.all_valid() {
Ok(true) => {
sep(f)?;
f.write_str("all_valid")?;
}
Ok(false) => {
if self.0.all_invalid().unwrap_or(false) {
sep(f)?;
f.write_str("all_invalid")?;
}
}
Err(e) => {
tracing::warn!("Failed to check validity: {e}");
sep(f)?;
f.write_str("validity_failed")?;
}
}
}

// NaN count (only if > 0)
if let Some(nan) = stats.get(Stat::NaNCount)
&& let Ok(n) = usize::try_from(&nan.into_inner())
&& n > 0
{
sep(f)?;
write!(f, "nan={}", n)?;
}

// Min/Max
if let Some(min) = stats.get(Stat::Min) {
sep(f)?;
write!(f, "min={}", min)?;
}
if let Some(max) = stats.get(Stat::Max) {
sep(f)?;
write!(f, "max={}", max)?;
}

// Sum
if let Some(sum) = stats.get(Stat::Sum) {
sep(f)?;
write!(f, "sum={}", sum)?;
}

// Boolean flags (compact)
if let Some(c) = stats.get(Stat::IsConstant)
&& bool::try_from(&c.into_inner()).unwrap_or(false)
{
sep(f)?;
f.write_str("const")?;
}
if let Some(s) = stats.get(Stat::IsStrictSorted) {
if bool::try_from(&s.into_inner()).unwrap_or(false) {
sep(f)?;
f.write_str("strict")?;
}
} else if let Some(s) = stats.get(Stat::IsSorted)
&& bool::try_from(&s.into_inner()).unwrap_or(false)
{
sep(f)?;
f.write_str("sorted")?;
}

// Close bracket if we wrote anything
if !first {
f.write_char(']')?;
}

Ok(())
}
}

/// Extractor that adds `nbytes=X (Y%)` to the header line.
pub struct NbytesExtractor;

impl TreeExtractor for NbytesExtractor {
fn header_annotations(&self, array: &dyn DynArray, ctx: &TreeContext) -> Vec<String> {
let nbytes = array.nbytes();
let total_size = ctx.parent_total_size().unwrap_or(nbytes);
let percent = if total_size == 0 {
0.0
} else {
100_f64 * nbytes as f64 / total_size as f64
};
vec![format!(
"nbytes={} ({:.2}%)",
format_size(nbytes, DECIMAL),
percent
)]
}
}

/// Extractor that adds stats annotations (e.g. `[nulls=3, min=5]`) to the header line.
pub struct StatsExtractor;

impl TreeExtractor for StatsExtractor {
fn header_annotations(&self, array: &dyn DynArray, _ctx: &TreeContext) -> Vec<String> {
let s = StatsDisplay(array).to_string();
let trimmed = s.trim_start();
if trimmed.is_empty() {
vec![]
} else {
vec![trimmed.to_string()]
}
}
}

/// Extractor that adds a `metadata: ...` detail line.
pub struct MetadataExtractor;

impl TreeExtractor for MetadataExtractor {
fn detail_lines(&self, array: &dyn DynArray, _ctx: &TreeContext) -> Vec<String> {
// Capture the metadata_fmt output
let mut buf = String::new();
// metadata_fmt writes directly to a Formatter, so we use a helper wrapper
struct FmtCapture<'a>(&'a dyn DynArray);
impl fmt::Display for FmtCapture<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.metadata_fmt(f)
}
}
let _ = write!(&mut buf, "{}", FmtCapture(array));
vec![format!("metadata: {buf}")]
}
}

/// Extractor that adds buffer detail lines.
pub struct BufferExtractor {
/// Whether to show buffer-level percentage of parent nbytes.
pub show_percent: bool,
}

impl TreeExtractor for BufferExtractor {
fn detail_lines(&self, array: &dyn DynArray, _ctx: &TreeContext) -> Vec<String> {
let nbytes = array.nbytes();
let mut lines = Vec::new();
for (name, buffer) in array.named_buffers() {
let loc = if buffer.is_on_device() {
"device"
} else if buffer.is_on_host() {
"host"
} else {
"location-unknown"
};
let align = if buffer.is_on_host() {
buffer.as_host().alignment().to_string()
} else {
String::new()
};

if self.show_percent {
let buffer_percent = if nbytes == 0 {
0.0
} else {
100_f64 * buffer.len() as f64 / nbytes as f64
};
lines.push(format!(
"buffer: {} {loc} {} (align={}) ({:.2}%)",
name,
format_size(buffer.len(), DECIMAL),
align,
buffer_percent,
));
} else {
lines.push(format!(
"buffer: {} {loc} {} (align={})",
name,
format_size(buffer.len(), DECIMAL),
align,
));
}
}
lines
}
}
Loading
Loading