CPUロードアベレージ

id:naoyaさんが「マルチコア時代のロードアベレージの見方」で書かれているように、Linuxでは/proc/loadavgがカーネルがアプリに情報を提供するインタフェースになっている。例えば、uptimeコマンドはこのファイルからロードアベレージを取得する。

では、いつものようにPlan9ではどうなっているかというと、/proc/loadavgに相当するのが/dev/sysstatである(正確には/proc/statに近いか)。Linuxは何でも/procに入れたがるが、Plan9の場合はデバイスファイルとして提供するのがポリシである。で、このファイルを提供しているのはconsデバイス。次にman cons(3)の該当部分を引用する。

The sysstat file holds 10 numbers: processor number, context switches, interrupts, system calls, page faults, TLB faults, TLB purges, load average, idle time and time spent servicing interrupts. The load average is in units of milli–CPUs and is decayed over time; idle time and interrupt time are percentage units; the others are total counts from boot time. If the machine is a multiprocessor, sysstat holds one line per processor. Writing anything to sysstat resets all of the counts on all processors.

Plan9には、topやvmstatというUNIXで馴染みのあるコマンドはないけど、/dev/sysstatを参照しているのがstatsコマンドである(インストールしたら左上に表示されるモニタプログラム)。コマンドラインのツールがあるかは知らない。ロードアベレージのエントリは単位に注意が必要で、1000ならば1.0という意味だ。

また、書かれている通り、プロセッサごとの出力になるので、ロードアベレージはCPUごとに計算されるようだ(マルチプロセッサマシンで動かしたことがないので確認できない)。id:naoyaさんの書かれているように、Linuxではマルチプロセッサでも、各CPUごとのロードアベレージではなく、全体でまとめた値になる。

ここからはカーネル内の話。

ロードアベレージは、プロセッサごとに用意されるMach構造体のloadメンバに格納されていて、タイマ割込みハンドラの延長で実行されるaccounttime関数で計算される。CPUごとにロードアベレージが計算されるのでは、と上では書いたけど、それがすぐに間違いだと気づく。

9/port/proc.c:
 1512: void
 1513: accounttime(void)
 1514: {

 1539:         /* only one processor gets to compute system load averages */
 1540:         if(m->machno != 0)
 1541:                 return;

実際にロードアベレージを計算しているのは、次の箇所。よくある移動平均で求めるやり方で、そのtick内に実行されたタスク数(nrun)とレディキューにキューイングされているタスク数(nrdy)の和を1/HZずつ加重平均している。マルチプロセッサだとnrunが1以上になり得る。上にも書いたけど、1000倍にスケーリングしている。

 1543:         /*
 1544:          * calculate decaying load average.
 1545:          * if we decay by (n-1)/n then it takes
 1546:          * n clock ticks to go from load L to .36 L once
 1547:          * things quiet down.  it takes about 5 n clock
 1548:          * ticks to go to zero.  so using HZ means this is
 1549:          * approximately the load over the last second,
 1550:          * with a tail lasting about 5 seconds.
 1551:          */
 1552:         n = nrun;
 1553:         nrun = 0;
 1554:         n = (nrdy+n)*1000;
 1555:         m->load = (m->load*(HZ-1)+n)/HZ;