Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add set_print, to send libbpf output to a callback #137

Merged
merged 1 commit into from
Nov 15, 2021

Conversation

benfogle
Copy link
Contributor

I want to be able to send output from libbpf-rs to the log crate and filter it by level, along with all the other chatter my application produces. Right now the best I can do is send it to stderr. This PR adds set_print, which allows a user-defined callback to receive libbpf output.

@mimullin-bbry
Copy link
Contributor

I was just thinking that something like this was needed. :)

pub fn set_print(func: PrintCallback) {
let cb = unsafe { std::mem::transmute::<PrintCallback, *mut ()>(func) };
PRINT_CB.store(cb, Ordering::Relaxed);
unsafe { libbpf_sys::libbpf_set_print(Some(outer_print_cb)) };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

libbpf_set_print returns the previous instance of print callback. It's quite important and useful when someone wants to temporarily replace print callback and restore the previous one. Can the same pattern be support in Rust?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can rework this to include that. I was a little unsure about how to handle return values. The return from libbpf_set_print would be a nullable, unsafe function pointer, so at best it could be a token to restore things later. Since in my implementation, the real callback is a static function, the libbpf_set_print's return value alone wouldn't be sufficient to restore the print callback in a way the user expects.

Would something like a resource guard be acceptable here, or do you think there is a real need to expose the return value?

{
    let _lock = libbpf_rs::set_print_temporary(callback);
    ...
}
// Original callback restored here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resource guard seems like an overkill. We can make set_print() not return anything in libbpf-rs. Alternatively you can return PrintCallback and just keep track of current callback on purely Rust side. It might be a bit fragile if someone is calling into libbpf_set_print() through FFI, but that should be extremely rare (an we can say unsupported) case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All right, that should be updated.

Copy link
Collaborator

@insearchoflosttime insearchoflosttime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this!!

// pointer.
let cb = PRINT_CB.load(Ordering::Relaxed);
let cb = unsafe { std::mem::transmute::<*const (), PrintCallback>(cb) };
let cb = from_ptr(PRINT_CB.load(Ordering::Relaxed));
match unsafe { vsprintf::vsprintf(fmtstr, va_list) } {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some callbacks may filter by log level (eg benchmark tests may discard all debug logs). In those cases, it'd be great to just return early here instead of calling vsprintf and calling into the cb. Maybe in the set_print API, we could also take in the level to filter by as another parameter, and then in this function here, check whether the message should be discarded or forwarded to the cb based off that filter. What are your thoughts on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. Rather than complicate the API, I changed the paramter from &str to &dyn Display. This way it will be lazily evaluated only when the user needs it.

@benfogle
Copy link
Contributor Author

benfogle commented Nov 4, 2021

I rebased off master to resolve some of the test errors I was seeing.

@@ -23,16 +24,31 @@ impl From<libbpf_sys::libbpf_print_level> for PrintLevel {
}
}

pub type PrintCallback = fn(PrintLevel, &str);
pub type PrintCallback = fn(PrintLevel, &dyn fmt::Display);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the simplest solution is expanding the set_print API to take in the log level to filter by a parameter, and keeping this print callback as a fn(PrintLevel, &str). One additional benefit of this is that users also then no longer have to do an if check themselves in their callback function to filter logs by the log level they want.

Other than this, your commits LGTM!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not going to push back very hard on this, because it's all minor details, but I still don't know about adding a filter parameter:

  1. Should the parameter be a minimum level to show or a maximum level to hide?
  2. Inclusive or exclusive?
  3. Depending on the previous answers, we'll need to introduce a PrintLevel::All or PrintLevel::None that does not exist in libbpf.

I really don't think that requiring the user to write an if-statement is much of a burden, especially because I'm of the opinion that the most common use will be to pass this on to log or syslog or something, which means users will need a match to convert PrintLevel to log::Level, etc. anyway..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reflecting on this, maybe the real solution is to make take a dyn Fn(...) + 'static so that we can pass closures as well as function pointers. That would be a much nicer interface, and there's really no reason I couldn't have done it that way.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to add PrintLevel::All or PrintLevel::None. The way I envision it working is hierarchically - if the filter is set to LIBBPF_WARN, then only LIBBPF_WARN messages should go through (LIBBPF_INFO and LIBBPF_DEBUG messages should be discarded). If the filter is set to LIBBPF_INFO, then LIBBPF_WARN and LIBBPF_INFO go through while LIBBPF_DEBUG messages are discarded. And if the filter is set to LIBBPF_DEBUG messages, then all messages go through.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should this work with restoring a previous callback?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add a getter function that will return to the user the current callback and print level, and correspondingly set_print would return back nothing. If the user has a particular callback + print level they want to restore in the future, then the user is responsible for getting and keeping that around, before the user replaces set_print with a new callback + print level. What are your thoughts on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can implement that. We'll still need a PrintLevel::None if we want to avoid the vsprintf penalty when turning off log messages completely. Or the callback can be an Option.

@anakryiko
Copy link
Member

@benfogle, for the PrintLevel::None you mean if the users doesn't want to get any logs at all, right? I think Option is probably best, it's same as passing NULL to libbpf C api.

@benfogle
Copy link
Contributor Author

benfogle commented Nov 9, 2021

Alright, another try. Changes made:

  • set_print takes an Option<(level, callback)> for a parameter.
  • get_print returns current callback as Option<(level, callback)>.
  • PrintCallback takes a String. (We don't do anything with the string afterwards so we might as well let the user take ownership of it.)
  • Examples in the docstrings.

Copy link
Collaborator

@insearchoflosttime insearchoflosttime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for these changes, these look great. Have a few small questions asked inline.

Copy link
Member

@anakryiko anakryiko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as for the other PR, please squash, provide good commit description and add your Signed-off-by:. Thanks!

@@ -29,30 +29,14 @@ impl ObjectBuilder {
}

/// Option to print debug output to stderr.
///
/// Note: This function interacts poorly with [`set_print`].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's be more specific than "interacts poorly"? Just specify that this will reset whatever callback was set with set_print().

func(level, msg);
}
}
1 // return value is ignored by libbpf
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is ignored, but conventionally 0 means success, so might as well return 0

/// // do things quietly
/// set_print(prev);
/// ```
pub fn get_print() -> Option<(PrintLevel, PrintCallback)> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we have PrintLevel and PrintCallback in tuples together everywhere, we might as well return it from set_print. Nothing wrong with keeping get_print as well.

@benfogle benfogle force-pushed the set-print-func branch 2 times, most recently from d04ee13 to a6b1205 Compare November 12, 2021 14:09
@benfogle
Copy link
Contributor Author

Comments should be addressed. Let me know if the commit message looks good and if the signed-off-by line has all the correct info for this PR.

@insearchoflosttime
Copy link
Collaborator

insearchoflosttime commented Nov 13, 2021

This LGTM. Thanks for making these changes.

One small nit: your signed-off by line should be

Signed-off-by: Ben Fogle

not Andrii's info :) (and for more information on "signed-off-by", https://www.kernel.org/doc/html/latest/process/submitting-patches.html might be helpful). This is something we will make more clear in the contributors' guidelines.

Would you mind amending your signed-off-by?

This commit exposes libbpf_set_print to direct libbpf output to a
user-defined callback.

Signed-off-by: Benjamin Fogle <benfogle@gmail.com>
@benfogle
Copy link
Contributor Author

hahahaha. It's been a long week, I guess. Fixed in both PRs.

@insearchoflosttime insearchoflosttime merged commit 283c0e5 into libbpf:master Nov 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants