Skip to content

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

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

Merged
merged 1 commit into from
Nov 15, 2021
Merged
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
74 changes: 74 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions libbpf-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ nix = "0.22"
num_enum = "0.5"
strum_macros = "0.21"
vsprintf = "2.0"
lazy_static = "1.4"

[dev-dependencies]
libc = "0.2"
plain = "0.2.3"
scopeguard = "1.1"
serial_test = "0.5"
log = "0.4"
2 changes: 2 additions & 0 deletions libbpf-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ mod link;
mod map;
mod object;
mod perf_buffer;
mod print;
mod program;
pub mod query;
mod ringbuf;
Expand All @@ -88,6 +89,7 @@ pub use crate::link::Link;
pub use crate::map::{Map, MapFlags, MapType, OpenMap};
pub use crate::object::{Object, ObjectBuilder, OpenObject};
pub use crate::perf_buffer::{PerfBuffer, PerfBufferBuilder};
pub use crate::print::{get_print, set_print, PrintCallback, PrintLevel};
pub use crate::program::{OpenProgram, Program, ProgramAttachType, ProgramType};
pub use crate::ringbuf::{RingBuffer, RingBufferBuilder};
pub use crate::util::num_possible_cpus;
25 changes: 5 additions & 20 deletions libbpf-rs/src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,15 @@ impl ObjectBuilder {
}

/// Option to print debug output to stderr.
///
/// Note: This function uses [`set_print`] internally and will overwrite any callbacks
/// currently in use.
pub fn debug(&mut self, dbg: bool) -> &mut Self {
extern "C" fn cb(
_level: libbpf_sys::libbpf_print_level,
fmtstr: *const c_char,
va_list: *mut libbpf_sys::__va_list_tag,
) -> i32 {
match unsafe { vsprintf::vsprintf(fmtstr, va_list) } {
Ok(s) => {
print!("{}", s);
0
}
Err(e) => {
eprintln!("Failed to parse libbpf output: {}", e);
1
}
}
}

if dbg {
unsafe { libbpf_sys::libbpf_set_print(Some(cb)) };
set_print(Some((PrintLevel::Debug, |_, s| print!("{}", s))));
} else {
unsafe { libbpf_sys::libbpf_set_print(None) };
set_print(None);
}

self
}

Expand Down
137 changes: 137 additions & 0 deletions libbpf-rs/src/print.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use crate::*;
use lazy_static::lazy_static;
use std::io::{self, Write};
use std::os::raw::c_char;
use std::sync::Mutex;

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[repr(u32)]
pub enum PrintLevel {
Warn = libbpf_sys::LIBBPF_WARN,
Info = libbpf_sys::LIBBPF_INFO,
Debug = libbpf_sys::LIBBPF_DEBUG,
}

impl From<libbpf_sys::libbpf_print_level> for PrintLevel {
fn from(level: libbpf_sys::libbpf_print_level) -> Self {
match level {
libbpf_sys::LIBBPF_WARN => Self::Warn,
libbpf_sys::LIBBPF_INFO => Self::Info,
libbpf_sys::LIBBPF_DEBUG => Self::Debug,
// shouldn't happen, but anything unknown becomes the highest level
_ => Self::Warn,
}
}
}

pub type PrintCallback = fn(PrintLevel, String);

/// Mimic the default print functionality of libbpf. This way if the user calls `get_print` when no
/// previous callback had been set, with the intention of restoring it, everything will behave as
/// expected.
fn default_callback(_lvl: PrintLevel, msg: String) {
let _ = io::stderr().write(msg.as_bytes());
}

// While we can't say that set_print is thread-safe, because we shouldn't assume that of
// libbpf_set_print, we should still make sure that things are sane on the rust side of things.
// Therefore we are using a lock to keep the log level and the callback in sync.
//
// We don't do anything that can panic with the lock held, so we'll unconditionally unwrap() when
// locking the mutex.
//
// Note that default print behavior ignores debug messages.
lazy_static! {
static ref PRINT_CB: Mutex<Option<(PrintLevel, PrintCallback)>> =
Mutex::new(Some((PrintLevel::Info, default_callback)));
}

extern "C" fn outer_print_cb(
level: libbpf_sys::libbpf_print_level,
fmtstr: *const c_char,
va_list: *mut libbpf_sys::__va_list_tag,
) -> i32 {
let level = level.into();
if let Some((min_level, func)) = { *PRINT_CB.lock().unwrap() } {
if level <= min_level {
let msg = match unsafe { vsprintf::vsprintf(fmtstr, va_list) } {
Ok(s) => s,
Err(e) => format!("Failed to parse libbpf output: {}", e),
};
func(level, msg);
}
}
0 // return value is ignored by libbpf
}

/// Set a callback to receive log messages from libbpf, instead of printing them to stderr.
///
/// # Arguments
///
/// * `callback` - Either a tuple `(min_level, function)` where `min_level` is the lowest priority
/// log message to handle, or `None` to disable all printing.
///
/// This overrides (and is overridden by) [`ObjectBuilder::debug`]
///
/// # Examples
///
/// To pass all messages to the `log` crate:
///
/// ```
/// use log;
/// use libbpf_rs::{PrintLevel, set_print};
///
/// fn print_to_log(level: PrintLevel, msg: String) {
/// match level {
/// PrintLevel::Debug => log::debug!("{}", msg),
/// PrintLevel::Info => log::info!("{}", msg),
/// PrintLevel::Warn => log::warn!("{}", msg),
/// }
/// }
///
/// set_print(Some((PrintLevel::Debug, print_to_log)));
/// ```
///
/// To disable printing completely:
///
/// ```
/// use libbpf_rs::set_print;
/// set_print(None);
/// ```
///
/// To temporarliy suppress output:
///
/// ```
/// use libbpf_rs::set_print;
///
/// let prev = set_print(None);
/// // do things quietly
/// set_print(prev);
/// ```
pub fn set_print(
mut callback: Option<(PrintLevel, PrintCallback)>,
) -> Option<(PrintLevel, PrintCallback)> {
let real_cb: libbpf_sys::libbpf_print_fn_t;
real_cb = callback.as_ref().and(Some(outer_print_cb));
std::mem::swap(&mut callback, &mut *PRINT_CB.lock().unwrap());
unsafe { libbpf_sys::libbpf_set_print(real_cb) };
callback
}

/// Return the current print callback and level.
///
/// # Examples
///
/// To temporarliy suppress output:
///
/// ```
/// use libbpf_rs::{get_print, set_print};
///
/// let prev = get_print();
/// set_print(None);
/// // 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.

*PRINT_CB.lock().unwrap()
}
74 changes: 74 additions & 0 deletions libbpf-rs/tests/test_print.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! This test is in its own file because the underlying libbpf_set_print function used by
//! set_print() and ObjectBuilder::debug() sets global state. The default is to run multiple tests
//! in different threads, so this test will always race with the others unless its isolated to a
//! different process.
//!
//! For the same reason, all tests here must run serially.

use libbpf_rs::{get_print, set_print, ObjectBuilder, PrintCallback, PrintLevel};
use serial_test::serial;
use std::sync::atomic::{AtomicBool, Ordering};

#[test]
#[serial]
fn test_set_print() {
static CORRECT_LEVEL: AtomicBool = AtomicBool::new(false);
static CORRECT_MESSAGE: AtomicBool = AtomicBool::new(false);

fn callback(level: PrintLevel, msg: String) {
if level == PrintLevel::Warn {
CORRECT_LEVEL.store(true, Ordering::Relaxed);
}

if msg.starts_with("libbpf: ") {
CORRECT_MESSAGE.store(true, Ordering::Relaxed);
}
}

set_print(Some((PrintLevel::Debug, callback)));
// expect_err requires that OpenObject implement Debug, which it does not.
let obj = ObjectBuilder::default().open_file("/dev/null");
assert!(obj.is_err(), "Successfully loaded /dev/null?");

let correct_level = CORRECT_LEVEL.load(Ordering::Relaxed);
let correct_message = CORRECT_MESSAGE.load(Ordering::Relaxed);
assert!(correct_level, "Did not capture a warning");
assert!(correct_message, "Did not capture the correct message");
}

#[test]
#[serial]
fn test_set_restore_print() {
fn callback1(_: PrintLevel, _: String) {
println!("one");
}
fn callback2(_: PrintLevel, _: String) {
println!("two");
}

set_print(Some((PrintLevel::Warn, callback1)));
let prev = get_print();
assert_eq!(prev, Some((PrintLevel::Warn, callback1 as PrintCallback)));

set_print(Some((PrintLevel::Debug, callback2)));
let prev = get_print();
assert_eq!(prev, Some((PrintLevel::Debug, callback2 as PrintCallback)));
}

#[test]
#[serial]
fn test_set_and_save_print() {
fn callback1(_: PrintLevel, _: String) {
println!("one");
}
fn callback2(_: PrintLevel, _: String) {
println!("two");
}

set_print(Some((PrintLevel::Warn, callback1)));
let prev = set_print(Some((PrintLevel::Debug, callback2)));
assert_eq!(prev, Some((PrintLevel::Warn, callback1 as PrintCallback)));

let prev = set_print(None);
assert_eq!(prev, Some((PrintLevel::Debug, callback2 as PrintCallback)));
}