跳转至

Kepler中的ebpf

背景

什么是ebpf?

eBPF是一项革命性的技术,起源于Linux内核,可以在操作系统内核等特权上下文中运行沙盒程序。它用于安全有效地扩展内核的功能,而无需更改内核源代码或加载内核模块。[1]

什么是kprobe?

KProbes是Linux内核的一种调试机制,也可用于监视生产系统内的事件。KProbes使您能够动态地闯入任何内核例程,并以无中断的方式收集调试和性能信息。您可以在几乎任何内核代码地址设置陷阱,指定在遇到断点时要调用的处理程序例程。[2]

如何查看已经注册的kprobes?

sudo cat /sys/kernel/debug/kprobes/list

CPU硬件事件监控

性能计数器是在如今大多数CPU上均已实现的一种特殊的硬件计数器。这些计数器在统计某些特殊类型的硬件事件: 例如执行命令,缓存失效,或分支预测错误的同时并不会降低内核或者程序执行速度。[4]

使用系统调用 perf_event_open [5], Linux系统允许设置硬件和软件性能的性能监视。它返回一个文件描述符来读取性能信息。 这个系统调用使用 pidcpuid 作为参数. Kepler使用pid == -1cpuid作为实际的cpuid。 这种pid和cpu的组合允许测量指定cpu上的所有进程/线程。

如何检查Linux内核是否支持perf_event_open?

检查是否存在/proc/sys/kernel/perf_event_paranoid,以了解内核是否支持perf_event_open以及允许测量的内容

   The perf_event_paranoid file can be set to restrict
   access to the performance counters.

   2      allow only user-space measurements (default since Linux 4.6).
   1      allow both kernel and user measurements (default before Linux 4.6).
   0      allow access to CPU-specific data but not raw tracepoint samples.
  -1      no restrictions.


   Measuring all process/threads required CAP_SYS_ADMIN capability or a value less than 1 in above file

CAP_SYS_ADMIN 是最高级别的能力,它可能具有一些安全影响。

kepler探测内核进程

kepler捕捉内核函数finish_task_switch[3], 该函数负责在任务切换发生后进行清理。由于探测通过kprobe发生,对它的调用发生在调用finish_task_switch之前,而不是在探测函数返回后调用kretprobe

当内核发生上下文切换时,函数finish_task_switch在新进程进入CPU时被调用。这个函数接受参数类型task_struct*,该参数类型包含所有关于离开CPU进程的所有信息。[3]

kepler的探测函数

int kprobe__finish_task_switch(struct pt_regs *ctx, struct task_struct *prev)

第一个参数的类型是指向pt_regs结构的指针,该结构指的是在内核函数条目时保持CPU寄存器状态的结构。此结构包含与CPU寄存器相对应的字段,例如通用寄存器(例如,r0、r1等)、堆栈指针(sp)、程序计数器(pc)和其他特定于体系结构的寄存器。 第二个参数是指向task_struct的指针,该指针包含前一任务的任务信息,即离开CPU的任务。

Kepler监控CPU硬件事件

Kepler监控以下CPU硬件事件

PERF Type Perf Count Type Description Array name
(in bpf program)
PERF_TYPE_HARDWARE PERF_COUNT_HW_CPU_CYCLES Total CPU cycles; can get affected by CPU frequency scaling cpu_cycles_hc_reader
PERF_TYPE_HARDWARE PERF_COUNT_HW_REF_CPU_CYCLES Total CPU cycles; not affected by CPU frequency scaling cpu_ref_cycles_hc_reader
PERF_TYPE_HARDWARE PERF_COUNT_HW_INSTRUCTIONS Retired instructions. Be careful, these can be affected by various issues, most notably hardware interrupt counts. cpu_instr_hc_reader
PERF_TYPE_HARDWARE PERF_COUNT_HW_CACHE_MISSES Cache misses. Usually this indicates Last Level Cache misses; this is intended to be used in conjunction with the PERF_COUNT_HW_CACHE_REFERENCES event to calculate cache miss rates. cache_miss_hc_reader

性能计数器通过特殊的文件描述符进行访问。每个使用的虚拟计数器都有一个文件描述符。文件描述符与相应的数组相关联。当使用bcc包装器函数时,它读取相应的fd并返回值。

计算进程CPU运行总时间

ebpf函数(bpfassets/perf_event/perf_event.c)维护一个基于时间戳对于<pid, cpuid>表。时间戳表示在cpu上调度pid时为pid调用kprobe_finish_task_switch的时刻<cpuid>

// <Task PID, CPUID> => Context Switch Start time

typedef struct pid_time_t { u32 pid; u32 cpu; } pid_time_t; 
BPF_HASH(pid_time, pid_time_t); 
// pid_time is the name of variable which if of type map
在函数get_on_cpu_time中,当前时间戳与pid_time映射中的时间戳之间的差用于计算当前cpu上先前任务的on_cpu_time_delta

on_cpu_time_delta用于累积前一任务的process_run_time度量。

计算进程CPU周期

对于进程的CPU周期,bpf程序维护cpu_cycles数组,并通过cpuid作为索引。这个数组包含性能数组cpu_cycles_hc_reader,是一个性能事件的数组。

在每个任务切换上, - 从性能计数器阵列cpu_cycles_hc_reader读取当前值 - 检索cpucycles中的上一个值 - delta是通过从当前值减去之前的值来计算的 - 当前值被复制回cpucycles,用于下一个任务切换

由此计算出的增量是离开cpu的进程所使用的cpu周期。

计算进程参考CPU周期

与计算CPU周期的过程相同,所使用的性能数组的替换为cpu_ref_cycles_hc_reader,prev值存储在CPU_ref_cycles

计算进程CPU指令

与计算CPU周期的过程相同,所使用的性能数组的替换为cpu_instr_hc_reader,prev值存储在cpu_instr

计算进程缓存失效

与计算CPU周期的过程相同,所使用的性能数组的替换为cache_miss_hc_reader,prev值存储在cache_miss

计算CPU上平均频率

avg_freq = ((on_cpu_cycles_delta * CPU_REF_FREQ) / on_cpu_ref_cycles_delta) * HZ;

CPU_REF_FREQ = 2500 
HZ = 1000

此值存储在数组cpu_freq_array

进程表

bpf程序维护一个名为processes的bpf散列。此散列维护为进程计算的数据。Kepler从这个散列中读取值(在bcc中称为Table)并生成度量。

Key Value Description
pid cgroupid Process CGroupID
pid Process ID
process_run_time Total time a process occupies CPU (calculated each time process leaves CPU on context switch)
cpu_cycles Total CPU cycles consumed by process
cpu_instr Total CPU instructions consumed by process
cache_miss Total Cache miss by process
vec_nr Total number of soft irq handles by process (max 10)
comm Process name (max length 16)

此散列由container_hc_collector.go中的内核收集器读取,用于收集指标。

参考

[1] https://ebpf.io/what-is-ebpf/ , https://www.splunk.com/en_us/blog/learn/what-is-ebpf.html , https://www.tigera.io/learn/guides/ebpf/

[2] An introduction to KProbes , Kernel Probes (Kprobes)

[3] finish_task_switch - clean up after a task-switch

[4] Performance Counters for Linux

[5] perf_event_open(2) — Linux manual page

Copyright Contributors to the Kepler's project.

The Linux Foundation® (TLF) has registered trademarks and uses trademarks. For a list of TLF trademarks, see Trademark Usage.