NeuTracer 教程
主页
  • 背景介绍
  • 项目架构
  • 数据收集
  • 服务端
  • 异常检测
项目测试
主页
  • 背景介绍
  • 项目架构
  • 数据收集
  • 服务端
  • 异常检测
项目测试
  • 详细介绍

    • 背景介绍
    • 项目架构
    • 数据收集
    • 服务端
    • 异常检测

CPU模块 eBPF 部分

CPUsnoop.bpf.c 用于监控 Linux 内核的进程调度事件,通过 BTF (BPF Type Format) 增强型跟踪点,捕获调度器事件,提供细粒度的 CPU 使用情况分析。模块可以计算每个进程的 CPU 运行时间与等待时间,同时捕获进程上下文切换事件,分析调度行为。此外,该模块还能跟踪进程在不同 CPU 核心间的迁移,从创建到退出进行全周期监控。

该程序挂载到两个关键的内核调度跟踪点来实现完整的进程监控。sched_switch 跟踪点在 CPU 从一个进程切换到另一个进程时触发,而 sched_process_exit 跟踪点在进程退出时触发。

SEC("tp_btf/sched_switch")
int BPF_PROG(sched_switch, bool preempt, struct task_struct *prev, struct task_struct *next)
{
    u64 ts = bpf_ktime_get_ns();
    u32 cpu_id = bpf_get_smp_processor_id();

    u32 prev_pid = BPF_CORE_READ(prev, pid);
    u32 next_pid = BPF_CORE_READ(next, pid);

    // 处理离开CPU的进程
    if (should_track(prev_pid)) {
        struct task_data *data = bpf_map_lookup_elem(&task_stats, &prev_pid);
        if (!data) {
            struct task_data new_data = {};
            // ......
            // 初始化新进程数据
            bpf_map_update_elem(&task_stats, &prev_pid, &new_data, BPF_ANY);
        } else {
            // 如果当前在CPU上,累加这次的on-cpu时间
            if (data->is_oncpu && data->last_switch_time > 0) {
                u64 oncpu_delta = ts - data->last_switch_time;
                data->window_oncpu += oncpu_delta;
            }

            #ifdef INTERNAL
                if(ts % INTERVAL_NS) {
                    return 0; // 如果不在采样间隔内,则跳过
                }
            #endif

            send_cpu_event(prev_pid, data, ts, cpu_id, prev);
            
            // 更新状态
            data->is_oncpu = 0;
            data->last_switch_time = ts;
            data->last_cpu = cpu_id;
            bpf_map_update_elem(&task_stats, &prev_pid, data, BPF_ANY);
        }
    }

    // 处理进入CPU的进程
    if (should_track(next_pid)) {
        struct task_data *data = bpf_map_lookup_elem(&task_stats, &next_pid);
        if (!data) {
            struct task_data new_data = {};
            // ......
            // 初始化新进程数据
            bpf_map_update_elem(&task_stats, &next_pid, &new_data, BPF_ANY);
        } else {
            // 更新状态
            data->is_oncpu = true;
            data->last_switch_time = ts;
            data->last_cpu = cpu_id;
            bpf_map_update_elem(&task_stats, &next_pid, data, BPF_ANY);
        }
    }

    return 0;
}

cpu_event记录进程 CPU 使用情况的完整信息,包括进程 ID、父进程 ID、CPU 核心 ID、CPU 运行时间、等待时间、利用率、时间戳和进程名称。task_data则跟踪每个进程的 CPU 时间累计,包含上次在 CPU 上运行的开始时间、上次离开 CPU 的时间、累计 CPU 运行时间和累计 CPU 等待时间。

struct cpu_event {
    u32 pid;           // 进程 ID
    u32 ppid;          // 父进程 ID
    u32 cpu_id;        // CPU 核心 ID
    u64 oncpu_time;    // CPU 运行时间(纳秒)
    u64 offcpu_time;   // CPU 等待时间(纳秒)
    u64 utilization;   // 利用率(0-10000,对应 0-100.00%)
    u64 timestamp;     // 时间戳(微秒)
    char comm[TASK_COMM_LEN]; // 进程名称
};

struct task_data {
    u64 last_oncpu;
    u64 last_offcpu;
    u64 total_oncpu;
    u64 total_offcpu;
};