use bpf_tracing_include::event::{CallsiteKey, Event, Kind};
use libbpf_rs::{MapCore, MapHandle};
use std::{
cell::RefCell,
collections::{HashMap, VecDeque},
path::{Component, Path, PathBuf},
thread::{self},
};
use tracing::{self, metadata::Metadata, span::EnteredSpan};
const TARGET: &str = "bpf";
type Spans = Vec<VecDeque<(String, EnteredSpan)>>;
thread_local! {
static CALLSITES: RefCell<HashMap<CallsiteKey, &'static Metadata<'static>>> = RefCell::new(HashMap::new());
static SPANS: RefCell<Spans> = {
let cpus = thread::available_parallelism().unwrap().get();
let mut spans: Spans = Vec::new();
for _ in 0..cpus {
spans.push(VecDeque::new());
}
RefCell::new(spans)
};
}
pub fn try_init(obj: &libbpf_rs::Object) -> libbpf_rs::Result<()> {
let mut builder = libbpf_rs::RingBufferBuilder::new();
let mut events: Option<MapHandle> = None;
for map in obj.maps() {
if map.name().eq("bpf_tracing_events") {
let map_id = map.info()?.info.id;
events = Some(MapHandle::from_map_id(map_id)?);
}
}
let Some(events) = events else {
return Err(libbpf_rs::Error::from(std::io::Error::new(
std::io::ErrorKind::NotFound,
"event ring buffer not found",
)));
};
builder.add(&events, |ev| process(ev)).unwrap();
let ringbuf = builder.build().unwrap();
thread::spawn(move || {
loop {
if let Err(_) = ringbuf.poll(std::time::Duration::from_millis(1)) {
continue;
}
}
});
Ok(())
}
fn process(event: &[u8]) -> i32 {
let Ok(event) = Event::try_from(event) else {
return -1;
};
emit(event);
0
}
fn strip_matching_prefix_components(full: &Path, base: &Path) -> PathBuf {
let mut full_it = full.components().peekable();
let mut base_it = base.components().peekable();
while let (Some(f), Some(b)) = (full_it.peek(), base_it.peek()) {
if f == b {
full_it.next();
base_it.next();
} else {
break;
}
}
let mut out = PathBuf::new();
for c in full_it {
match c {
Component::Normal(s) => out.push(s),
Component::CurDir => out.push("."),
Component::ParentDir => out.push(".."),
Component::RootDir => out.push(Path::new("/")),
Component::Prefix(p) => out.push(p.as_os_str()),
}
}
out
}
fn get_callsite(key: CallsiteKey) -> &'static Metadata<'static> {
CALLSITES.with_borrow_mut(|cs| {
if let Some(meta) = cs.get(&key) {
*meta
} else {
let (file, line, is_span, level) = key;
let callsite = if is_span {
tracing::callsite!(name: "fake", kind: tracing::metadata::Kind::EVENT, fields: &[])
} else {
tracing::callsite!(name: "fake", kind: tracing::metadata::Kind::SPAN, fields: &[])
};
let static_file: Option<&'static str> = if let Some(ref file) = file {
let path = Path::new(&file);
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
let rel = strip_matching_prefix_components(path, manifest)
.to_string_lossy()
.to_string();
Some(Box::leak(rel.into_boxed_str()) as &'static str)
} else {
None
};
let meta = Box::leak(Box::new(Metadata::new(
"",
TARGET,
level,
static_file,
line,
None,
tracing::field::FieldSet::new(
&["message"],
tracing::callsite::Identifier(callsite),
),
if is_span {
tracing::metadata::Kind::SPAN
} else {
tracing::metadata::Kind::EVENT
},
)));
let key = (file, line, is_span, level);
cs.insert(key, meta);
let meta: &'static Metadata = meta;
meta
}
})
}
fn emit(event: Event) {
let cpu = event.cpu;
SPANS.with_borrow_mut(|spans| match &event.kind {
Kind::Message(lvl) => {
if *lvl <= tracing::metadata::LevelFilter::current() {
let content = event.content.clone();
let meta = get_callsite(event.try_into().unwrap());
let parent = spans[cpu].back().and_then(|(_, p)| p.id());
tracing::Event::child_of(
parent,
meta,
&tracing::valueset_all!(meta.fields(), "{}", content),
);
}
}
Kind::StartSpan(lvl) => {
if *lvl <= tracing::metadata::LevelFilter::current() {
let content = event.content.clone();
let meta = get_callsite(event.try_into().unwrap());
let parent = spans[cpu].back().and_then(|(_, p)| p.id());
let span = tracing::Span::child_of(
parent,
meta,
&tracing::valueset_all!(meta.fields(), "{}", content),
);
spans[cpu].push_back((content, span.entered()));
}
}
Kind::EndSpan => {
let content = event.content;
while let Some((n, _)) = spans[cpu].pop_back() {
if n == content {
break;
}
}
}
});
}
#[cfg(test)]
mod tests {
use tracing::Level;
use super::*;
#[test]
fn leaks_one_callsite_per_level_and_kind() {
fn callsite_len() -> usize {
CALLSITES.with_borrow(|cs| cs.len())
}
let event_msg_info1 = Event {
kind: Kind::Message(Level::INFO),
content: "event 1".to_string(),
cpu: 1,
file: None,
line: None,
};
let event_msg_info2 = Event {
kind: Kind::Message(Level::INFO),
content: "event 2".to_string(),
cpu: 9,
file: None,
line: None,
};
let _callsite1 = get_callsite(event_msg_info1.try_into().unwrap());
let _callsite2 = get_callsite(event_msg_info2.try_into().unwrap());
assert_eq!(callsite_len(), 1);
let event_span_info3 = Event {
kind: Kind::StartSpan(Level::INFO),
content: "event 3".to_string(),
cpu: 29,
file: None,
line: None,
};
let _callsite3 = get_callsite(event_span_info3.try_into().unwrap());
assert_eq!(callsite_len(), 2);
let event_span_info4 = Event {
kind: Kind::StartSpan(Level::INFO),
content: "event 4".to_string(),
cpu: 29,
file: Some(String::from("this/is/a/test_file.rs")),
line: Some(12),
};
let _callsite4 = get_callsite(event_span_info4.try_into().unwrap());
assert_eq!(callsite_len(), 3);
let event_span_info5 = Event {
kind: Kind::StartSpan(Level::INFO),
content: "event 5".to_string(),
cpu: 29,
file: Some(String::from("this/is/a/test_file.rs")),
line: Some(12),
};
let _callsite5 = get_callsite(event_span_info5.try_into().unwrap());
assert_eq!(callsite_len(), 3);
}
}