
UNIXコンピューティングにおいて、システム負荷とは、コンピュータシステムが実行する計算処理量の尺度です。負荷平均は、一定期間におけるシステム負荷の平均値を表します。通常、過去1分、5分、15分のシステム負荷を表す3つの数値で表示されます。
Unixのロード番号は、 CPUを使用している、またはCPUを待機しているプロセスの数、つまり、準備完了キューまたは実行キューにあるプロセスの数を指します。アイドル状態のコンピュータのロード番号は0です(アイドル状態のプロセスはカウントされません)。実行中のプロセスごとにロード番号が1ずつ増加します。終了するプロセスごとにロード番号が1ずつ減少します。ほとんどのUNIXシステムでは、実行中( CPU使用中)または実行可能(CPU待機中)の状態(状態R)にあるプロセスのみがカウントされます。
Linuxには、「R」状態のプロセスに加えて、割り込み不可能なスリープ状態 (通常はディスクアクティビティを待機している状態、「D」状態) のプロセスも含まれており、I/O システムがビジーまたは停止しているために多くのプロセスがI/Oでブロックされたままになっている場合、著しく異なる結果につながる可能性があります。 [ 1 ]これには、たとえばNFSサーバーの障害やメディア( USB 1.x ストレージ デバイスなど) が遅すぎるためにブロックされているプロセスが含まれます。このような状況では、CPU 使用率の実際の増加を反映しない負荷平均が高くなる可能性があります。ディスク待機は CPU 待機とは異なりますが、ユーザーが待つ必要がある時間を反映しているという考えに基づいています。
最新の UNIX システムでは、ロード アベレージに関してスレッドの扱い方が異なります。一部のシステムでは、ロード アベレージの計算のためにスレッドをプロセスとして扱います。実行待ちの各スレッドは、ロードに 1 加算されます。しかし、他のシステム、特にいわゆるM:N スレッドを実装しているシステムでは、ロードの目的でプロセスを 1 回だけカウントする (スレッドの数に関係なく)、または、ユーザー スレッド スケジューラによってカーネルに現在公開されているスレッドのみをカウントするなど、異なる戦略が使用されています。これは、プロセスに設定されている並行性のレベルに依存する場合があります。Linux では、各スレッドを個別にカウントし、ロードに 1 加算しているようです。[ 2 ]
異なる Unix 系システム間で実行キューの長さを取得する標準的な方法はありませんが、[ a ]一般的に利用可能な方法は、psコマンドの出力を解析し、具体的には を解析して、「R」で始まる行の数を数えることです (「R」状態のプロセスに対応)。必要に応じて、 Linux および FreeBSD では「D」、 macOSでは「U」とラベル付けされた、割り込み不可能な「ディスク」待機状態を追加することもできます。 は、Linux および macOS でスレッドごとの情報を取得するために使用できますが、FreeBSD では使用できません。FreeBSD では、代わりに のオプションを使用します。[ 4 ] (プロセス自体がカウントされるため、報告される負荷が 0 になることはありません。実際の負荷を取得するには、カウントから 1 を減算します。)ps -ax -o stat-M-Hps
特にLinuxでは、procfsファイルには、スケジューリングエンティティ(プロセス/スレッド)がそれぞれ「R」状態と「D」状態にあることを示す/proc/stat2行procs_runningとが含まれています。これを使用して、の代わりに現在の負荷を読み取ることができます。以前と同様に、報告される負荷には現在procfsファイルを読み取っているプログラムが含まれるため、合計から1を引いて真の負荷を取得します。[ 5 ]procs_blockedps
Ferrari らが行ったさまざまな負荷指標の比較研究では、CPU キュー長に基づく CPU 負荷情報は、CPU 使用率と比較して負荷分散においてはるかに優れていると報告されています。CPU キュー長の方が優れている理由は、ホストに大きな負荷がかかっている場合、CPU 使用率が 100% に近くなり、使用率の正確な負荷レベルを反映できないためと考えられます。対照的に、CPU キュー長は CPU の負荷量を直接反映できます。たとえば、キューに 3 つのプロセスがあるシステムと 6 つのプロセスがあるシステムでは、プロセスの待ち時間に関しては明らかに違いがあるものの、どちらも使用率が 100% に近くなる可能性が非常に高いです。[ 6 ]
すべてのUnixおよびUnixライクなシステムは、カーネル内で3つの「ロードアベレージ」数値からなる無次元の指標を生成します。ユーザーは、 Unixシェルから以下のコマンドを実行することで、現在の結果を簡単に照会できます。uptime
$ uptime 14:34:03 up 10:43、4 users、ロードアベレージ: 0.06、0.11、0.09およびコマンドはw、さまざまなグラフィカル ユーザー インターフェイスtopユーティリティと同様に、同じ 3 つのロード アベレージ値を表示します。基盤となるインターフェイスは、1990 年の 4.3BSD-Reno 以降、ほとんどの UNIX システムに存在する C 関数です (ただし、POSIXの一部ではありません)。[ 7 ]特に Linux では、この情報を読み取ることもできます。このファイルには、"R" 状態のプロセスの数、プロセスの総数、および最近作成されたプロセスのプロセス IDに関する即時情報も提供されます。 [ 8 ]getloadavg()/proc/loadavg
システムは、負荷数の指数減衰/加重移動平均として負荷平均を計算します。負荷平均の 3 つの値は、システムの過去 1 分、5 分、および 15 分間の動作を指します。[ 9 ]
数学的に言えば、これら3つの値はすべて、システム起動以来のシステム負荷の平均値です。これらはすべて指数関数的に減衰しますが、減衰速度は異なります。それぞれ1分、5分、15分後に指数関数的にeで減衰します。したがって、1分間の負荷平均は、直近1分間の負荷の63%(より正確には1 - 1/ e )と、起動以来の平均負荷(直近1分を除く)の37%(1/ e)で構成されます。5分間および15分間の負荷平均についても、それぞれ5分間と15分間で同じ63%/37%の比率が計算されます。したがって、1分間の負荷平均には直近60秒間のアクティビティのみが含まれるというのは技術的に正確ではありません。なぜなら、過去のアクティビティの37%が含まれているからです。しかし、直近1分間のアクティビティが大部分を占めていると言うのは正しいです。
CPUバウンドなシングルCPUシステムの場合、ロードアベレージは該当期間におけるシステム利用率の指標と考えることができます。マルチCPUシステムの場合は、比較可能な指標を得るために、ロードをプロセッサ数で割る必要があります。
例えば、シングルCPUシステムにおける「1.73 0.60 7.98」というロードアベレージは、次のように解釈できます。
これは、このシステムが1.73倍の速度であれば、最後の1分間に予定されていたすべての作業を処理できたはずだということを意味する。
4つのCPUを搭載したシステムにおいて、ロードアベレージが3.73であれば、平均して3.73個のプロセスが実行可能な状態にあることを示します。この数は4未満であるため、各プロセスが1つのCPUに割り当てられる可能性があり、過負荷状態は発生していないことがわかります。
Linux システムでは、ロードアベレージはクロックティックごとに計算されるのではなく、周波数設定に基づいて各クロックティックでテストされる変数値によって制御されます。この設定はカーネルクロックティックレートをヘルツHZ(1 秒あたりの回数)で定義し、デフォルト値は 100 です。10ミリ秒の ティック。カーネルアクティビティはこのティック数を使用して自身の時間を計測します。具体的には、calc_load()ロードアベレージを計算する関数(loadavg.h、以前はsched.h)は、名目上LOAD_FREQ (5*HZ+1)ティックごとに実行されます。つまり、ほんの少しだけ超える間隔です。5 秒。
extern unsigned long avenrun []; /* ロードアベレージ */ extern void get_avenrun ( unsigned long * loads , unsigned long offset , int shift );#define FSHIFT 11 /* 精度ビット数 */ #define FIXED_1 (1<<FSHIFT) /* 1.0 を固定小数点として */ #define LOAD_FREQ (5*HZ+1) /* 5 秒間隔 */ #define EXP_1 1884 /* 1/exp(5秒/1分) を固定小数点として */ #define EXP_5 2014 /* 1/exp(5秒/5分) */ #define EXP_15 2037 /* 1/exp(5秒/15分) *//* a1 = a0 * e + a * (1 - e) */ static inline unsigned long calc_load ( unsigned long load , unsigned long exp , unsigned long active ) { unsigned long newload ;newload = load * exp + active * ( FIXED_1 - exp ); if ( active >= load ) newload += FIXED_1 -1 ;return newload / FIXED_1 ; }extern unsigned long calc_load_n ( unsigned long load , unsigned long exp , unsigned long active , unsigned int n );#define LOAD_INT(x) ((x) >> FSHIFT) #define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100)avenrun 配列には、1 分、5 分、15 分の平均が含まれます。このcalc_load()関数は、デフォルトの更新レートに対してロードアベレージを正しく更新しますLOAD_FREQ (5*HZ+1)。[ 10 ] loadavg.c (以前は sched.c) では次のように使用されます。[ 11 ]
void calc_global_load ( void ) { unsigned long sample_window ; long active , delta ;sample_window = READ_ONCE ( calc_load_update ); if ( time_before ( jiffies , sample_window + 10 )) return ;/* * '古い' NO_HZ-delta を折り返して、すべての NO_HZ CPU を含めるようにします。 */ delta = calc_load_nohz_read (); 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 );WRITE_ONCE ( calc_load_update , sample_window + LOAD_FREQ );/* * 複数の LOAD_FREQ 間隔で NO_HZ になった場合 、 * まとめて追いつく。 */ calc_global_nohz (); }NO_HZ CPU の処理方法に注意してください。NO_HZ は、アイドル状態のプロセッサでのスケジューリング クロック割り込みの数を減らすためのモードで、電力効率の向上とクロック ジッタの低減を目的としています。ただし、これによりプロセッサが更新ティックを逃す可能性があります。そのため、タスクのカウントはアトミック操作を使用して行われます。この関数は、calc_global_nohz複数のティックに追いつく必要がある場合の計算を処理し、次の関数を使用します。
/* [1] 等比級数の適用: * n 1 - x^(n+1) * S_n := \Sum x^i = ------------- * i=0 1 - x */ unsigned long calc_load_n ( unsigned long load , unsigned long exp , unsigned long active , unsigned int n ) { return calc_load ( load , fixed_power_int ( exp , FSHIFT , n ), active ); }ここでfixed_power_int(記事には含まれていませんが)、固定小数点演算でexpをn乗します。
負荷平均の「サンプリング」計算は、ある程度一般的な動作です。FreeBSDも、値を 5 秒ごとに更新するだけです。この間隔は通常、特定の瞬間に起動するようにスケジュールされたプロセスを収集しないように、正確ではないとみなされます。これが、上記の Linux コードの「+1」の理由です。FreeBSD では、代わりに間隔に擬似乱数オフセットが追加されます。[ 12 ]
loadavg.hまた、上記の固定小数点計算で 11 ビットの小数部を使用しているため、間隔 ( LOAD_FREQ) をこれ以上小さくすることができないとも述べています。たとえば、2 秒間隔の場合、EXP 値は 1981、2034、2043 となり、使用可能な精度 (0 – 2047) をほぼ飽和させてしまいます。[ 10 ]
リップケ・クラウスは2011年に、「+1」修正だけでは、定期的にスケジュールされたプロセスからのモアレアーティファクトを回避するには不十分であることを示した。彼の実験では、4.61がより良い値であることを示唆している。0.61は黄金比に近く、サンプルポイントを小数秒に分散させるのに役立つ。同時に、4.61は60/13に近いので、5s は整数分数である60 秒が維持されます。[ 13 ] [ 14 ] Ripke の変更はAndroid システムカーネルで一般的ですが、使用されている正確な式 ( 4*HZ+61) は HZ が 100 であることを前提としています。[ 15 ]60*HZ/13は HZ のさまざまな値に対してより適切です。新しい値は次のとおりです。[ 14 ]
#define LOAD_FREQ (60*HZ/13) /* 60/13 ~ 4.61 秒間隔 */ #define EXP_1 1896 /* 1/exp(4.61秒/1分) = 1/exp(1/13) 固定小数点として */ #define EXP_5 2017 /* 1/exp(4.61秒/5分) = 1/exp(1/13/5) */ #define EXP_15 2038 /* 1/exp(4.61秒/15分) = 1/exp(1/13/15) */以前にも述べたように、ほとんどのカーネルは効率性と簡便性のために固定小数点演算を使用してロードアベレージを計算します。しかし、これは達成可能な更新レートと精度を制限します。ユーザー空間から瞬時ロードを取得する方法が確立されているため、ユーザー空間からロードアベレージを計算することも可能になります。以下のコードは、 10秒から1時間までの時間範囲で、更新レートφ ≈ 1.618秒を使用してPythonでこれを実行します。
#!/usr/bin/env python3インポート時間import osfrom datetime import datetimefrom math import exp , logfrom dataclasses import dataclassLOGSYSPERIODS = [ log ( x ) for x in [ 60 , 300 , 900 ]]REFRESH_RATE = (( 5 ** 0.5 ) + 1 ) / 2 # リップケの黄金比周期= [ 10 , 30 , 60 , 120 , 300 , 900 , 1800 , 3600 ]COUNT_DISKWAIT = True # ディスク待機を負荷計算に含めるかどうか@dataclassクラスLoadEntry :平均: floatexp : floatdef initialize_loads ( now : int | float , lavgs : dict [ int , LoadEntry ]):システム負荷平均と瞬間負荷数の線形補間に基づいて負荷平均を初期化します。sys = getloadavg ()傾斜= (( sys [ 0 ] - now ) / 60 , ( sys [ 1 ] - sys [ 0 ]) / 240 , ( sys [ 2 ] - sys [ 1 ]) / 600 )期間が[ 10 , 30 , 60 , 120 , 300 , 900 , 1800 , 3600 ]の場合:exp_factor = exp ( - REFRESH_RATE / period )期間が60未満の場合:est_avg = now + slopes [ 0 ] * ( period - 60 )elif period < 300 :est_avg = sys [ 0 ] + slopes [ 1 ] * ( period - 300 )それ以外:est_avg = sys [ 1 ] + slopes [ 2 ] * ( period - 900 )lavgs [ period ] = LoadEntry ( avg = max ( est_avg , 0 ), exp = exp_factor )def update_loads ( lavgs : dict [ int , LoadEntry ], current_load : int | float ) -> None :for _ 、lavgs.items ( )のエントリ:entry.avg = entry.avg * entry.exp + current_load * ( 1 - entry.exp )if os.name == " posix " :uname = os . uname ()[ 0 ] .より低い()getloadavg = os.getloadavgif uname == "linux" :def get_current_load () -> int :負荷= 0with open ( "/proc/stat" , "r" ) as f :for line in f :行が「procs_running」で始まる場合:load += int ( line . split ()[ 1 ]) # procs_running を読み込むload -= 1 # 自分自身のために1を引くelif line.startswith ( " procs_blocked " ) and COUNT_DISKWAIT :load += int ( line . split ()[ 1 ]) # procs_blocked を読み込むリターンロードそれ以外:PS_THREAD_OPTION = "-H" if os.uname ()[ 0 ] .lower () . endswith ( " bsd" ) else " -M "PS_DISK_WAIT = "U" if os.uname ( )[ 0 ] == "Darwin" else "D "PS_STATES = ( "R" + PS_DISK_WAIT ) if COUNT_DISKWAIT else "R"def get_current_load () -> int :with os.popen ( f " ps { PS_THREAD_OPTION } ax -o stat" , " r" ) as f :states = map ( f , lambda line : line . split ()[ - 1 ]) # 最後の列を取得します。macOS では必須です。return sum ( if state [ 0 ] in PS_STATES else 0 for state in states ) - 1elif os.name == " nt " :# Windowsのパフォーマンスカウンターを使用してキューの長さを取得することが可能です。# 実際、マイクロソフトはCPU使用率に加えて、負荷の指標としてこれを推奨しています。# https://learn.microsoft.com/en-us/biztalk/technical-guides/using-the-performance-analysis-of-logs-pal-tool#processor-queue-length-analysis# これを使用すると、Unix システムと同様の負荷を得ることもできます。from pyperfmon import pyperfmonpm = pyperfmon.pyperfmon ( )ncores = os.cpu_count ( )get_counter = lambda x : pm . getCounter ( x )def get_current_load () -> float :戻る(# CPUを待機しているスレッド(実行中のスレッドではない)get_counter ( r "System\Processor Queue Length" )# CPUを使用しているスレッドのおおよその数+ get_counter ( r "Processor\_Total\% Processor Time" ) * ncores# ディスクI/Oを待機しているスレッド+ get_counter ( r "PhysicalDisk\_Total\Current Disk Queue Length" )COUNT_DISKWAITの場合それ以外の場合は0)def getloadavg () -> tuple [ float , float , float ]:# Windows 用のダミー実装load = get_current_load ()return ( load , load , load )def main ():lavgs : dict [ int , LoadEntry ] = {}current_load = get_current_load ()initialize_loads ( current_load , lavgs )見出し= [ "SYSTIME" , " CURR" ] + [ str ( x ) for x in PERIODS ]print ( " \t " .join ( heading ) )while True :# 再計算する前に必ず印刷するentries = [ f " { datetime . now () . strftime ( '%H:%M:%S' ) } { current_load : .4f } " ]entries += [ f " { entry . avg : .4f } " for entry in lavgs . values ()]print ( " \t " .join ( entries ) , flush = True )# スリープして待機します。これは、新しい行を更新するのにかかる時間が# 睡眠時間に比べてごくわずかです。そうでない場合は、# sleepuntil() 型の計算方法を使用する必要があります。time.sleep ( REFRESH_RATE )# 印刷current_load = get_current_load ()update_loads ( lavgs , current_load )if __name__ == "__main__" :主要()システムパフォーマンスを評価するためのその他のコマンドには、次のものがあります。
uptime –システムの信頼性と負荷平均top –システム全体の概要 htop –対話型プロセスビューアbtop –もう一つのシステム全体ビューツールvmstat – vmstatは、実行可能なプロセスまたはブロックされたプロセス、メモリ、ページング、ブロックI/O、トラップ、およびCPUに関する情報を報告します。dool(以前はdstat)、[ 16 ] –プロセス、メモリ、ページング、ブロックI/O、トラップ、CPUアクティビティに関する既存のすべてのリソースデータを相関させるのに役立ちます。atop iftop –インターフェースごとの対話型ネットワークトラフィックビューアnethogs –プロセスごとの対話型ネットワークトラフィックビューアiotop –対話型I/Oビューア[ 17 ]iostat –ストレージI/O統計情報netstat –ネットワーク統計情報mpstat – CPU統計情報tload –端末のロードアベレージグラフxload – XのロードアベレージグラフLOAD_FREQ (4*HZ+61)loadavg・ウィアーズはDstatの開発を中止しました…