2019独角兽企业重金招聘Python工程师标准>>>
Loadavg分析
Loadavg浅述
cat /proc/loadavg 可以看到当前系统的load
$ cat /proc/loadavg
0.01 0.02 0.05 2/317 26207
前面三个值分别对应系统当前1分钟、5分钟、15分钟内的平均load。load用于反映当前系统的负载情况,对于16核的系统,如果每个核上cpu利用率为30%,则在不存在uninterruptible进程的情况下,系统load应该维持在4.8左右。对16核系统,如果load维持在16左右,在不存在uninterrptible进程的情况下,意味着系统CPU几乎不存在空闲状态,利用率接近于100%。结合iowait、vmstat和loadavg可以分析出系统当前的整体负载,各部分负载分布情况。
Loadavg读取
在内核中/proc/loadavg是通过load_read_proc来读取相应数据,下面首先来看一下load_read_proc的实现:
fs/proc/proc_misc.c
static int loadavg_read_proc(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
int a, b, c;
int len;
a = avenrun[0] + (FIXED_1/200);
b = avenrun[1] + (FIXED_1/200);
c = avenrun[2] + (FIXED_1/200);
len = sprintf(page,"%d.%02d %d.%02d %d.%02d %ld/%d %d\n",
LOAD_INT(a), LOAD_FRAC(a),
LOAD_INT(b), LOAD_FRAC(b),
LOAD_INT(c), LOAD_FRAC(c),
nr_running(), nr_threads, last_pid);
return proc_calc_metrics(page, start, off, count, eof, len);
}
几个宏定义如下:
#define FSHIFT 11 /* nr of bits of precision */
#define FIXED_1 (1<> FSHIFT)
#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100)
根据输出格式,LOAD_INT对应计算的是load的整数部分,LOAD_FRAC计算的是load的小数部分。
将a=avenrun[0] + (FIXED_1/200)带入整数部分和小数部分计算可得:
LOAD_INT(a) = avenrun[0]/(2^11) + 1/200
LOAD_FRAC(a) = ((avenrun[0]%(2^11) + 2^11/200) * 100) / (2^11)
= (((avenrun[0]%(2^11)) * 100 + 2^10) / (2^11)
= ((avenrun[0]%(2^11) * 100) / (2^11) + 1/2
由上述计算结果可以看出,FIXED_1/200在这里是用于小数部分第三位的四舍五入,由于小数部分只取前两位,第三位如果大于5,则进一位,否则直接舍去。
临时变量a/b/c的低11位存放的为load的小数部分值,第11位开始的高位存放的为load整数部分。因此可以得到a=load(1min) * 2^11
因此有: load(1min) * 2^11 = avenrun[0] + 2^11 / 200
进而推导出: load(1min)=avenrun[0]/(2^11) + 1/200
忽略用于小数部分第3位四舍五入的1/200,可以得到load(1min)=avenrun[0] / 2^11,即:
avenrun[0] = load(1min) * 2^11
avenrun是个陌生的量,这个变量是如何计算的,和系统运行进程、cpu之间的关系如何,在第二阶段进行分析。
Loadavg和进程之间的关系
内核将load的计算和load的查看进行了分离,avenrun就是用于连接load计算和load查看的桥梁。
下面开始分析通过avenrun进一步分析系统load的计算。
avenrun数组是在calc_load中进行更新
kernel/timer.c
/*
* calc_load - given tick count, update the avenrun load estimates.
* This is called while holding a write_lock on xtime_lock.
*/
static inline void calc_load(unsigned long ticks)
{
unsigned long active_tasks; /* fixed-point */
static int count = LOAD_FREQ;
count -= ticks;
if (count < 0) {
count += LOAD_FREQ;
active_tasks = count_active_tasks();
CALC_LOAD(avenrun[0], EXP_1, active_tasks);
CALC_LOAD(avenrun[1], EXP_5, active_tasks);
CALC_LOAD(avenrun[2], EXP_15, active_tasks);
}
}
static unsigned long count_active_tasks(void)
{
return nr_active() * FIXED_1;
}
#define LOAD_FREQ (5*HZ) /* 5 sec intervals */
#define EXP_1 1884 /* 1/exp(5sec/1min) as fixed-point */
#define EXP_5 2014 /* 1/exp(5sec/5min) */
#define EXP_15 2037 /* 1/exp(5sec/15min) */
calc_load在每个tick都会执行一次,每个LOAD_FREQ(5s)周期执行一次avenrun的更新。
active_tasks为系统中当前贡献load的task数nr_active乘于FIXED_1,用于计算avenrun。宏CALC_LOAD定义如下:
#define CALC_LOAD(load,exp,n) \
load *= exp; \
load += n*(FIXED_1-exp); \
load >>= FSHIFT;
用avenrun(t-1)和avenrun(t)分别表示上一次计算的avenrun和本次计算的avenrun,则根据CALC_LOAD宏可以得到如下计算:
avenrun(t)=(avenrun(t-1) * EXP_N + nr_active * FIXED_1*(FIXED_1 – EXP_N)) / FIXED_1
= avenrun(t-1) + (nr_active*FIXED_1 – avenrun(t-1)) * (FIXED_1 -EXP_N) / FIXED_1
推导出:
avenrun(t) – avenrun(t-1) = (nr_active*FIXED_1 – avenrun(t-1)) * (FIXED_1 – EXP_N) / FIXED_1
将第一阶段推导的结果代入上式,可得:
(load(t) – load(t-1)) * FIXED_1 = (nr_active – load(t-1)) * (FIXED_1 – EXP_N)
进一步得到nr_active变化和load变化之间的关系式:
load(t) – load(t-1) = (nr_active – load(t-1)) * (FIXED_1 – EXP_N) / FIXED_1
这个式子可以反映的内容包含如下两点:
1)当nr_active为常数时,load会不断的趋近于nr_active,趋近速率由快逐渐变缓
2)nr_active的变化反映在load的变化上是被降级了的,系统突然间增加10个进程,
1分钟load的变化每次只能够有不到1的增加(这个也就是权重的的分配)。
另外也可以通过将式子简化为:
load(t)= load(t-1) * EXP_N / FIXED_1 + nr_active * (1 - EXP_N/FIXED_1)
这样可以更加直观的看出nr_active和历史load在当前load中的权重关系
#define EXP_1 1884 /* 1/exp(5sec/1min) as fixed-point */
#define EXP_5 2014 /* 1/exp(5sec/5min) */
#define EXP_15 2037 /* 1/exp(5sec/15min) */
1分钟、5分钟、15分钟对应的EXP_N值如上,随着EXP_N的增大,(FIXED_1 – EXP_N)/FIXED_1值就越小,
这样nr_active的变化对整体load带来的影响就越小。对于一个nr_active波动较小的系统,load会
不断的趋近于nr_active,最开始趋近比较快,随着相差值变小,趋近慢慢变缓,越接近时越缓慢,并最终达到nr_active。
也因此得到一个结论,load直接反应的是系统中的nr_active。 那么nr_active又包含哪些? 如何去计算 当前系统中的nr_active? 这些就涉及到了nr_active的采样。
Loadavg采样
nr_active直接反映的是为系统贡献load的进程总数,这个总数在nr_active函数中计算:
kernel/sched.c
unsigned long nr_active(void)
{
unsigned long i, running = 0, uninterruptible = 0;
for_each_online_cpu(i) {
running += cpu_rq(i)->nr_running; // 处于运行中的进程
uninterruptible += cpu_rq(i)->nr_uninterruptible; // 处于uninterruptible状态的进程
}
if (unlikely((long)uninterruptible < 0))
uninterruptible = 0;
return running + uninterruptible;
}
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_STOPPED 4
#define TASK_TRACED 8
/* in tsk->exit_state */
#define EXIT_ZOMBIE 16
#define EXIT_DEAD 32
/* in tsk->state again */
#define TASK_NONINTERACTIVE 64
该函数反映,为系统贡献load的进程主要包括两类,一类是TASK_RUNNING,一类是TASK_UNINTERRUPTIBLE。
当5s采样周期到达时,对各个online-cpu的运行队列进行遍历,取得当前时刻该队列上running和uninterruptible的进程数作为当前cpu的load,各个cpu load的和即为本次采样得到的nr_active。
下面的示例说明了在2.6.18内核情况下loadavg的计算方法:
cpu0 | cpu1 | cpu2 | cpu3 | cpu4 | cpu5 | cpu6 | cpu7 | calc_load | |
0HZ+10 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
5HZ | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 4 |
5HZ+1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
5HZ+9 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 |
5HZ+11 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | <