1use anyhow::anyhow;
2use aya::maps::{Array, HashMap, MapData, RingBuf, StackTraceMap};
3use aya::programs::{
4 perf_event::{PerfEventScope, PerfTypeId, SamplePolicy},
5 KProbe, PerfEvent,
6};
7use aya::programs::{RawTracePoint, TracePoint, UProbe};
8use aya::{include_bytes_aligned, util::online_cpus};
9use aya::{Btf, Ebpf, EbpfLoader};
10
11use aya::Pod;
12use profile_bee_common::{ProcInfo, ProcInfoKey, UnwindEntry};
13
14#[repr(transparent)]
16#[derive(Debug, Clone, Copy)]
17pub struct StackInfoPod(pub profile_bee_common::StackInfo);
18unsafe impl Pod for StackInfoPod {}
19
20#[repr(transparent)]
21#[derive(Debug, Clone, Copy)]
22pub struct FramePointersPod(pub profile_bee_common::FramePointers);
23unsafe impl Pod for FramePointersPod {}
24
25#[repr(transparent)]
26#[derive(Debug, Clone, Copy)]
27pub struct UnwindEntryPod(pub UnwindEntry);
28unsafe impl Pod for UnwindEntryPod {}
29
30#[repr(transparent)]
31#[derive(Debug, Clone, Copy)]
32pub struct ProcInfoKeyPod(pub ProcInfoKey);
33unsafe impl Pod for ProcInfoKeyPod {}
34
35#[repr(transparent)]
36#[derive(Clone, Copy)]
37pub struct ProcInfoPod(pub ProcInfo);
38unsafe impl Pod for ProcInfoPod {}
39
40#[derive(Debug)]
42pub struct EbpfProfiler {
43 pub bpf: Ebpf,
44 pub stack_traces: StackTraceMap<MapData>,
46 pub counts: HashMap<MapData, StackInfoPod, u64>,
48 pub stacked_pointers: HashMap<MapData, StackInfoPod, FramePointersPod>,
50}
51#[derive(Debug, Clone)]
54pub struct UProbeConfig {
55 pub function: String,
57 pub path: String,
59 pub is_retprobe: bool,
61 pub pid: Option<i32>,
63}
64
65#[derive(Debug, Clone)]
68pub struct SmartUProbeConfig {
69 pub probes: Vec<crate::probe_resolver::ResolvedProbe>,
71 pub pid: Option<i32>,
73}
74
75pub struct ProfilerConfig {
76 pub skip_idle: bool,
77 pub stream_mode: u8,
78 pub frequency: u64,
79 pub kprobe: Option<String>,
80 pub uprobe: Option<UProbeConfig>,
82 pub smart_uprobe: Option<SmartUProbeConfig>,
84 pub tracepoint: Option<String>,
85 pub raw_tracepoint: Option<String>,
88 pub raw_tracepoint_task_regs: Option<String>,
92 pub raw_tracepoint_generic: Option<String>,
95 pub target_syscall_nr: i64,
97 pub pid: Option<u32>,
98 pub cpu: Option<u32>,
99 pub self_profile: bool,
100 pub dwarf: bool,
101}
102
103pub fn load_ebpf(config: &ProfilerConfig) -> Result<Ebpf, anyhow::Error> {
105 let data = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/profile-bee.bpf.o"));
109
110 let skip_idle = if config.skip_idle { 1u8 } else { 0u8 };
111 let dwarf_enabled = if config.dwarf { 1u8 } else { 0u8 };
112 let target_syscall_nr: i64 = config.target_syscall_nr;
113
114 let bpf = EbpfLoader::new()
115 .set_global("SKIP_IDLE", &skip_idle, true)
116 .set_global("NOTIFY_TYPE", &config.stream_mode, true)
117 .set_global("DWARF_ENABLED", &dwarf_enabled, true)
118 .set_global("TARGET_SYSCALL_NR", &target_syscall_nr, true)
119 .btf(Btf::from_sys_fs().ok().as_ref())
120 .load(data)
121 .map_err(|e| {
122 println!("{:?}", e);
123 e
124 })?;
125
126 Ok(bpf)
130}
131
132pub fn setup_ebpf_profiler(config: &ProfilerConfig) -> Result<EbpfProfiler, anyhow::Error> {
133 let mut bpf = load_ebpf(config)?;
134
135 if let Some(kprobe) = &config.kprobe {
136 let program: &mut KProbe = bpf.program_mut("kprobe_profile").unwrap().try_into()?;
137 program.load()?;
138 program.attach(kprobe, 0)?;
139 } else if let Some(smart) = &config.smart_uprobe {
140 if smart.probes.is_empty() {
142 return Err(anyhow!("no probe targets resolved — nothing to attach"));
143 }
144
145 let has_uprobe = smart.probes.iter().any(|p| !p.is_ret);
147 let has_uretprobe = smart.probes.iter().any(|p| p.is_ret);
148
149 if has_uprobe {
150 let program: &mut UProbe = bpf.program_mut("uprobe_profile").unwrap().try_into()?;
151 program.load()?;
152
153 for probe in smart.probes.iter().filter(|p| !p.is_ret) {
154 let display_name = probe.demangled.as_deref().unwrap_or(&probe.symbol_name);
155 eprintln!(
156 " attaching uprobe: {}:{} (0x{:x})",
157 probe.library_path.display(),
158 display_name,
159 probe.address,
160 );
161 program.attach(
162 Some(probe.symbol_name.as_str()),
163 probe.offset,
164 probe.library_path.to_str().ok_or_else(|| {
165 anyhow!("non-UTF8 library path: {}", probe.library_path.display())
166 })?,
167 smart.pid,
168 )?;
169 }
170 }
171
172 if has_uretprobe {
173 let program: &mut UProbe = bpf.program_mut("uretprobe_profile").unwrap().try_into()?;
174 program.load()?;
175
176 for probe in smart.probes.iter().filter(|p| p.is_ret) {
177 let display_name = probe.demangled.as_deref().unwrap_or(&probe.symbol_name);
178 eprintln!(
179 " attaching uretprobe: {}:{} (0x{:x})",
180 probe.library_path.display(),
181 display_name,
182 probe.address,
183 );
184 program.attach(
185 Some(probe.symbol_name.as_str()),
186 probe.offset,
187 probe.library_path.to_str().ok_or_else(|| {
188 anyhow!("non-UTF8 library path: {}", probe.library_path.display())
189 })?,
190 smart.pid,
191 )?;
192 }
193 }
194 } else if let Some(uprobe_config) = &config.uprobe {
195 let program_name = if uprobe_config.is_retprobe {
197 "uretprobe_profile"
198 } else {
199 "uprobe_profile"
200 };
201
202 let program: &mut UProbe = bpf.program_mut(program_name).unwrap().try_into()?;
203 program.load()?;
204
205 let (fn_name, offset) = if let Some(plus_pos) = uprobe_config.function.find('+') {
207 let (name, offset_str) = uprobe_config.function.split_at(plus_pos);
208 let offset = offset_str[1..]
209 .parse::<u64>()
210 .map_err(|e| anyhow::anyhow!("Invalid offset in uprobe function: {}", e))?;
211 (Some(name), offset)
212 } else {
213 (Some(uprobe_config.function.as_str()), 0)
214 };
215
216 program.attach(
217 fn_name,
218 offset,
219 uprobe_config.path.as_str(),
220 uprobe_config.pid,
221 )?;
222 } else if let Some(raw_tp) = &config.raw_tracepoint {
223 let prog_name = if raw_tp == "sys_exit" {
225 "raw_tp_sys_exit"
226 } else {
227 "raw_tp_sys_enter"
228 };
229 let program: &mut RawTracePoint = bpf.program_mut(prog_name).unwrap().try_into()?;
230 program.load()?;
231 program.attach(raw_tp)?;
232 } else if let Some(raw_tp) = &config.raw_tracepoint_task_regs {
233 let program: &mut RawTracePoint =
234 bpf.program_mut("raw_tp_with_regs").unwrap().try_into()?;
235 program.load()?;
236 program.attach(raw_tp)?;
237 } else if let Some(raw_tp) = &config.raw_tracepoint_generic {
238 let program: &mut RawTracePoint = bpf.program_mut("raw_tp_generic").unwrap().try_into()?;
239 program.load()?;
240 program.attach(raw_tp)?;
241 } else if let Some(tracepoint) = &config.tracepoint {
242 let program: &mut TracePoint = bpf.program_mut("tracepoint_profile").unwrap().try_into()?;
243 program.load()?;
244
245 let mut split = tracepoint.split(':');
246 let category = split.next().expect("category");
247 let name = split.next().expect("name");
248
249 program.attach(category, name)?;
250 } else {
251 let program: &mut PerfEvent = bpf.program_mut("profile_cpu").unwrap().try_into()?;
252
253 program.load()?;
254
255 const PERF_COUNT_SW_CPU_CLOCK: u64 = 0;
257
258 let perf_type = PerfTypeId::Software;
261
262 if config.self_profile {
263 program.attach(
264 perf_type,
265 PERF_COUNT_SW_CPU_CLOCK,
266 PerfEventScope::CallingProcessAnyCpu,
267 SamplePolicy::Frequency(config.frequency),
268 true,
269 )?;
270 } else if config.pid.is_some() || config.cpu.is_some() {
271 let cpus = if let Some(cpu) = config.cpu {
274 vec![cpu]
275 } else {
276 online_cpus().map_err(|(_, error)| error)?
277 };
278
279 let nprocs = cpus.len();
280 if let Some(pid) = config.pid {
281 eprintln!(
282 "Profiling PID {} and child processes across {} CPUs",
283 pid, nprocs
284 );
285 } else if let Some(cpu) = config.cpu {
286 eprintln!("Profiling CPU {}", cpu);
287 }
288
289 for cpu in cpus {
290 program.attach(
291 perf_type.clone(),
292 PERF_COUNT_SW_CPU_CLOCK,
293 PerfEventScope::AllProcessesOneCpu { cpu },
294 SamplePolicy::Frequency(config.frequency),
295 true,
296 )?;
297 }
298 } else {
299 let cpus = online_cpus().map_err(|(_, error)| error)?;
300 let nprocs = cpus.len();
301 eprintln!("CPUs: {}", nprocs);
302
303 for cpu in cpus {
304 program.attach(
305 perf_type.clone(),
306 PERF_COUNT_SW_CPU_CLOCK,
307 PerfEventScope::AllProcessesOneCpu { cpu },
308 SamplePolicy::Frequency(config.frequency),
309 true,
310 )?;
311 }
312 }
313 }
314
315 let stack_traces = StackTraceMap::try_from(
316 bpf.take_map("stack_traces")
317 .ok_or(anyhow!("stack_traces not found"))?,
318 )?;
319
320 let counts = bpf
321 .take_map("counts")
322 .ok_or(anyhow!("counts not found"))?
323 .try_into()?;
324
325 let stacked_pointers = bpf
326 .take_map("stacked_pointers")
327 .ok_or(anyhow!("stacked_pointers not found"))?
328 .try_into()?;
329
330 Ok(EbpfProfiler {
331 bpf,
332 stack_traces,
333 counts,
334 stacked_pointers,
335 })
336}
337
338pub fn setup_ring_buffer(bpf: &mut Ebpf) -> Result<RingBuf<MapData>, anyhow::Error> {
339 let ring_buf = RingBuf::try_from(
340 bpf.take_map("RING_BUF_STACKS")
341 .ok_or(anyhow!("RING_BUF_STACKS not found"))?,
342 )?;
343
344 Ok(ring_buf)
345}
346
347impl EbpfProfiler {
348 pub fn set_target_pid(&mut self, pid: u32) -> Result<(), anyhow::Error> {
350 let mut target_pid_map: Array<&mut MapData, u32> = Array::try_from(
351 self.bpf
352 .map_mut("target_pid_map")
353 .ok_or(anyhow!("target_pid_map not found"))?,
354 )?;
355 target_pid_map.set(0, pid, 0)?;
356 Ok(())
357 }
358
359 pub fn load_dwarf_unwind_tables(
361 &mut self,
362 manager: &crate::dwarf_unwind::DwarfUnwindManager,
363 ) -> Result<(), anyhow::Error> {
364 let all_shard_ids: Vec<u8> = manager.binary_tables.keys().copied().collect();
366 self.update_dwarf_tables(manager, &all_shard_ids)
367 }
368
369 fn load_shard(&mut self, shard_id: u8, entries: &[UnwindEntry]) -> Result<(), anyhow::Error> {
371 let map_name = format!("shard_{}", shard_id);
372 let mut arr: Array<&mut MapData, UnwindEntryPod> = Array::try_from(
373 self.bpf
374 .map_mut(&map_name)
375 .ok_or_else(|| anyhow!("{} map not found", map_name))?,
376 )?;
377 for (idx, entry) in entries.iter().enumerate() {
378 arr.set(idx as u32, UnwindEntryPod(*entry), 0)?;
379 }
380 Ok(())
381 }
382
383 pub fn update_dwarf_tables(
386 &mut self,
387 manager: &crate::dwarf_unwind::DwarfUnwindManager,
388 new_shard_ids: &[u8],
389 ) -> Result<(), anyhow::Error> {
390 if !new_shard_ids.is_empty() {
391 let mut total_entries = 0usize;
392 for &shard_id in new_shard_ids {
393 if let Some(entries) = manager.binary_tables.get(&shard_id) {
394 self.load_shard(shard_id, entries)?;
395 total_entries += entries.len();
396 tracing::info!("Loaded shard {} with {} entries", shard_id, entries.len());
397 }
398 }
399
400 tracing::info!(
401 "Loaded {} total unwind entries across {} shards",
402 total_entries,
403 new_shard_ids.len()
404 );
405 }
406
407 let mut proc_info_map: HashMap<&mut MapData, ProcInfoKeyPod, ProcInfoPod> =
408 HashMap::try_from(
409 self.bpf
410 .map_mut("proc_info")
411 .ok_or(anyhow!("proc_info map not found"))?,
412 )?;
413
414 for (&tgid, proc_info) in &manager.proc_info {
415 let key = ProcInfoKeyPod(ProcInfoKey { tgid, _pad: 0 });
416 let value = ProcInfoPod(*proc_info);
417 proc_info_map.insert(key, value, 0)?;
418
419 tracing::info!(
420 "Loaded process info for tgid {} ({} mappings)",
421 tgid,
422 proc_info.mapping_count,
423 );
424 }
425
426 Ok(())
427 }
428}