use anyhow::{anyhow, Result};
use libbpf_rs::{btf, query::ProgInfoIter, ProgramType};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::fs;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SchedExtOpType {
Enqueue,
Dequeue,
Dispatch,
Running,
Stopping,
Quiescent,
Yield,
CoreSched,
SetWeight,
SetCpumask,
CpuAcquire,
CpuRelease,
CpuOnline,
CpuOffline,
InitTask,
ExitTask,
Enable,
Cancel,
Unknown,
}
impl SchedExtOpType {
pub fn from_program_name(name: &str) -> Self {
let name_lower = name.to_lowercase();
if name_lower.contains("enqueue") {
SchedExtOpType::Enqueue
} else if name_lower.contains("dequeue") {
SchedExtOpType::Dequeue
} else if name_lower.contains("dispatch") {
SchedExtOpType::Dispatch
} else if name_lower.contains("running") {
SchedExtOpType::Running
} else if name_lower.contains("stopping") {
SchedExtOpType::Stopping
} else if name_lower.contains("quiescent") {
SchedExtOpType::Quiescent
} else if name_lower.contains("yield") {
SchedExtOpType::Yield
} else if name_lower.contains("coresched") {
SchedExtOpType::CoreSched
} else if name_lower.contains("set_weight") {
SchedExtOpType::SetWeight
} else if name_lower.contains("set_cpumask") {
SchedExtOpType::SetCpumask
} else if name_lower.contains("cpu_acquire") {
SchedExtOpType::CpuAcquire
} else if name_lower.contains("cpu_release") {
SchedExtOpType::CpuRelease
} else if name_lower.contains("cpu_online") {
SchedExtOpType::CpuOnline
} else if name_lower.contains("cpu_offline") {
SchedExtOpType::CpuOffline
} else if name_lower.contains("init_task") || name_lower.contains("init") {
SchedExtOpType::InitTask
} else if name_lower.contains("exit_task") || name_lower.contains("exit") {
SchedExtOpType::ExitTask
} else if name_lower.contains("enable") {
SchedExtOpType::Enable
} else if name_lower.contains("cancel") {
SchedExtOpType::Cancel
} else {
SchedExtOpType::Unknown
}
}
pub fn display_name(&self) -> &'static str {
match self {
SchedExtOpType::Enqueue => "Enqueue",
SchedExtOpType::Dequeue => "Dequeue",
SchedExtOpType::Dispatch => "Dispatch",
SchedExtOpType::Running => "Running",
SchedExtOpType::Stopping => "Stopping",
SchedExtOpType::Quiescent => "Quiescent",
SchedExtOpType::Yield => "Yield",
SchedExtOpType::CoreSched => "CoreSched",
SchedExtOpType::SetWeight => "SetWeight",
SchedExtOpType::SetCpumask => "SetCpumask",
SchedExtOpType::CpuAcquire => "CPU Acquire",
SchedExtOpType::CpuRelease => "CPU Release",
SchedExtOpType::CpuOnline => "CPU Online",
SchedExtOpType::CpuOffline => "CPU Offline",
SchedExtOpType::InitTask => "Init Task",
SchedExtOpType::ExitTask => "Exit Task",
SchedExtOpType::Enable => "Enable",
SchedExtOpType::Cancel => "Cancel",
SchedExtOpType::Unknown => "Unknown",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BpfLineInfo {
pub file_name_off: u32,
pub line_col: u32,
pub instruction_offset: u32,
}
impl BpfLineInfo {
#[allow(dead_code)]
pub fn line_number(&self) -> u32 {
self.line_col >> 10
}
#[allow(dead_code)]
pub fn column_number(&self) -> u32 {
self.line_col & 0x3ff
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BpfSymbolInfo {
pub tag: String,
pub jited_line_info: Vec<u64>,
pub line_info: Vec<BpfLineInfo>,
pub btf_info: Option<Vec<u8>>,
pub jited_ksyms: Vec<u64>, pub jited_func_lens: Vec<u32>, }
impl BpfSymbolInfo {
#[allow(dead_code)]
pub fn get_filename(&self, file_name_off: u32) -> Option<String> {
Some(format!("bpf_program_{}.c", file_name_off))
}
pub fn get_source_location(&self, ip: u64) -> Option<(u32, u32)> {
if self.jited_line_info.is_empty() || self.line_info.is_empty() {
return None;
}
match self.jited_line_info.binary_search(&ip) {
Ok(idx) => {
if idx < self.line_info.len() {
let line_info = &self.line_info[idx];
Some((line_info.line_number(), line_info.column_number()))
} else {
None
}
}
Err(idx) => {
if idx > 0 && idx <= self.jited_line_info.len() {
let prev_idx = idx - 1;
let prev_addr = self.jited_line_info[prev_idx];
if ip >= prev_addr && ip < prev_addr + 100 && prev_idx < self.line_info.len() {
let line_info = &self.line_info[prev_idx];
return Some((line_info.line_number(), line_info.column_number()));
}
}
None
}
}
}
pub fn contains_address(&self, ip: u64) -> bool {
if !self.jited_ksyms.is_empty() && self.jited_ksyms.len() == self.jited_func_lens.len() {
for (i, &start_addr) in self.jited_ksyms.iter().enumerate() {
let end_addr = start_addr + self.jited_func_lens[i] as u64;
if ip >= start_addr && ip < end_addr {
return true;
}
}
false
} else if !self.jited_line_info.is_empty() {
match self.jited_line_info.binary_search(&ip) {
Ok(_) => true, Err(pos) => {
if pos > 0 {
let prev_addr = self.jited_line_info[pos - 1];
if ip >= prev_addr && ip < prev_addr + 1000 {
return true;
}
}
if pos < self.jited_line_info.len() {
let next_addr = self.jited_line_info[pos];
if ip >= next_addr && ip < next_addr + 1000 {
return true;
}
}
false
}
}
} else {
false
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BpfProgData {
pub id: u32,
pub prog_type: String,
pub name: String,
pub run_time_ns: u64,
pub run_cnt: u64,
pub min_runtime_ns: u64,
pub max_runtime_ns: u64,
pub recursion_misses: u64,
pub verified_insns: u32,
pub loaded_at: u64,
pub uid: u32,
pub gpl_compatible: bool,
pub netns_dev: u64,
pub netns_ino: u64,
pub nr_map_ids: u32,
pub map_ids: Vec<u32>,
pub btf_id: u32,
#[serde(skip)]
pub runtime_history: VecDeque<u64>,
#[serde(skip)]
pub calls_history: VecDeque<u64>,
#[serde(skip)]
pub timestamp_history: VecDeque<u64>,
pub p50_runtime_ns: u64,
pub p90_runtime_ns: u64,
pub p99_runtime_ns: u64,
pub is_sched_ext: bool,
pub sched_ext_ops_name: Option<String>,
pub sched_ext_op_type: Option<SchedExtOpType>,
}
impl BpfProgData {
pub fn avg_runtime_ns(&self) -> f64 {
if self.run_cnt == 0 {
0.0
} else {
self.run_time_ns as f64 / self.run_cnt as f64
}
}
pub fn runtime_percentage(&self, total_runtime_ns: u64) -> f64 {
if total_runtime_ns == 0 {
0.0
} else {
(self.run_time_ns as f64 / total_runtime_ns as f64) * 100.0
}
}
fn interpolate_percentile(sorted_data: &[u64], rank: f64) -> u64 {
let rank_floor = rank.floor();
let rank_ceil = rank.ceil();
if rank_floor == rank_ceil {
sorted_data[rank as usize]
} else {
let idx_floor = rank_floor as usize;
let idx_ceil = rank_ceil as usize;
let d0 = sorted_data[idx_floor];
let d1 = sorted_data[idx_ceil];
d0 + ((rank - rank_floor) * (d1 - d0) as f64) as u64
}
}
pub fn update_histogram(&mut self, max_samples: usize) {
while self.runtime_history.len() > max_samples {
self.runtime_history.pop_front();
}
while self.calls_history.len() > max_samples {
self.calls_history.pop_front();
}
while self.timestamp_history.len() > max_samples {
self.timestamp_history.pop_front();
}
if self.run_cnt > 0 && !self.runtime_history.is_empty() {
let mut sorted_runtimes: Vec<u64> = self.runtime_history.iter().copied().collect();
sorted_runtimes.sort_unstable();
let len = sorted_runtimes.len();
if len > 0 {
let n = len as f64;
let rank_p50 = 0.50 * (n - 1.0);
self.p50_runtime_ns = Self::interpolate_percentile(&sorted_runtimes, rank_p50);
let rank_p90 = 0.90 * (n - 1.0);
self.p90_runtime_ns = Self::interpolate_percentile(&sorted_runtimes, rank_p90);
let rank_p99 = 0.99 * (n - 1.0);
self.p99_runtime_ns = Self::interpolate_percentile(&sorted_runtimes, rank_p99);
}
}
}
pub fn calls_per_second(&self) -> f64 {
if self.calls_history.len() < 2 || self.timestamp_history.len() < 2 {
return 0.0;
}
let call_delta = self.calls_history.back().unwrap() - self.calls_history.front().unwrap();
let time_delta_ns =
self.timestamp_history.back().unwrap() - self.timestamp_history.front().unwrap();
if time_delta_ns == 0 {
return 0.0;
}
(call_delta as f64 / time_delta_ns as f64) * 1_000_000_000.0
}
}
#[derive(Debug, Clone)]
pub struct OperationStats {
pub total_runtime_ns: u64,
pub total_calls: u64,
pub program_count: usize,
}
#[derive(Debug, Clone)]
pub struct BpfProgStats {
pub programs: HashMap<u32, BpfProgData>,
pub total_runtime_ns: u64,
pub operation_stats: HashMap<SchedExtOpType, OperationStats>,
}
#[repr(C)]
#[derive(Default)]
struct BpfProgInfo {
type_: u32,
id: u32,
tag: [u8; 8],
jited_prog_len: u32,
xlated_prog_len: u32,
jited_prog_insns: u64,
xlated_prog_insns: u64,
load_time: u64,
created_by_uid: u32,
nr_map_ids: u32,
map_ids: u64,
name: [u8; 16],
ifindex: u32,
gpl_compatible: u32,
netns_dev: u64,
netns_ino: u64,
nr_jited_ksyms: u32,
nr_jited_func_lens: u32,
jited_ksyms: u64,
jited_func_lens: u64,
btf_id: u32,
func_info_rec_size: u32,
func_info: u64,
nr_func_info: u32,
nr_line_info: u32,
line_info: u64,
jited_line_info: u64,
nr_jited_line_info: u32,
line_info_rec_size: u32,
jited_line_info_rec_size: u32,
nr_prog_tags: u32,
prog_tags: u64,
run_time_ns: u64,
run_cnt: u64,
recursion_misses: u64,
verified_insns: u32,
}
type LineInfoResult = (Vec<BpfLineInfo>, Vec<u64>, Vec<u64>, Vec<u32>);
impl BpfProgStats {
pub fn new() -> Self {
Self {
programs: HashMap::new(),
total_runtime_ns: 0,
operation_stats: HashMap::new(),
}
}
pub fn calculate_operation_stats(&mut self) {
self.operation_stats.clear();
for prog_data in self.programs.values() {
if let Some(op_type) = prog_data.sched_ext_op_type {
let stats = self
.operation_stats
.entry(op_type)
.or_insert(OperationStats {
total_runtime_ns: 0,
total_calls: 0,
program_count: 0,
});
stats.total_runtime_ns += prog_data.run_time_ns;
stats.total_calls += prog_data.run_cnt;
stats.program_count += 1;
}
}
}
fn detect_sched_ext_program(info: &libbpf_rs::query::ProgramInfo) -> (bool, Option<String>) {
if matches!(info.ty, ProgramType::StructOps) {
let name = info.name.to_string_lossy().to_string();
if name.contains("_ops") {
let scheduler_name = name
.strip_prefix("scx_")
.or(Some(name.as_str()))
.and_then(|n| n.strip_suffix("_ops"))
.map(|n| n.to_string());
return (true, scheduler_name);
}
}
let name = info.name.to_string_lossy().to_string();
let sched_ext_keywords = [
"enqueue",
"dequeue",
"dispatch",
"running",
"stopping",
"quiescent",
"yield",
"set_weight",
"set_cpumask",
"cpu_acquire",
"cpu_release",
"cpu_online",
"cpu_offline",
"init_task",
"exit_task",
];
for keyword in &sched_ext_keywords {
if name.contains(keyword) {
let parts: Vec<&str> = name.split('_').collect();
if parts.len() > 1 && parts[0] == "scx" {
return (true, Some(parts[1].to_string()));
}
return (true, Some("sched_ext".to_string()));
}
}
(false, None)
}
fn program_type_to_string(prog_type: &ProgramType) -> String {
match prog_type {
ProgramType::SocketFilter => "socket_filter",
ProgramType::Kprobe => "kprobe",
ProgramType::SchedCls => "sched_cls",
ProgramType::SchedAct => "sched_act",
ProgramType::Tracepoint => "tracepoint",
ProgramType::Xdp => "xdp",
ProgramType::PerfEvent => "perf_event",
ProgramType::CgroupSkb => "cgroup_skb",
ProgramType::CgroupSock => "cgroup_sock",
ProgramType::LwtIn => "lwt_in",
ProgramType::LwtOut => "lwt_out",
ProgramType::LwtXmit => "lwt_xmit",
ProgramType::SockOps => "sock_ops",
ProgramType::SkSkb => "sk_skb",
ProgramType::CgroupDevice => "cgroup_device",
ProgramType::SkMsg => "sk_msg",
ProgramType::RawTracepoint => "raw_tracepoint",
ProgramType::CgroupSockAddr => "cgroup_sock_addr",
ProgramType::LwtSeg6local => "lwt_seg6local",
ProgramType::LircMode2 => "lirc_mode2",
ProgramType::SkReuseport => "sk_reuseport",
ProgramType::FlowDissector => "flow_dissector",
ProgramType::CgroupSysctl => "cgroup_sysctl",
ProgramType::RawTracepointWritable => "raw_tracepoint_writable",
ProgramType::CgroupSockopt => "cgroup_sockopt",
ProgramType::Tracing => "tracing",
ProgramType::StructOps => "struct_ops",
ProgramType::Ext => "ext",
ProgramType::SkLookup => "sk_lookup",
ProgramType::Syscall => "syscall",
_ => "unknown",
}
.to_string()
}
pub fn collect_and_update(&mut self) -> Result<()> {
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let mut new_programs = HashMap::new();
let mut new_total_runtime_ns = 0u64;
if let Err(e) =
Self::collect_via_bpf_calls_temp(&mut new_programs, &mut new_total_runtime_ns)
{
log::debug!("BPF syscalls failed: {}, falling back to procfs", e);
Self::collect_via_procfs_temp(&mut new_programs, &mut new_total_runtime_ns)?;
}
for (prog_id, mut new_prog_data) in new_programs {
if let Some(prev_data) = self.programs.get(&prog_id) {
let runtime_delta = new_prog_data
.run_time_ns
.saturating_sub(prev_data.run_time_ns);
let calls_delta = new_prog_data.run_cnt.saturating_sub(prev_data.run_cnt);
let avg_runtime = if calls_delta > 0 {
let avg = runtime_delta / calls_delta;
if new_prog_data.min_runtime_ns == 0 || avg < new_prog_data.min_runtime_ns {
new_prog_data.min_runtime_ns = avg;
} else {
new_prog_data.min_runtime_ns = prev_data.min_runtime_ns;
}
if avg > new_prog_data.max_runtime_ns {
new_prog_data.max_runtime_ns = avg;
} else {
new_prog_data.max_runtime_ns = prev_data.max_runtime_ns;
}
avg
} else {
0
};
new_prog_data.runtime_history = prev_data.runtime_history.clone();
new_prog_data.calls_history = prev_data.calls_history.clone();
new_prog_data.timestamp_history = prev_data.timestamp_history.clone();
new_prog_data.p50_runtime_ns = prev_data.p50_runtime_ns;
new_prog_data.p90_runtime_ns = prev_data.p90_runtime_ns;
new_prog_data.p99_runtime_ns = prev_data.p99_runtime_ns;
if calls_delta > 0 {
new_prog_data.runtime_history.push_back(avg_runtime);
}
} else {
}
new_prog_data.calls_history.push_back(new_prog_data.run_cnt);
new_prog_data.timestamp_history.push_back(current_time);
new_prog_data.update_histogram(300);
self.programs.insert(prog_id, new_prog_data);
}
self.total_runtime_ns = new_total_runtime_ns;
self.calculate_operation_stats();
Ok(())
}
pub fn get_real_symbol_info(prog_id: u32) -> Result<Option<BpfSymbolInfo>> {
let prog_fd = Self::get_prog_fd_by_id(prog_id)?;
if prog_fd < 0 {
return Ok(None);
}
let prog_info = Self::get_detailed_prog_info(prog_fd)?;
let (line_info, jited_line_info, jited_ksyms, jited_func_lens) =
Self::extract_line_info(prog_fd, &prog_info)?;
unsafe {
libc::close(prog_fd);
}
let btf_info = Self::get_btf_info_raw(prog_id);
let tag = format!("{:016x}", u64::from_ne_bytes(prog_info.tag));
Ok(Some(BpfSymbolInfo {
tag,
jited_line_info,
line_info,
btf_info,
jited_ksyms,
jited_func_lens,
}))
}
fn get_program_instruction_count(prog_id: u32) -> Result<u32> {
let prog_fd = Self::get_prog_fd_by_id(prog_id)?;
if prog_fd < 0 {
return Ok(0);
}
let prog_info = Self::get_detailed_prog_info(prog_fd)?;
unsafe {
libc::close(prog_fd);
}
Ok(prog_info.verified_insns)
}
fn get_prog_fd_by_id(prog_id: u32) -> Result<i32> {
use std::mem;
const BPF_PROG_GET_FD_BY_ID: u32 = 13;
#[repr(C)]
#[derive(Default)]
struct BpfAttr {
prog_get_fd_by_id: BpfProgGetFdById,
}
#[repr(C)]
#[derive(Default)]
struct BpfProgGetFdById {
prog_id: u32,
next_id: u32,
open_flags: u32,
}
let mut attr = BpfAttr {
prog_get_fd_by_id: BpfProgGetFdById {
prog_id,
next_id: 0,
open_flags: 0,
},
};
let fd = unsafe {
libc::syscall(
libc::SYS_bpf,
BPF_PROG_GET_FD_BY_ID as libc::c_long,
&mut attr as *mut _ as libc::c_long,
mem::size_of::<BpfAttr>() as libc::c_long,
)
};
if fd < 0 {
return Err(anyhow!(
"Failed to get prog fd by ID {}: {}",
prog_id,
std::io::Error::last_os_error()
));
}
Ok(fd as i32)
}
fn get_detailed_prog_info(prog_fd: i32) -> Result<BpfProgInfo> {
use std::mem;
const BPF_OBJ_GET_INFO_BY_FD: u32 = 15;
#[repr(C)]
#[derive(Default)]
struct BpfAttr {
info: BpfObjGetInfo,
}
#[repr(C)]
#[derive(Default)]
struct BpfObjGetInfo {
bpf_fd: u32,
info_len: u32,
info: u64,
}
let mut prog_info = BpfProgInfo::default();
let mut attr = BpfAttr {
info: BpfObjGetInfo {
bpf_fd: prog_fd as u32,
info_len: mem::size_of::<BpfProgInfo>() as u32,
info: &mut prog_info as *mut _ as u64,
},
};
let result = unsafe {
libc::syscall(
libc::SYS_bpf,
BPF_OBJ_GET_INFO_BY_FD as libc::c_long,
&mut attr as *mut _ as libc::c_long,
mem::size_of::<BpfAttr>() as libc::c_long,
)
};
if result < 0 {
return Err(anyhow!(
"Failed to get program info: {}",
std::io::Error::last_os_error()
));
}
Ok(prog_info)
}
fn extract_line_info(prog_fd: i32, prog_info: &BpfProgInfo) -> Result<LineInfoResult> {
use std::mem;
const BPF_OBJ_GET_INFO_BY_FD: u32 = 15;
#[repr(C)]
#[derive(Default)]
struct BpfAttr {
info: BpfObjGetInfo,
}
#[repr(C)]
#[derive(Default)]
struct BpfObjGetInfo {
bpf_fd: u32,
info_len: u32,
info: u64,
}
let mut line_info = Vec::new();
let mut jited_line_info = Vec::new();
let mut jited_ksyms = Vec::new();
let mut jited_func_lens = Vec::new();
if prog_info.nr_line_info > 0 {
let line_info_size =
prog_info.nr_line_info as usize * prog_info.line_info_rec_size as usize;
let mut line_info_buf = vec![0u8; line_info_size];
let mut new_prog_info = BpfProgInfo {
line_info: line_info_buf.as_mut_ptr() as u64,
nr_line_info: prog_info.nr_line_info,
line_info_rec_size: prog_info.line_info_rec_size,
..Default::default()
};
let mut attr = BpfAttr {
info: BpfObjGetInfo {
bpf_fd: prog_fd as u32,
info_len: mem::size_of::<BpfProgInfo>() as u32,
info: &mut new_prog_info as *mut _ as u64,
},
};
let result = unsafe {
libc::syscall(
libc::SYS_bpf,
BPF_OBJ_GET_INFO_BY_FD as libc::c_long,
&mut attr as *mut _ as libc::c_long,
mem::size_of::<BpfAttr>() as libc::c_long,
)
};
if result >= 0 {
let line_info_rec_size = prog_info.line_info_rec_size as usize;
for i in 0..prog_info.nr_line_info as usize {
let offset = i * line_info_rec_size;
if offset + 12 <= line_info_buf.len() {
let instruction_offset = u32::from_ne_bytes([
line_info_buf[offset],
line_info_buf[offset + 1],
line_info_buf[offset + 2],
line_info_buf[offset + 3],
]);
let file_name_off = u32::from_ne_bytes([
line_info_buf[offset + 4],
line_info_buf[offset + 5],
line_info_buf[offset + 6],
line_info_buf[offset + 7],
]);
let line_col = u32::from_ne_bytes([
line_info_buf[offset + 8],
line_info_buf[offset + 9],
line_info_buf[offset + 10],
line_info_buf[offset + 11],
]);
line_info.push(BpfLineInfo {
file_name_off,
line_col,
instruction_offset,
});
}
}
} else {
log::warn!(
"Failed to extract line info: {}",
std::io::Error::last_os_error()
);
}
}
if prog_info.nr_jited_line_info > 0 {
let jited_line_info_size =
prog_info.nr_jited_line_info as usize * prog_info.jited_line_info_rec_size as usize;
let mut jited_line_info_buf = vec![0u8; jited_line_info_size];
let mut new_prog_info = BpfProgInfo {
jited_line_info: jited_line_info_buf.as_mut_ptr() as u64,
nr_jited_line_info: prog_info.nr_jited_line_info,
jited_line_info_rec_size: prog_info.jited_line_info_rec_size,
..Default::default()
};
let mut attr = BpfAttr {
info: BpfObjGetInfo {
bpf_fd: prog_fd as u32,
info_len: mem::size_of::<BpfProgInfo>() as u32,
info: &mut new_prog_info as *mut _ as u64,
},
};
let result = unsafe {
libc::syscall(
libc::SYS_bpf,
BPF_OBJ_GET_INFO_BY_FD as libc::c_long,
&mut attr as *mut _ as libc::c_long,
mem::size_of::<BpfAttr>() as libc::c_long,
)
};
if result >= 0 {
let jited_line_info_rec_size = prog_info.jited_line_info_rec_size as usize;
for i in 0..prog_info.nr_jited_line_info as usize {
let offset = i * jited_line_info_rec_size;
if offset + 8 <= jited_line_info_buf.len() {
let addr = u64::from_ne_bytes([
jited_line_info_buf[offset],
jited_line_info_buf[offset + 1],
jited_line_info_buf[offset + 2],
jited_line_info_buf[offset + 3],
jited_line_info_buf[offset + 4],
jited_line_info_buf[offset + 5],
jited_line_info_buf[offset + 6],
jited_line_info_buf[offset + 7],
]);
jited_line_info.push(addr);
}
}
} else {
log::warn!(
"Failed to extract JIT line info: {}",
std::io::Error::last_os_error()
);
}
}
if prog_info.nr_jited_ksyms > 0 {
let ksyms_size = prog_info.nr_jited_ksyms as usize * 8; let mut ksyms_buf = vec![0u8; ksyms_size];
let func_lens_size = prog_info.nr_jited_func_lens as usize * 4; let mut func_lens_buf = vec![0u8; func_lens_size];
let mut new_prog_info = BpfProgInfo {
jited_ksyms: ksyms_buf.as_mut_ptr() as u64,
nr_jited_ksyms: prog_info.nr_jited_ksyms,
jited_func_lens: func_lens_buf.as_mut_ptr() as u64,
nr_jited_func_lens: prog_info.nr_jited_func_lens,
..Default::default()
};
let mut attr = BpfAttr {
info: BpfObjGetInfo {
bpf_fd: prog_fd as u32,
info_len: mem::size_of::<BpfProgInfo>() as u32,
info: &mut new_prog_info as *mut _ as u64,
},
};
let result = unsafe {
libc::syscall(
libc::SYS_bpf,
BPF_OBJ_GET_INFO_BY_FD as libc::c_long,
&mut attr as *mut _ as libc::c_long,
mem::size_of::<BpfAttr>() as libc::c_long,
)
};
if result >= 0 {
for i in 0..prog_info.nr_jited_ksyms as usize {
let offset = i * 8;
if offset + 8 <= ksyms_buf.len() {
let addr = u64::from_ne_bytes([
ksyms_buf[offset],
ksyms_buf[offset + 1],
ksyms_buf[offset + 2],
ksyms_buf[offset + 3],
ksyms_buf[offset + 4],
ksyms_buf[offset + 5],
ksyms_buf[offset + 6],
ksyms_buf[offset + 7],
]);
jited_ksyms.push(addr);
}
}
for i in 0..prog_info.nr_jited_func_lens as usize {
let offset = i * 4;
if offset + 4 <= func_lens_buf.len() {
let len = u32::from_ne_bytes([
func_lens_buf[offset],
func_lens_buf[offset + 1],
func_lens_buf[offset + 2],
func_lens_buf[offset + 3],
]);
jited_func_lens.push(len);
}
}
} else {
log::warn!(
"Failed to extract JIT ksyms/func_lens: {}",
std::io::Error::last_os_error()
);
}
}
Ok((line_info, jited_line_info, jited_ksyms, jited_func_lens))
}
fn get_btf_info_raw(prog_id: u32) -> Option<Vec<u8>> {
if prog_id == 0 {
return None;
}
match btf::Btf::from_prog_id(prog_id) {
Ok(_btf_obj) => {
None }
Err(e) => {
log::warn!("Failed to get BTF data for program ID {}: {}", prog_id, e);
None
}
}
}
fn collect_via_bpf_calls_temp(
programs: &mut HashMap<u32, BpfProgData>,
total_runtime_ns: &mut u64,
) -> Result<()> {
for prog_info in ProgInfoIter::default() {
if let Some(prog_data) = Self::convert_libbpf_prog_info(&prog_info) {
*total_runtime_ns += prog_data.run_time_ns;
programs.insert(prog_data.id, prog_data);
}
}
Ok(())
}
fn convert_libbpf_prog_info(info: &libbpf_rs::query::ProgramInfo) -> Option<BpfProgData> {
let id = info.id;
let name = info.name.to_string_lossy().to_string();
let prog_type = Self::program_type_to_string(&info.ty);
let run_time_ns = info.run_time_ns;
let run_cnt = info.run_cnt;
let recursion_misses = info.recursion_misses;
let verified_insns = Self::get_program_instruction_count(id).unwrap_or(0);
let loaded_at = info.load_time.as_nanos() as u64; let uid = info.created_by_uid;
let gpl_compatible = info.gpl_compatible;
let netns_dev = info.netns_dev;
let netns_ino = info.netns_ino;
let btf_id = info.btf_id;
let map_ids = info.map_ids.clone();
let nr_map_ids = map_ids.len() as u32;
let (is_sched_ext, sched_ext_ops_name) = Self::detect_sched_ext_program(info);
let sched_ext_op_type = if is_sched_ext {
Some(SchedExtOpType::from_program_name(&name))
} else {
None
};
Some(BpfProgData {
id,
prog_type,
name,
run_time_ns,
run_cnt,
min_runtime_ns: 0, max_runtime_ns: 0, recursion_misses,
verified_insns,
loaded_at,
uid,
gpl_compatible,
netns_dev,
netns_ino,
nr_map_ids,
map_ids,
btf_id,
runtime_history: VecDeque::new(),
calls_history: VecDeque::new(),
timestamp_history: VecDeque::new(),
p50_runtime_ns: 0,
p90_runtime_ns: 0,
p99_runtime_ns: 0,
is_sched_ext,
sched_ext_ops_name,
sched_ext_op_type,
})
}
fn collect_via_procfs_temp(
programs: &mut HashMap<u32, BpfProgData>,
total_runtime_ns: &mut u64,
) -> Result<()> {
for entry in fs::read_dir("/proc")? {
let entry = entry?;
let path = entry.path();
if let Some(pid_str) = path.file_name().and_then(|n| n.to_str()) {
if let Ok(pid) = pid_str.parse::<u32>() {
if Self::scan_process_for_bpf_temp(pid, programs, total_runtime_ns).is_err() {
continue;
}
}
}
}
Ok(())
}
fn scan_process_for_bpf_temp(
pid: u32,
programs: &mut HashMap<u32, BpfProgData>,
total_runtime_ns: &mut u64,
) -> Result<()> {
let fd_dir = format!("/proc/{}/fd", pid);
if !Path::new(&fd_dir).exists() {
return Ok(());
}
for entry in fs::read_dir(&fd_dir)? {
let entry = entry?;
let fd_path = entry.path();
if let Some(fd_str) = fd_path.file_name().and_then(|n| n.to_str()) {
if let Ok(fd) = fd_str.parse::<u32>() {
if Self::check_fd_for_bpf_temp(pid, fd, programs, total_runtime_ns).is_err() {
continue;
}
}
}
}
Ok(())
}
fn check_fd_for_bpf_temp(
pid: u32,
fd: u32,
programs: &mut HashMap<u32, BpfProgData>,
total_runtime_ns: &mut u64,
) -> Result<()> {
let fdinfo_path = format!("/proc/{}/fdinfo/{}", pid, fd);
if let Ok(content) = fs::read_to_string(&fdinfo_path) {
if content.contains("prog_type") {
if let Ok(prog_data) = Self::parse_bpf_fdinfo(&content, fd) {
*total_runtime_ns += prog_data.run_time_ns;
programs.insert(prog_data.id, prog_data);
}
}
}
Ok(())
}
fn parse_bpf_fdinfo(content: &str, fd: u32) -> Result<BpfProgData> {
let mut prog_data = BpfProgData {
id: fd, prog_type: String::new(),
name: String::new(),
run_time_ns: 0,
run_cnt: 0,
min_runtime_ns: 0,
max_runtime_ns: 0,
recursion_misses: 0,
verified_insns: 0,
loaded_at: 0,
uid: 0,
gpl_compatible: false,
netns_dev: 0,
netns_ino: 0,
nr_map_ids: 0,
map_ids: Vec::new(),
btf_id: 0,
runtime_history: VecDeque::new(),
calls_history: VecDeque::new(),
timestamp_history: VecDeque::new(),
p50_runtime_ns: 0,
p90_runtime_ns: 0,
p99_runtime_ns: 0,
is_sched_ext: false,
sched_ext_ops_name: None,
sched_ext_op_type: None,
};
for line in content.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
match parts[0] {
"prog_type:" => prog_data.prog_type = parts[1].to_string(),
"prog_name:" => prog_data.name = parts[1].to_string(),
"run_time_ns:" => prog_data.run_time_ns = parts[1].parse().unwrap_or(0),
"run_cnt:" => prog_data.run_cnt = parts[1].parse().unwrap_or(0),
"recursion_misses:" => {
prog_data.recursion_misses = parts[1].parse().unwrap_or(0)
}
"verified_insns:" => prog_data.verified_insns = parts[1].parse().unwrap_or(0),
"load_time:" => prog_data.loaded_at = parts[1].parse().unwrap_or(0),
"uid:" => prog_data.uid = parts[1].parse().unwrap_or(0),
"gpl_compatible:" => prog_data.gpl_compatible = parts[1] == "1",
"netns_dev:" => prog_data.netns_dev = parts[1].parse().unwrap_or(0),
"netns_ino:" => prog_data.netns_ino = parts[1].parse().unwrap_or(0),
"btf_id:" => prog_data.btf_id = parts[1].parse().unwrap_or(0),
_ => {}
}
}
}
Ok(prog_data)
}
}
impl Default for BpfProgStats {
fn default() -> Self {
Self::new()
}
}