一、ksamplingd 线程概述
在内核中新建 ksamplingd 用于采样硬件性能事件的线程:
- 从 Perf Events 的 ring buffer 里读取样本
- 处理样本数据
- 根据自身实际 CPU 时间占用率,动态调整采样频率
这篇文章的介绍将略过采样和处理样本的部分,因为采样在之前博客里详细分析过。这里主要调整的参数是struct perf_event_attr sample_period
二、线程初始化
1. CPU 线程给定
const struct cpumask *cpumask = cpumask_of_node(0);
if (!cpumask_empty(cpumask))
do_set_cpus_allowed(access_sampling, cpumask);
cpumask_of_node(0)获取节点 0 所有 CPU 的 mask。- 通过
do_set_cpus_allowed()把采样线程给定运行在某些 CPU 上。看需求,因为在笔者的场景中,只关注 socket0 的冷数据和热数据,而socket0 虽然 NUMA 有两个,实际物理核心只是 NUMA0 的。
2. 时间变量初始化
trace_runtime = total_runtime = exec_runtime = t->se.sum_exec_runtime;
trace_cputime = total_cputime = elapsed_cputime = jiffies;
sleep_timeout = usecs_to_jiffies(2000);
t->se.sum_exec_runtime:是内核调度实体task_struct的struct sched_entity中记录该进程历史上总共运行的 CPU 耗时(单位 ns)。原本用于计算 CFS 调度器中优先级和时间分配。jiffies:内核时钟计数器sleep_timeout:指定最短采样间隔
三、CPU 占用率计算
1. 时间差值
exec_runtime = t->se.sum_exec_runtime - exec_runtime; // ns
elapsed_cputime = jiffies_to_usecs(cur - elapsed_cputime); // us
exec_runtime:本次周期内采样线程耗时elapsed_cputime:本次周期内当前增量时间
2. 占用率计算
需要单位符合:把 ns -> us
u64 cur_cputime = div64_u64(exec_runtime / 1000, elapsed_cputime); // permil
最终得到 cputime :
- 单位是 permil (% 的 10倍)
- 如果
cputime = 300,即 CPU 占用 30%
四、动态调整采样频率
1. 调整规则
关键参数:ksampled_soft_cpu_quota 目标 CPU 占用率上限(permil,千分比)cputime 实际测得的 CPU 时间占用率sample_period 当前 LLC 采样周期(控制采样间隔)sample_inst_period 当前指令采样周期pcount 宏定义的上限
增加采样周期(降低采样频率):
当 cputime 超过目标占用率 ksampled_soft_cpu_quota + 5(防止频繁调整),且 sample_period 没有达到上限 pcount,调用 increase_sample_period 使采样周期增加,从而降低采样频率。如果周期有变化,应用新的采样周期。
减少采样周期(提高采样频率):
当 cputime 低于目标占用率 ksampled_soft_cpu_quota - 5,并且 sample_period 大于 pinstcount,可以调用 decrease_sample_period 减小采样周期,增加采样频率。同样如果采样周期发生变化,需要更新 pebs 采样器的配置。
2. 调整函数实现细节
- 线性调整:每次增加或减少采样周期,只调整一个单位。确保不会超过预定义的上下限
pcount和pinstcount。响应慢但足够稳定。 - 通过查表(预先定义一个已经排序的数组,数组中每个值表示周期)进行非线性调整,适合对不同采样周期有特殊设计的应用场景。
五、线程休眠机制
schedule_timeout_interruptible(sleep_timeout);
- 休眠控制线程激活频率
- 配合采样周期,避免忙等和超时间