LoadとCPUの利用率はどのように計算したのですか


多くの人がLinuxのトップコマンドの「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

そして2番目の大きなクエリーを開くと、間もなく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利用率は、2つのサンプリングポイントを先に取り、その差分値を算出するために、以下の方法を用いることができる.
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!
"; open (JIFF, "/proc/stat") || die "Can't open /proc/stat file!
"; @jiff_0=; print JIFF_TMP $jiff_0[0] ; close (JIFF); sleep $SLEEPTIME; open (JIFF, "/proc/stat") || die "Can't open /proc/stat file!
"; @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%%
",$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値は、軽量レベルのプロセス、すなわちスレッドを含む.現在2つの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
", 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の2ワード配列avenrunを定義し、2ワードの低い11ビットは負荷の小数部を格納し、高い21ビットは整数部を格納する.プロセスが消費するCPUタイムスライス数がCPUが5秒以内に提供できるタイムスライス数を超えると、カーネルは上記の3つの負荷を計算する.負荷は0に初期化され、最近の1、5、15分の平均負荷がload 1、load 5、load 15であると仮定すると、次の計算時刻が到来すると、カーネルは次の計算式によって負荷を計算する.
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の指数分布に従うと考え,指数分布の確率密度はカーネル計算負荷load 1を例に,隣接する2つの計算時刻間のシステム活動のプロセス集合をS 0とする.1分前から現在の計算時刻までの間に活動するload 1プロセスは、彼らの集合をS 1とし、カーネルが考える確率密度は:λe-λx,現在の時点で活動するn個のプロセスは,それらの集合をSnコアが考える確率密度が1−であるとする.λe-λx.ここで、x=5/60は、隣接する2つの計算時刻間のプロセスが消費するCPU時間が5秒であるのに対し、考慮される期間は1分(60秒)である.では、最近の1分間のシステム実行キューの長さを求めることができます.
load1 = |S1| -* λe-λx + |Sn| * (1-λe-λx) = load1 * λe-λx + n * (1-λe-λx)

内λ = 1,x=5/60,|S 1|および|Sn|は、Linuxカーネルソースファイルshedである集合要素の個数である.cの関数calc_load()計算負荷の数学的根拠.
したがって、「Load値=CPUコア数」は、競合することなく、1つのタスクに1つのコアが割り当てられる最適な状態です.
データは5秒ごとにアクティブなプロセス数をチェックし、この数値から算出します.
この数をCPUのコア数で除算すると,結果が5より高い場合にはシステムが過負荷で稼働していることを示す.