Docker_LXC 容器在 Linux 中正確獲取 Loadavg 的解決方案
背景
本文主要解決如何在 Linux 容器中正確獲取 Loadavg 資訊。
我們 cat /proc/loadavg 時會發現如下值:
$ > cat /proc/loadavg
0.64 0.81 0.86 3/364 6930
這些值的含義依次為:
- 0.64:1-分鐘平均負載。
- 0.81:5-分鐘平均負載。
- 0.86:15-分鐘平均負載。
- 3: 在取樣時刻,執行佇列的任務的數目。
- 364: 在取樣時刻,系統中活躍的任務的個數(不包括執行已經結束的任務)。
- 6930: 最大的 pid 值,包括輕量級程序,即執行緒。
平均負載定義:在特定時間間隔內執行佇列中的平均程序數。
程序狀態定義
- R (TASK_RUNNING),可執行狀態。
- S (TASK_INTERRUPTIBLE),可中斷的睡眠狀態。
- D (TASK_UNINTERRUPTIBLE),不可中斷的睡眠狀態。
- T (TASK_STOPPED or TASK_TRACED),暫停狀態或跟蹤狀態。
- Z (TASK_DEAD – EXIT_ZOMBIE),退出狀態,程序成為殭屍程序。
- X (TASK_DEAD – EXIT_DEAD),退出狀態,程序即將被銷燬。
如何計算 Loadavg?
計算公式:
load(t) = load(t-1) * exp(-5/60R) + n(t) * (1 – exp(5/60R))
n(t) 是系統活動的程序數, R 對應1、5、15分鐘(如當計算15分鐘的平均負載時,R 的值就為15)。
Linux 核心認為程序的生存時間服從引數為 1 的指數分佈,指數分佈的概率密度為:核心計算負載 load1 為例,設相鄰兩個計算時刻之間系統活動的程序集合為 S0。從 1 分鐘前到當前計算時刻這段時間裡面活動的 load1 的程序,設他們的集合是 S1,核心認為的概率密度是:λe-λx,而在當前時刻活動的 n 個程序,設他們的集合是 Sn 核心認為的概率密度是 1-λe-λx。其中 x = 5 / 60,因為相鄰兩個計算時刻之間程序所耗的 CPU 時間為 5 秒,而考慮的時間段是 1 分鐘(60 秒)。那麼可以求出最近 1 分鐘系統執行佇列的長度:
load1 = |S1| * λe-λx + |Sn| * (1-λe-λx) = load1 * λe-λx + n * (1-λe-λx)
其中 λ = 1, x = 5 / 60, |S1| 和 |Sn| 是集合元素的個數。
Linux 核心定義一個 unsigned long 型別陣列 avenrun[3],因為核心不能使用浮點數,就將低 11位 用於存放負載的小數部分,高 21 位用於存放整數部分。
-
avenrun[0] 對應前1分鐘系統負載;
-
avenrun[1] 對應前5分鐘系統負載;
-
avenrun[2] 對應前15分鐘系統負載;
核心每隔 5秒鐘 更新一次 load average 的值。
核心如何實現 Loadavg?
在核心中 Loadavg 計算部分與讀取部分是分開的。
注:使用 3.10.107 版本核心原始碼
- 讀取部分。
由下列原始碼我們可以看出,根據輸出格式,LOAD_INT 對應計算的是 load 的整數部分,LOAD__FRAC 計算的是 load 的小數部分。loadavg_proc_show 讀取 get_avenrun 來獲取 1、5、15 分鐘的系統負載值。
檔案:fs/proc/loadavg.c
#define LOAD_INT(x) ((x) >> FSHIFT)
#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100)
static int loadavg_proc_show(struct seq_file *m, void *v)
{
unsigned long avnrun[3];
get_avenrun(avnrun, FIXED_1/200, 0);
seq_printf(m, "%lu.%02lu %lu.%02lu %lu.%02lu %ld/%d %d\n",
LOAD_INT(avnrun[0]), LOAD_FRAC(avnrun[0]),
LOAD_INT(avnrun[1]), LOAD_FRAC(avnrun[1]),
LOAD_INT(avnrun[2]), LOAD_FRAC(avnrun[2]),
nr_running(), nr_threads,
task_active_pid_ns(current)->last_pid);
return 0;
}
檔案:kernel/sched/core.c
unsigned long avenrun[3];
void get_avenrun(unsigned long *loads, unsigned long offset, int shift)
{
loads[0] = (avenrun[0] + offset) << shift;
loads[1] = (avenrun[1] + offset) << shift;
loads[2] = (avenrun[2] + offset) << shift;
}
unsigned long nr_running(void)
{
unsigned long i, sum = 0;
for_each_online_cpu(i)
sum += cpu_rq(i)->nr_running;
return sum;
}
- 計算部分。
核心設計一個定時器,時鐘一到就會去呼叫 xtime_update()-> do_timer()-> calc_global_load() 函式,如果超時 5s 那麼就會更新一次 load 資料,即:avenrun 陣列。具體如下核心原始碼:
//檔案:include/linux/sched.h
#define FSHIFT 11 /* nr of bits of precision */
#define FIXED_1 (1<<FSHIFT) /* 1.0 as fixed-point */
#define LOAD_FREQ (5*HZ+1) /* 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) */
//檔案:kernel/time/timekeeping.c
/**
* xtime_update() - advances the timekeeping infrastructure
* @ticks: number of ticks, that have elapsed since the last call.
*
* Must be called with interrupts disabled.
*/
void xtime_update(unsigned long ticks)
{
write_seqlock(&jiffies_lock);
do_timer(ticks);
write_sequnlock(&jiffies_lock);
}
/*
* Must hold jiffies_lock
*/
void do_timer(unsigned long ticks)
{
jiffies_64 += ticks;
update_wall_time();
calc_global_load(ticks);
}
// 檔案:kernel/sched/core.c
/*
* a1 = a0 * e + a * (1 - e)
*/
static unsigned long
calc_load(unsigned long load, unsigned long exp, unsigned long active)
{
load *= exp;
load += active * (FIXED_1 - exp);
load += 1UL << (FSHIFT - 1);
return load >> FSHIFT;
}
static unsigned long calc_load_update;
/*
* calc_load - update the avenrun load estimates 10 ticks after the
* CPUs have updated calc_load_tasks.
*/
void calc_global_load(unsigned long ticks)
{
long active, delta;
if (time_before(jiffies, calc_load_update + 10))
return;
/*
* Fold the 'old' idle-delta to include all NO_HZ cpus.
*/
delta = calc_load_fold_idle();
if (delta)
atomic_long_add(delta, &calc_load_tasks);
active = atomic_long_read(&calc_load_tasks);
active = active > 0 ? active * FIXED_1 : 0;
avenrun[0] = calc_load(avenrun[0], EXP_1, active);
avenrun[1] = calc_load(avenrun[1], EXP_5, active);
avenrun[2] = calc_load(avenrun[2], EXP_15, active);
calc_load_update += LOAD_FREQ;
/*
* In case we idled for multiple LOAD_FREQ intervals, catch up in bulk.
*/
calc_global_nohz();
}
容器中獲取 Loadavg 方案
如果想在容器中獲取正確 Loadavg 資訊的那麼就要具備以下幾點:
- 獲取執行在容器中的所有程序(包括:執行緒)。
- 獲取執行在容器中的程序總數。
- 獲取執行在容器中的所有程序執行狀態。
- Loadavg 計算公式。
目前 cgroup 已實現了獲取容器中所有的程序 ID,具體如下:
../cgroup/pids/<docker|lxc>/<id>/tasks
922
2038
2545
2546
2547
2548
Cgroup 中也可以獲取執行在容器中的執行程序總數,具體如下:
../cgroup/pids/<docker|lxc>/<id>/pids.current
128
獲取執行在容器中的所有程序後可以通過系統 proc 來獲取程序狀態,具體如下:
cat /proc/2546/status
Name: connmaster
State: S (sleeping)
Tgid: 2546
Ngid: 0
Pid: 2546
PPid: 2545
TracerPid: 0
Uid: 669 669 669 669
Gid: 669 669 669 669
FDSize: 1024
Groups: 669
VmPeak: 4621592 kB
VmSize: 4621592 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 157948 kB
VmRSS: 152624 kB
RssAnon: 149084 kB
RssFile: 3540 kB
RssShmem: 0 kB
VmData: 4606308 kB
VmStk: 136 kB
VmExe: 8860 kB
VmLib: 1972 kB
VmPTE: 600 kB
VmSwap: 0 kB
Threads: 34
SigQ: 0/600000
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: fffffffe7bfa7a25
SigIgn: 0000000000000001
SigCgt: ffffffffffc1fefe
CapInh: 0000001fffffffff
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000001fffffffff
CapAmb: 0000000000000000
Seccomp: 0
Cpus_allowed: 000c,000003fc
Cpus_allowed_list: 2-9,34-35
Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000003
Mems_allowed_list: 0-1
voluntary_ctxt_switches: 88
nonvoluntary_ctxt_switches: 1
或
cat /proc/2546/stat
2546 (connmaster) S 2545 2545 2545 0 -1 1077944320 1279939 0 109 0 568906 239100 0 0 20 0 34 0 448464477 4732510208 38156 18446744073709551615 4194304 13266708 140727468767200 140727468766640 4591875 0 2080012837 1 2143420158 18446744073709551615 0 0 17 6 0 0 12 0 0 15363864 15540872 21028864 140727468774844 140727468774891 140727468774891 140727468777449 0
Loadavg 計算公式上面已得知。
總結
綜合上面分析,只要在瞭解 Loadavg 的實現原理,那麼就可以根據自己需求完成容器中正確獲取 Loadavg 資訊。