再現可能な負荷平均の計算

再現可能な負荷平均の計算

LA計算を掘り下げています。だから私が見つけたものは次のとおりです。

  1. LAは5秒ごとに指数移動平均を計算します。

     LA(t) = LA(t-1) + EXP_R * (n(t) - LA(t-1))
    

    どこ

    • LA(t-1) - 最後の反復で記録されたLA。
    • EXP_R - 1分、5分、15分に対して事前定義された3つの定数。
    • n(t) - システムのR(実行中) または (中断不可能) プロセス数D
  2. 以下は、これらの操作(およびいくつかの浮動小数点マジック)を実行するカーネルコードです。

    unsigned long avenrun[3];
    
    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);
        }
    }
    
    
    static unsigned long count_active_tasks(void)
    {
        struct task_struct *p;
        unsigned long nr = 0;
    
        read_lock(&tasklist_lock);
        for_each_task(p)
        {
            if ((p->state == TASK_RUNNING ||
                 (p->state & TASK_UNINTERRUPTIBLE)))
                nr += FIXED_1;
        }
        read_unlock(&tasklist_lock);
        return nr;
    }
    
    
    #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 */
    #define EXP_1           1884            /* 1/exp(5sec/1min) as fixed-point */
    #define EXP_5           2014            /* 1/exp(5sec/5min) */
    #define EXP_15          2037            /* 1/exp(5sec/15min) */
    
    #define CALC_LOAD(load,exp,n) \
            load *= exp; \
            load += n*(FIXED_1-exp); \
            load >>= FSHIFT;
    
  3. 同じことをするために単純なbashスクリプトを書いた。

    #!/usr/bin/env bash
    set -euo pipefail
    
    LA_1=0
    LA_5=0
    LA_15=0
    
    EXP_1=0.0800
    EXP_5=0.0165
    EXP_15=0.0055
    
    count() {
        echo $(ps -eostat | grep -E "R|D" | wc -l)
    }
    
    echo "1 min          5 min            15 min"
    
    while true; do
        n=$(($(count) - 1)) # -1 to eliminate `ps` from the result
        LA_1=$(bc -l  <<<"$LA_1  + $EXP_1  * ($n - $LA_1)")
        LA_5=$(bc -l  <<<"$LA_5  + $EXP_5  * ($n - $LA_5)")
        LA_15=$(bc -l <<<"$LA_15 + $EXP_15 * ($n - $LA_15)")
        echo -ne "$LA_1      $LA_5      $LA_15\r"
        sleep 5
    done
    

しかし、私の台本の結果は実際のロサンゼルスとは距離が遠かった。主な違いは、カーネルが呼び出され、count_active_tasks()スクリプトが簡単ですps

Bashでより正確なアクティブジョブ数を取得できますか?それとも私が他の場所で何か間違っているのでしょうか?

UPD:しばらくスクリプトを実行しましたが、結果は次のとおりです。

1 min                      5 min                      15 min
.42342580723140551985      .53553677285166903835      .35305247755440928285

実際のロサンゼルスは次のとおりです。

load average: 0.80, 1.63, 1.54

カーネルのソースコードは、LAを説明するこの記事から入手しました。https://wiki.nix-pro.com/view/Load_average_explained

UPD:私のスクリプトのEXP_R定義はカーネルソースの定義とは異なります。私のスクリプトでは実際には1 - exp_kernelです(ここでexp_kernelはカーネルソースの定義です)。最終要素は同じままであるため、最終結果には影響しません。

答え1

私が使っていた公式のエラーをつかんでくれた@muruに感謝します。これは正確で、結果は非常に正確です。

#!/usr/bin/env bash
set -euo pipefail

LA_1=0
LA_5=0
LA_15=0

EXP_1=0.9200
EXP_5=0.9835
EXP_15=0.9945

count() {
    echo $(ps -eostat | grep -E "R|D" | wc -l)
}

echo "1 min         5 min           15 min"

while true; do
    n=$(($(count) - 1))
    LA_1=$(bc -l   <<<"$LA_1  * $EXP_1 +  $n * (1 - $EXP_1)")
    LA_5=$(bc -l   <<<"$LA_5  * $EXP_5 +  $n * (1 - $EXP_5)")
    LA_15=$(bc -l  <<<"$LA_15 * $EXP_15 + $n * (1 - $EXP_15)")
    echo -ne "$LA_1      $LA_5      $LA_15\r"
    sleep 5
done

関連情報