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

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

首先,咱們經過實驗來大概肯定其計算方式:
測試服務器:4核Xeon處理器
測試軟件:MySQL 5.1.40
服務器上除了mysql沒有運行其餘任何非系統自帶軟件。由於MySQL只能單線程運行單條SQL,因此能夠很好的經過增長查詢併發來控制使用的CPU核數。mysql

空載時,top的信息爲:linux

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  

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

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,假設最近一、五、15分鐘內的平均負載分別爲 load一、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的時候就代表系統在超負荷運轉了。

相關文章
相關標籤/搜索