Refactor to do all formatting in userspace
willfindlay opened this issue · 10 comments
Per discussion on Discord, we're interested in refactoring aya-log to something more defmt-like, performing all formatting in userspace rather than using ufmt. I'm filing this as a tracking issue so we can discuss progress.
I looked into this a while back. defmt as is isn't usable for us, it does things like interning and compression that complicate the implementation a lot (it requires a linker script and a special ELF layout). What I think we should do is take the defmt macros (it's already a separate crate), and plug in a different implementation for the Format trait, where we require everything be Pod and we just copy_from_slice() into a buffer we send as a perf event like aya-log already does.
What I think we should do is take the defmt macros (it's already a separate crate), and plug in a different implementation for the Format trait, where we require everything be Pod and we just copy_from_slice() into a buffer we send as a perf event like aya-log already does.
Right, I like this approach of reusing what we can and taking inspiration where necessary.
I think we should make a check list of action items so it's easier to split up the work if needed.
I'm trying to dig into defmt sources right now. And I'm starting to thing that what we might need is a different implementation of Logger
:
Since its description mentions that
This trait's methods will be called by the defmt logging macros to transmit the
encoded log data over the wire. The call order is:
- One
acquire()
call to start the log frame.- Multiple
write()
calls, with fragments of the log frame data each.- One
release()
call.The data passed to
write()
is unencoded. Implementations MUST encode it withEncoder
prior to sending it over the wire. The simplest way is foracquire()
to callEncoder::start_frame()
,
write()
to callEncoder::write()
, andrelease()
to callEncoder::end_frame()
.
There is also a more detailed description of the write
method:
We may simply skip encoding in our write
implementation. What's more, it seems like defmt provides an option to skip encoding with raw
encoding format, according to https://defmt.ferrous-systems.com/encoding.html
The question now is how we could skip interning.
Huh, it seems like interning is also used in defmt-macros
macros/src/construct.rs:29:pub(crate) fn interned_string(string: &str, tag: &str, is_log_statement: bool) -> TokenStream2 {
macros/src/construct/symbol.rs:20: /// * `defmt_fmt`, `defmt_str` for interned format strings and string literals.
macros/src/derives/format/codegen.rs:31: let format_tag = construct::interned_string(&format_string, "derived", false);
macros/src/derives/format/codegen/enum_data.rs:14: format_tag: construct::interned_string("!", "derived", false),
macros/src/derives/format/codegen/enum_data.rs:49: let format_tag = construct::interned_string(&format_string, "derived", false);
macros/src/function_like/intern.rs:8: construct::interned_string(&literal.value(), "str", false).into()
macros/src/function_like/log.rs:39: let header = construct::interned_string(&format_string, level.as_str(), true);
macros/src/function_like/println.rs:33: let header = construct::interned_string(&format_string, "println", true);
macros/src/function_like/write.rs:37: let format_tag = construct::interned_string(&format_string, "write", false);
macros/src/items/bitflags.rs:25: let format_tag = construct::interned_string(&format_string, "bitflags", false);
So I'm wondering if it even makes sense to reuse anything from defmt. Maybe we should rather do everything in aya-log and just:
- remove ufmt from aya-log-ebpf, require all supported types to be a Pod and send them into a buffer with
copy_from_slice
with currently existing macros - use any kind of formatter in userspace
So I'm wondering if it even makes sense to reuse anything from defmt. Maybe we should rather do everything in aya-log and just:
* remove ufmt from aya-log-ebpf, require all supported types to be a Pod and send them into a buffer with `copy_from_slice` with currently existing macros * use any kind of formatter in userspace
You'll find out that in order to do that, you need large chunks of defmt-macros (minus the interning) :P
defmt-macros defines a formatting DSL to specify types and display hints, see https://defmt.ferrous-systems.com/macros.html. You'll need something like that in order to know how to encode, decode and format the bytes you send over perf buffers.
To expand a bit since I was a bit dense: when you log something with defmt, it "sends over" [format string][arguments referenced by the format string]
. Then the process that does the formatting (user space in our case), reads the format string, and based on the content (the types and display hints) of the format string it decodes and format the [arguments referenced by the format string]
. That's the part that i think is reusable, together with the proc macros for info!()
error!()
etc.
I'm sorry, but I still can't see how defmt-macros can be reused minus the interning, if you mean using it as an actual crate and importing the macros as they are, it seems to be impossible for me.
For example, with the info!()
macro we start here:
Then we go to function_like::log::expand
, so to those two functions:
Here is a line which tells me that it depends on interned strings:
And the definition (which for me seems to try to get the string from the DWARF):
So I think we will need to modify them to our needs.
Oh sorry I realize I could have been clearer. I meant forking/copying the macro code from defmt-macros, not using it as is
So, to sum it up, what most likely needs to be done is:
- Figuring out what part of code we need from defmt-macros
- just implement it in aya-log 🤷♂️
or...
- implementing
Logger
trait to send data to the ring buffers (for now I started doing that)- probably the best to be done as a separate crate/repo (so far I created https://github.com/vadorovsky/defmt-aya for that)
- firnware/ subdir has several example crates doing that
- defmt-semihosting is probably the easiest example
- fork the main defmt repo (so far I'm doing that)
- defmt-macros is the most important part to reuse, but...
- it relies on interned strings, we have to make that optional
- we might also need to modify/implement the
Format
trait? not sure about that though
- implement the userspace consumer
- something what probe-rs, probe-rs-rtt are doing
- probably as a separate crate/repository
Leaning towards the 1nd option so far.
Tricky problems to solve:
- Our
Formatter
has to output the data toAYA_LOGS
map. It must be done withAYA_LOGS.output(&ctx, &buf, 0)
. But how do we passctx
to Formatter? We cannot pass it to thewrite(bytes: &[u8])
method ofFormatter
trait.
@willfindlay let me know if you would like to work on any of the items, if you see any more work items or if you see anything wrong with what I wrote here (which is very likely, honestly this is how I still feel like when writing this).
Notes (will be edited, mostly for myself 😅 ):
- Converting types to bytes is done here
- in defmt
- in probe-rs