本文主要解決如何在 Linux 容器中正確獲取 Loadavg 信息。linux
咱們 cat /proc/loadavg 時會發現以下值:web
$ > cat /proc/loadavg 0.64 0.81 0.86 3/364 6930
這些值的含義依次爲:docker
平均負載定義:在特定時間間隔內運行隊列中的平均進程數。數組
計算公式:
load(t) = load(t-1) * exp(-5/60R) + n(t) * (1 – exp(5/60R))svg
n(t) 是系統活動的進程數, R 對應一、五、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 分鐘系統運行隊列的長度:atom
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分鐘系統負載;code
avenrun[1] 對應前5分鐘系統負載;
avenrun[2] 對應前15分鐘系統負載;
內核每隔 5秒鐘 更新一次 load average 的值。
在內核中 Loadavg 計算部分與讀取部分是分開的。
注:使用 3.10.107 版本內核源碼
由下列源碼咱們能夠看出,根據輸出格式,LOAD_INT 對應計算的是 load 的整數部分,LOAD__FRAC 計算的是 load 的小數部分。loadavg_proc_show 讀取 get_avenrun 來獲取 一、五、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 信息的那麼就要具有如下幾點:
目前 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 信息。