1use bpf_tracing_include::event::{CallsiteKey, Event, Kind};
96use libbpf_rs::{MapCore, MapHandle};
97use std::{
98 cell::RefCell,
99 collections::{HashMap, VecDeque},
100 path::{Component, Path, PathBuf},
101 thread::{self},
102};
103use tracing::{self, metadata::Metadata, span::EnteredSpan};
104
105const TARGET: &str = "bpf";
106
107type Spans = Vec<VecDeque<(String, EnteredSpan)>>;
108
109thread_local! {
110 static CALLSITES: RefCell<HashMap<CallsiteKey, &'static Metadata<'static>>> = RefCell::new(HashMap::new());
111 static SPANS: RefCell<Spans> = {
112 let cpus = thread::available_parallelism().unwrap().get();
113 let mut spans: Spans = Vec::new();
114 for _ in 0..cpus {
115 spans.push(VecDeque::new());
116 }
117 RefCell::new(spans)
118 };
119}
120
121pub fn try_init(obj: &libbpf_rs::Object) -> libbpf_rs::Result<()> {
128 let mut builder = libbpf_rs::RingBufferBuilder::new();
129 let mut events: Option<MapHandle> = None;
130
131 for map in obj.maps() {
132 if map.name().eq("bpf_tracing_events") {
133 let map_id = map.info()?.info.id;
134 events = Some(MapHandle::from_map_id(map_id)?);
135 }
136 }
137
138 let Some(events) = events else {
139 return Err(libbpf_rs::Error::from(std::io::Error::new(
140 std::io::ErrorKind::NotFound,
141 "event ring buffer not found",
142 )));
143 };
144
145 builder.add(&events, |ev| process(ev)).unwrap();
146 let ringbuf = builder.build().unwrap();
147
148 thread::spawn(move || {
149 loop {
150 if let Err(_) = ringbuf.poll(std::time::Duration::from_millis(1)) {
151 continue;
152 }
153 }
154 });
155
156 Ok(())
157}
158
159fn process(event: &[u8]) -> i32 {
160 let Ok(event) = Event::try_from(event) else {
161 return -1;
162 };
163
164 emit(event);
165
166 0
167}
168
169fn strip_matching_prefix_components(full: &Path, base: &Path) -> PathBuf {
170 let mut full_it = full.components().peekable();
171 let mut base_it = base.components().peekable();
172
173 while let (Some(f), Some(b)) = (full_it.peek(), base_it.peek()) {
174 if f == b {
175 full_it.next();
176 base_it.next();
177 } else {
178 break;
179 }
180 }
181
182 let mut out = PathBuf::new();
183 for c in full_it {
184 match c {
185 Component::Normal(s) => out.push(s),
186 Component::CurDir => out.push("."),
187 Component::ParentDir => out.push(".."),
188 Component::RootDir => out.push(Path::new("/")),
189 Component::Prefix(p) => out.push(p.as_os_str()),
190 }
191 }
192 out
193}
194
195fn get_callsite(key: CallsiteKey) -> &'static Metadata<'static> {
196 CALLSITES.with_borrow_mut(|cs| {
197 if let Some(meta) = cs.get(&key) {
198 *meta
199 } else {
200 let (file, line, is_span, level) = key;
201
202 let callsite = if is_span {
203 tracing::callsite!(name: "fake", kind: tracing::metadata::Kind::EVENT, fields: &[])
204 } else {
205 tracing::callsite!(name: "fake", kind: tracing::metadata::Kind::SPAN, fields: &[])
206 };
207
208 let static_file: Option<&'static str> = if let Some(ref file) = file {
209 let path = Path::new(&file);
210 let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
211 let rel = strip_matching_prefix_components(path, manifest)
212 .to_string_lossy()
213 .to_string();
214
215 Some(Box::leak(rel.into_boxed_str()) as &'static str)
216 } else {
217 None
218 };
219
220 let meta = Box::leak(Box::new(Metadata::new(
221 "",
222 TARGET,
223 level,
224 static_file,
225 line,
226 None,
227 tracing::field::FieldSet::new(
228 &["message"],
229 tracing::callsite::Identifier(callsite),
230 ),
231 if is_span {
232 tracing::metadata::Kind::SPAN
233 } else {
234 tracing::metadata::Kind::EVENT
235 },
236 )));
237
238 let key = (file, line, is_span, level);
239 cs.insert(key, meta);
240
241 let meta: &'static Metadata = meta;
242 meta
243 }
244 })
245}
246
247fn emit(event: Event) {
248 let cpu = event.cpu;
249 SPANS.with_borrow_mut(|spans| match &event.kind {
250 Kind::Message(lvl) => {
251 if *lvl <= tracing::metadata::LevelFilter::current() {
252 let content = event.content.clone();
253 let meta = get_callsite(event.try_into().unwrap());
254 let parent = spans[cpu].back().and_then(|(_, p)| p.id());
255
256 tracing::Event::child_of(
257 parent,
258 meta,
259 &tracing::valueset_all!(meta.fields(), "{}", content),
260 );
261 }
262 }
263 Kind::StartSpan(lvl) => {
264 if *lvl <= tracing::metadata::LevelFilter::current() {
265 let content = event.content.clone();
266 let meta = get_callsite(event.try_into().unwrap());
267 let parent = spans[cpu].back().and_then(|(_, p)| p.id());
268
269 let span = tracing::Span::child_of(
270 parent,
271 meta,
272 &tracing::valueset_all!(meta.fields(), "{}", content),
273 );
274 spans[cpu].push_back((content, span.entered()));
275 }
276 }
277 Kind::EndSpan => {
278 let content = event.content;
279 while let Some((n, _)) = spans[cpu].pop_back() {
280 if n == content {
281 break;
282 }
283 }
284 }
285 });
286}
287
288#[cfg(test)]
289mod tests {
290 use tracing::Level;
291
292 use super::*;
293
294 #[test]
295 fn leaks_one_callsite_per_level_and_kind() {
296 fn callsite_len() -> usize {
297 CALLSITES.with_borrow(|cs| cs.len())
298 }
299
300 let event_msg_info1 = Event {
301 kind: Kind::Message(Level::INFO),
302 content: "event 1".to_string(),
303 cpu: 1,
304 file: None,
305 line: None,
306 };
307
308 let event_msg_info2 = Event {
309 kind: Kind::Message(Level::INFO),
310 content: "event 2".to_string(),
311 cpu: 9,
312 file: None,
313 line: None,
314 };
315
316 let _callsite1 = get_callsite(event_msg_info1.try_into().unwrap());
317 let _callsite2 = get_callsite(event_msg_info2.try_into().unwrap());
318 assert_eq!(callsite_len(), 1);
319
320 let event_span_info3 = Event {
321 kind: Kind::StartSpan(Level::INFO),
322 content: "event 3".to_string(),
323 cpu: 29,
324 file: None,
325 line: None,
326 };
327 let _callsite3 = get_callsite(event_span_info3.try_into().unwrap());
328 assert_eq!(callsite_len(), 2);
329
330 let event_span_info4 = Event {
331 kind: Kind::StartSpan(Level::INFO),
332 content: "event 4".to_string(),
333 cpu: 29,
334 file: Some(String::from("this/is/a/test_file.rs")),
335 line: Some(12),
336 };
337 let _callsite4 = get_callsite(event_span_info4.try_into().unwrap());
338 assert_eq!(callsite_len(), 3);
339
340 let event_span_info5 = Event {
341 kind: Kind::StartSpan(Level::INFO),
342 content: "event 5".to_string(),
343 cpu: 29,
344 file: Some(String::from("this/is/a/test_file.rs")),
345 line: Some(12),
346 };
347 let _callsite5 = get_callsite(event_span_info5.try_into().unwrap());
348 assert_eq!(callsite_len(), 3);
349 }
350}