Skip to main content

dump/
depth_printer.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7//! Printing of 'dump' style information at different depths.
8
9use core::fmt::{self, Write};
10
11/// A [`core::fmt::Write`] implementation that writes to the kernel console.
12#[derive(Clone, Copy, Debug, Default)]
13pub struct ConsoleSink;
14
15impl Write for ConsoleSink {
16    fn write_str(&mut self, s: &str) -> fmt::Result {
17        kprint::kprint!("{:s}", s);
18        Ok(())
19    }
20}
21
22/// Formats a single item and emits it to a [`DepthPrinter`].
23///
24/// This wraps [`DepthPrinter::emit`], building the [`core::fmt::Arguments`] from the given format
25/// string and arguments.
26///
27/// ```ignore
28/// dump::emit!(printer, "physical_page_provider {:p} phys_base_ 0x{:x}", self_ptr, phys_base);
29/// ```
30#[macro_export]
31macro_rules! emit {
32    ($printer:expr, $($args:tt)*) => {
33        $printer.emit(core::format_args!($($args)*))
34    };
35}
36
37/// Helper for printing 'dump' style information at different depths (i.e. with space prefixes).
38///
39/// This type is not thread safe.
40pub struct DepthPrinter<W: Write> {
41    writer: W,
42    depth: usize,
43    list_max: usize,
44    list_emitted: usize,
45    in_list: bool,
46}
47
48impl<W: Write> DepthPrinter<W> {
49    /// Creates a printer emitting items at `depth` to `writer`.
50    pub fn new(writer: W, depth: usize) -> Self {
51        Self { writer, depth, list_max: 0, list_emitted: 0, in_list: false }
52    }
53
54    /// Emit a single item. It will have spaces prefixed based on the depth and a newline added
55    /// to the end.
56    ///
57    /// Use the [`emit!`] macro to construct the [`core::fmt::Arguments`] and emit an item in one
58    /// step.
59    pub fn emit(&mut self, args: fmt::Arguments<'_>) {
60        if self.in_list {
61            self.list_emitted += 1;
62            if self.list_emitted > self.list_max {
63                return;
64            }
65        }
66        self.print_depth();
67        let _ = self.writer.write_fmt(args);
68        let _ = self.writer.write_str("\n");
69    }
70
71    /// Indicates a list is about to be emitted. Only at most `max` emit calls will result in
72    /// output, with any additional being counted. [`DepthPrinter::end_list`] must be called once
73    /// finished emitting the list.
74    pub fn begin_list(&mut self, max: usize) {
75        debug_assert!(!self.in_list);
76        self.list_max = max;
77        self.list_emitted = 0;
78        self.in_list = true;
79    }
80
81    /// Indicates that printing of the list is finished and, if relevant, will emit a message
82    /// indicating how many items were skipped.
83    pub fn end_list(&mut self) {
84        debug_assert!(self.in_list);
85        self.in_list = false;
86        if self.list_emitted > self.list_max {
87            let not_emitted = self.list_emitted - self.list_max;
88            crate::emit!(self, "[{} items not emitted]", not_emitted);
89        }
90    }
91
92    fn print_depth(&mut self) {
93        for _ in 0..self.depth {
94            let _ = self.writer.write_str("  ");
95        }
96    }
97}
98
99impl DepthPrinter<ConsoleSink> {
100    /// Creates a printer emitting items at `depth` to the kernel console.
101    pub fn console(depth: usize) -> Self {
102        Self::new(ConsoleSink, depth)
103    }
104}
105
106impl<W: Write> Drop for DepthPrinter<W> {
107    fn drop(&mut self) {
108        debug_assert!(!self.in_list);
109    }
110}
111
112/// Test suite for the Rust `DepthPrinter` implementation.
113#[cfg(ktest)]
114#[unittest::suite(name = "depth_printer_rust")]
115mod tests {
116    use crate::DepthPrinter;
117    use core::cell::RefCell;
118    use core::fmt;
119    use fbl::StringBuffer;
120    use unittest::{expect_eq, expect_true};
121
122    /// A [`core::fmt::Write`] sink accumulating everything written to it in a [`StringBuffer`],
123    /// which can be shared by multiple [`DepthPrinter`]s.
124    struct StringSink {
125        buffer: RefCell<StringBuffer<2048>>,
126    }
127
128    impl StringSink {
129        fn new() -> Self {
130            Self { buffer: RefCell::new(StringBuffer::new()) }
131        }
132    }
133
134    impl fmt::Write for &StringSink {
135        fn write_str(&mut self, s: &str) -> fmt::Result {
136            self.buffer.borrow_mut().write_str(s)
137        }
138    }
139
140    /// Tests that items are emitted at their depth and that lists elide their excess items.
141    #[test]
142    fn depth_printer_smoke_test() {
143        const EXPECTED: &[u8] = concat!(
144            "Hello\n",
145            "        World\n",
146            "One\n",
147            "Two\n",
148            "[2 items not emitted]\n",
149            "        One\n",
150            "        Two\n",
151            "        Goodbye\n",
152            "foo bar baz\n",
153        )
154        .as_bytes();
155
156        let buffer = StringSink::new();
157
158        let mut no_depth = DepthPrinter::new(&buffer, 0);
159        let mut depth = DepthPrinter::new(&buffer, 4);
160
161        emit!(no_depth, "Hello");
162        emit!(depth, "World");
163        no_depth.begin_list(2);
164        emit!(no_depth, "One");
165        emit!(no_depth, "Two");
166        emit!(no_depth, "Three");
167        emit!(no_depth, "Four");
168        no_depth.end_list();
169        depth.begin_list(4);
170        emit!(depth, "One");
171        emit!(depth, "Two");
172        depth.end_list();
173        emit!(depth, "Goodbye");
174        emit!(no_depth, "foo {} baz", "bar");
175
176        let output = buffer.buffer.borrow();
177        expect_eq!(EXPECTED.len(), output.len());
178        expect_true!(EXPECTED == &output[..]);
179    }
180}