| // Copyright 2026 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| //! Printing of 'dump' style information at different depths. |
| |
| use core::fmt::{self, Write}; |
| |
| /// A [`core::fmt::Write`] implementation that writes to the kernel console. |
| #[derive(Clone, Copy, Debug, Default)] |
| pub struct ConsoleSink; |
| |
| impl Write for ConsoleSink { |
| fn write_str(&mut self, s: &str) -> fmt::Result { |
| kprint::kprint!("{:s}", s); |
| Ok(()) |
| } |
| } |
| |
| /// Formats a single item and emits it to a [`DepthPrinter`]. |
| /// |
| /// This wraps [`DepthPrinter::emit`], building the [`core::fmt::Arguments`] from the given format |
| /// string and arguments. |
| /// |
| /// ```ignore |
| /// dump::emit!(printer, "physical_page_provider {:p} phys_base_ 0x{:x}", self_ptr, phys_base); |
| /// ``` |
| #[macro_export] |
| macro_rules! emit { |
| ($printer:expr, $($args:tt)*) => { |
| $printer.emit(core::format_args!($($args)*)) |
| }; |
| } |
| |
| /// Helper for printing 'dump' style information at different depths (i.e. with space prefixes). |
| /// |
| /// This type is not thread safe. |
| pub struct DepthPrinter<W: Write> { |
| writer: W, |
| depth: usize, |
| list_max: usize, |
| list_emitted: usize, |
| in_list: bool, |
| } |
| |
| impl<W: Write> DepthPrinter<W> { |
| /// Creates a printer emitting items at `depth` to `writer`. |
| pub fn new(writer: W, depth: usize) -> Self { |
| Self { writer, depth, list_max: 0, list_emitted: 0, in_list: false } |
| } |
| |
| /// Emit a single item. It will have spaces prefixed based on the depth and a newline added |
| /// to the end. |
| /// |
| /// Use the [`emit!`] macro to construct the [`core::fmt::Arguments`] and emit an item in one |
| /// step. |
| pub fn emit(&mut self, args: fmt::Arguments<'_>) { |
| if self.in_list { |
| self.list_emitted += 1; |
| if self.list_emitted > self.list_max { |
| return; |
| } |
| } |
| self.print_depth(); |
| let _ = self.writer.write_fmt(args); |
| let _ = self.writer.write_str("\n"); |
| } |
| |
| /// Indicates a list is about to be emitted. Only at most `max` emit calls will result in |
| /// output, with any additional being counted. [`DepthPrinter::end_list`] must be called once |
| /// finished emitting the list. |
| pub fn begin_list(&mut self, max: usize) { |
| debug_assert!(!self.in_list); |
| self.list_max = max; |
| self.list_emitted = 0; |
| self.in_list = true; |
| } |
| |
| /// Indicates that printing of the list is finished and, if relevant, will emit a message |
| /// indicating how many items were skipped. |
| pub fn end_list(&mut self) { |
| debug_assert!(self.in_list); |
| self.in_list = false; |
| if self.list_emitted > self.list_max { |
| let not_emitted = self.list_emitted - self.list_max; |
| crate::emit!(self, "[{} items not emitted]", not_emitted); |
| } |
| } |
| |
| fn print_depth(&mut self) { |
| for _ in 0..self.depth { |
| let _ = self.writer.write_str(" "); |
| } |
| } |
| } |
| |
| impl DepthPrinter<ConsoleSink> { |
| /// Creates a printer emitting items at `depth` to the kernel console. |
| pub fn console(depth: usize) -> Self { |
| Self::new(ConsoleSink, depth) |
| } |
| } |
| |
| impl<W: Write> Drop for DepthPrinter<W> { |
| fn drop(&mut self) { |
| debug_assert!(!self.in_list); |
| } |
| } |
| |
| /// Test suite for the Rust `DepthPrinter` implementation. |
| #[cfg(ktest)] |
| #[unittest::suite(name = "depth_printer_rust")] |
| mod tests { |
| use crate::DepthPrinter; |
| use core::cell::RefCell; |
| use core::fmt; |
| use fbl::StringBuffer; |
| use unittest::{expect_eq, expect_true}; |
| |
| /// A [`core::fmt::Write`] sink accumulating everything written to it in a [`StringBuffer`], |
| /// which can be shared by multiple [`DepthPrinter`]s. |
| struct StringSink { |
| buffer: RefCell<StringBuffer<2048>>, |
| } |
| |
| impl StringSink { |
| fn new() -> Self { |
| Self { buffer: RefCell::new(StringBuffer::new()) } |
| } |
| } |
| |
| impl fmt::Write for &StringSink { |
| fn write_str(&mut self, s: &str) -> fmt::Result { |
| self.buffer.borrow_mut().write_str(s) |
| } |
| } |
| |
| /// Tests that items are emitted at their depth and that lists elide their excess items. |
| #[test] |
| fn depth_printer_smoke_test() { |
| const EXPECTED: &[u8] = concat!( |
| "Hello\n", |
| " World\n", |
| "One\n", |
| "Two\n", |
| "[2 items not emitted]\n", |
| " One\n", |
| " Two\n", |
| " Goodbye\n", |
| "foo bar baz\n", |
| ) |
| .as_bytes(); |
| |
| let buffer = StringSink::new(); |
| |
| let mut no_depth = DepthPrinter::new(&buffer, 0); |
| let mut depth = DepthPrinter::new(&buffer, 4); |
| |
| emit!(no_depth, "Hello"); |
| emit!(depth, "World"); |
| no_depth.begin_list(2); |
| emit!(no_depth, "One"); |
| emit!(no_depth, "Two"); |
| emit!(no_depth, "Three"); |
| emit!(no_depth, "Four"); |
| no_depth.end_list(); |
| depth.begin_list(4); |
| emit!(depth, "One"); |
| emit!(depth, "Two"); |
| depth.end_list(); |
| emit!(depth, "Goodbye"); |
| emit!(no_depth, "foo {} baz", "bar"); |
| |
| let output = buffer.buffer.borrow(); |
| expect_eq!(EXPECTED.len(), output.len()); |
| expect_true!(EXPECTED == &output[..]); |
| } |
| } |