linux Loadavg和CPU利用率是如何算出來的

相信很多人都對Linux中top命令裏“load average”這一欄困惑過,到底什麼是Load,Load代表了什麼含義,Load高會有什麼後果?“%CPU”這一欄爲什麼會超過100%,它是如何計算的?
帶着這些問題,我們通過一些測試,來探索下其中的不解之處。

首先,我們通過實驗來大概確定其計算方式:
測試服務器:4核Xeon處理器
測試軟件:MySQL 5.1.40
服務器上除了MySQL沒有運行其他任何非系統自帶軟件。因爲MySQL只能單線程運行單條SQL,所以可以很好的通過增加查詢併發來控制使用的CPU核數。

空載時,top的信息爲:

top – 14:51:47 up 35 days, 4:43, 1 user, load average: 0.00, 0.00, 0.00
Tasks: 76 total, 1 running, 75 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0%us, 0.0%sy, 0.0%ni, 99.5%id, 0.1%wa, 0.2%hi, 0.2%si, 0.0%st

在數據庫中啓動一個大查詢:

top – 15:28:09 up 35 days, 5:19, 3 users, load average: 0.99, 0.92, 0.67
Tasks: 80 total, 1 running, 79 sleeping, 0 stopped, 0 zombie
Cpu0 : 0.0%us, 0.0%sy, 0.0%ni, 96.3%id, 0.0%wa, 1.3%hi, 2.3%si, 0.0%st
Cpu1 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu2 : 98.7%us, 1.3%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu3 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st

同時可以看到%CPU也是在100%

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
877 mysql 15 0 308m 137m 4644 S 99.9 6.8 15:13.28 mysqld

然後開啓第二個大查詢,不久就可以看到top信息的變化,Load到了2:

top – 15:36:44 up 35 days, 5:28, 3 users, load average: 1.99, 1.62, 1.08
Tasks: 80 total, 1 running, 79 sleeping, 0 stopped, 0 zombie
Cpu0 : 0.0%us, 0.0%sy, 0.0%ni, 97.7%id, 0.0%wa, 1.0%hi, 1.3%si, 0.0%st
Cpu1 : 99.0%us, 1.0%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu2 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu3 : 99.0%us, 1.0%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st

也可以觀察到%CPU增加到了200%:

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
877 mysql 15 0 312m 141m 4644 S 199.8 7.0 22:31.27 mysqld

由此可以簡單的做出如下臨時結論:
1. %CPU是由每個核的CPU佔用律之和算出來的。
2. load跟執行的任務數有關
不過要想準確的知道其含義,還是必須從源碼入手。


CPU利用率的計算方法

下載busybox的源碼,在procps目錄下有top.c的源碼,查看第293行附近(1.17.1版),可以看到

if (prev_hist_count) do {
        if (prev_hist[i].pid == pid) {
                cur->pcpu = cur->ticks - prev_hist[i].ticks;
                total_pcpu += cur->pcpu;
                break;
        }
        i = (i+1) % prev_hist_count;
        /* hist_iterations++; */
} while (i != last_i);

這就是計算%CPU的代碼,很明顯total_pcpu就是累加了每個線程對每個核的使用率,所以%CPU的最大值就是核數*100%

而CPU利用率又是怎麼計算的呢,跟蹤代碼可以發現,是從系統的/proc/stat這裏讀取的,這個文件的格式可以參考:http://www.linuxhowtos.org/System/procstat.htm,下面是我筆記本上讀出來的內容。

plx@plinux-Laptop:~/busybox-1.17.1$ cat /proc/stat
cpu 520529 3525 658608 3500749 210662 6650 29698 0 0
cpu0 249045 1936 466387 1624486 136381 308 17051 0 0
cpu1 271483 1588 192221 1876263 74281 6342 12646 0 0
intr 84067574 42497789 41743 0 0 0 0 0 0 1 57928 0 0 7175 0 0 0 477092 24693 0 5 0 183 0 20 0 0 0 12455 821851 745906 10192555 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 142313984
btime 1281403521
processes 6707
procs_running 2
procs_blocked 0
softirq 56932805 0 20168080 9440286 238191 821787 0 10621375 4052209 13257 11577620

cpuN的含義從左到右分別是:user、system、nice、idle、iowait、irq、softirq,具體含義可以看文檔。
在下面幾行的含義是:
“intr”這行給出中斷的信息,第一個爲自系統啓動以來,發生的所有的中斷的次數;然後每個數對應一個特定的中斷自系統啓動以來所發生的次數。
“ctxt”給出了自系統啓動以來CPU發生的上下文交換的次數。
“btime”給出了從系統啓動到現在爲止的時間,單位爲秒。
“processes (total_forks) 自系統啓動以來所創建的任務的個數目。
“procs_running”:當前運行隊列的任務的數目。
“procs_blocked”:當前被阻塞的任務的數目。
那麼CPU利用率可以使用以下方法,先取兩個採樣點,然後計算其差值:

cpu usage=(idle2-idle1)/(cpu2-cpu1)*100 cpu usage=[(user_2 +sys_2+nice_2) - (user_1 + sys_1+nice_1)]/(total_2 - total_1)*100;

這是一段Bash代碼採集利用率的,摘自網絡:

#!/bin/sh
##echo user nice system idle iowait irq softirq
CPULOG_1=$(cat /proc/stat | grep 'cpu ' | awk '{print $2" "$3" "$4" "$5" "$6" "$7" "$8}')
SYS_IDLE_1=$(echo $CPULOG_1 | awk '{print $4}')
Total_1=$(echo $CPULOG_1 | awk '{print $1+$2+$3+$4+$5+$6+$7}')
 
sleep 5
 
CPULOG_2=$(cat /proc/stat | grep 'cpu ' | awk '{print $2" "$3" "$4" "$5" "$6" "$7" "$8}')
SYS_IDLE_2=$(echo $CPULOG_2 | awk '{print $4}')
Total_2=$(echo $CPULOG_2 | awk '{print $1+$2+$3+$4+$5+$6+$7}') 
 
SYS_IDLE=`expr $SYS_IDLE_2 - $SYS_IDLE_1`
 
Total=`expr $Total_2 - $Total_1`
SYS_USAGE=`expr $SYS_IDLE/$Total*100 |bc -l`
 
SYS_Rate=`expr 100-$SYS_USAGE |bc -l`
 
Disp_SYS_Rate=`expr "scale=3; $SYS_Rate/1" |bc`
echo $Disp_SYS_Rate%

還有一段Perl的代碼,也是摘自網絡:

#!/usr/bin/perl
use warnings;
 
$SLEEPTIME=5;
 
if (-e "/tmp/stat") {
	unlink "/tmp/stat";
}
open (JIFF_TMP, ">>/tmp/stat") || die "Can't open /proc/stat file!\n";
open (JIFF, "/proc/stat") || die "Can't open /proc/stat file!\n";
@jiff_0=;
print JIFF_TMP $jiff_0[0] ;
close (JIFF);
 
sleep $SLEEPTIME;
 
open (JIFF, "/proc/stat") || die "Can't open /proc/stat file!\n";  @jiff_1=;
print JIFF_TMP $jiff_1[0];
close (JIFF);
close (JIFF_TMP);
 
@USER=`awk '{print \$2}' "/tmp/stat"`;
@NICE=`awk '{print \$3}' "/tmp/stat"`;
@SYSTEM=`awk '{print \$4}' "/tmp/stat"`;
@IDLE=`awk '{print \$5}' "/tmp/stat"`;
@IOWAIT=`awk '{print \$6}' "/tmp/stat"`;
@IRQ=`awk '{print \$7}' "/tmp/stat"`;
@SOFTIRQ=`awk '{print \$8}' "/tmp/stat"`;
 
$JIFF_0=$USER[0]+$NICE[0]+$SYSTEM[0]+$IDLE[0]+$IOWAIT[0]+$IRQ[0]+$SOFTIRQ[0];
$JIFF_1=$USER[1]+$NICE[1]+$SYSTEM[1]+$IDLE[1]+$IOWAIT[1]+$IRQ[1]+$SOFTIRQ[1];
$SYS_IDLE=($IDLE[0]-$IDLE[1]) / ($JIFF_0-$JIFF_1) * 100;  $SYS_USAGE=100 - $SYS_IDLE;
 
printf ("The CPU usage is %1.2f%%\n",$SYS_USAGE);




Load的計算方法

跟蹤busybox的代碼可以知道,load是從/proc/loadavg中讀取的。
我本機的一次抓取內容如下:

plx@plinux-Laptop:~/busybox-1.17.1$ cat /proc/loadavg
0.64 0.81 0.86 3/364 6930

每個值的含義依次爲:
lavg_1 (0.64) 1-分鐘平均負載
lavg_5 (0.81) 5-分鐘平均負載
lavg_15(0.86) 15-分鐘平均負載
nr_running (3) 在採樣時刻,運行隊列的任務的數目,與/proc/stat的procs_running表示相同意思
nr_threads (364) 在採樣時刻,系統中活躍的任務的個數(不包括運行已經結束的任務)
last_pid(6930) 最大的pid值,包括輕量級進程,即線程。
假設當前有兩個CPU,則每個CPU的當前任務數爲0.64/2=0.32

我們可以在Linux內核中找到loadavg文件的源碼:

tatic 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);
}

以及計算load的代碼:

#define FSHIFT      11          /* nr of bits of precision */
#define FIXED_1     (1<<FSHIFT) /* 1.0 as fixed-point(定點) */
#define LOAD_FREQ   (5*HZ)      /* 5 sec intervals,每隔5秒計算一次平均負載值 */
#define CALC_LOAD(load, exp, n)     \
         load *= exp;               \
         load += n*(FIXED_1 - exp); \
         load >>= FSHIFT;
 
unsigned long avenrun[3];
 
EXPORT_SYMBOL(avenrun);
 
/*
* 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);
        }
}

看了大師的文章,理解了這些代碼。
所以可以明白:Linux的系統負載指運行隊列的平均長度,也就是等待CPU的平均進程數。 Linux的系統負載指運行隊列的平均長度,也就是等待CPU的平均進程數。因爲Linux內禁止浮點運算,因此係統的負載只能通過計算變化的次數這一修正值來計算。Linux內核定義一個長度爲3的雙字數組avenrun,雙字的低11位用於存放負載的小數部分,高21位用於存放整數部分。當進程所耗的 CPU時間片數超過CPU在5秒內能夠提供的時間片數時,內核計算上述的三個負載。負載初始化爲0,假設最近1、5、15分鐘內的平均負載分別爲 load1、load5和load15,那麼下一個計算時刻到來時,內核通過下面的算式計算負載:
load1 -= load1 -* exp(-5 / 60) -+ n * (1 – exp(-5 / 60 ))
load5 -= load5 -* exp(-5 / 300) + n * (1 – exp(-5 / 300))
load15 = load15 * exp(-5 / 900) + n * (1 – exp(-5 / 900))
其中,exp(x)爲e的x次冪,n爲當前運行隊列的長度。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內核源文件shed.c的函數calc_load()計算負載的數學依據。


所以“Load值=CPU核數”,這是最理想的狀態,沒有任何競爭,一個任務分配一個核。
由於數據是每隔5秒鐘檢查一次活躍的進程數,然後根據這個數值算出來的。如果這個數除以CPU的核數,結果高於5的時候就表明系統在超負荷運轉了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章