1. ryuichi1208

    No comment

    ryuichi1208
Changes in body
Source | HTML | Preview

20150307003237.jpg

背景

CPU使用率だったりAverageLoadだったりを見てサーバの負荷度だったりを調査する必要があってそれについていろいろ疑問だった点をまとめました。間違い等あればご指摘お願いします。

https://github.com/torvalds/linux

Load Averageって?

ロードアベレージはシステム全体の負荷状況を表す指標。
「1CPUにおける単位時間あたりの実行待ちとディスクI/O待ちのプロセスの数」で表される。

Linuxカーネルはプロセス1つごとにプロセスディスクリプタを持っている。
Linuxでのプロセスディスクリプタはtask_struct構造体という名前になっていて、プロセスに関する全ての情報が入っています。

以下はtask_structの実装の一部です。

include/linux/sched.h
struct task_struct {

    // コンテキストスイッチ時にスタックポインタ、インストラクションポインタなどを保存する領域
    struct thread_info      thread_info;
    // プロセスの状態
    volatile long           state;
    // 優先度
    int             prio;
    // プロセスの仮想メモリ空間に関する情報(ページテーブルなど)を記録
    struct mm_struct        *mm;

    // 他にもいろいろとプロセスに関する情報を保持している
    int             static_prio;
    int             normal_prio;
    unsigned int            rt_priority;
    int             exit_state;
    int             exit_code;
    int             exit_signal;
}

システムで実行中のプロセスは、IO待ちなどにより一時的に実行を中断している状態などがあります。
以下はプロセスの状態です。

  • TASK_RUNNING: 実行中・実行待ちの状態
  • TASK_STOPPED: 実行中断になった状態
  • TASK_ZOMBIE: ゾンビプロセス
  • TASK_UNINTERRUPTABLE: 割り込み不能な待ち状態
  • TASK_INTERRUPTABLE: 割り込み可能な待ち状態。ハードウェア割り込み、システム資源の解放、シグナルなどの要因により起床

今回取り上げるLoad Averageは上記のうちRUNNINGとUNINTERRUPTABLEの和がLoad Averageとなっています。
アクティブなプロセス数の算出をしている処理。

kernel/sched/loadavg.c
long calc_load_fold_active(struct rq *this_rq, long adjust)
{
    long nr_active, delta = 0;

    nr_active = this_rq->nr_running - adjust;
    nr_active += (long)this_rq->nr_uninterruptible;

    if (nr_active != this_rq->calc_load_active) {
        delta = nr_active - this_rq->calc_load_active;
        this_rq->calc_load_active = nr_active;
    }

    return delta;
}

構造体rqはCPU毎のrunqueueデータ構造を表している
上記で述べた状態のプロセス数などもこの構造体を参照して引っ張り出しています。
※task_struct同様巨大な構造体なので使いそうなのだけ抜き出してます

kernel/sched/sched.h
struct rq {
    /* runqueue lock: */
    raw_spinlock_t      lock;

    unsigned int        nr_running;
    unsigned long       nr_uninterruptible;

    struct task_struct  *curr;
    struct task_struct  *idle;
    struct task_struct  *stop;

Load Averageを確認する

Load Averageは以下のコマンドで確認できます

  • uptime
  • w
  • top
  • sar

uptimeコマンド

システムの稼働時間を見るコマンド

$ uptime
 05:01:16 up 12:52,  2 users,  load average: 0.09, 0.09, 0.04

wコマンド

システムに誰がログインしていて何をしているかを表すコマンド

$ w
 05:00:53 up 12:52,  2 users,  load average: 0.13, 0.10, 0.04
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
ryu      :0       :0               Sat06   ?xdm?   4:59   0.05s /usr/lib/gdm3/gdm-x-session --run-script env GNOME
ryu      pts/1    192.168.1.117    04:59    1.00s  0.10s  0.06s w

topコマンド

システムの様々な統計を同時に表示し変化を監視することができるコマンド

$ top
top - 04:59:28 up 12:50,  2 users,  load average: 0.43, 0.12, 0.04
Tasks: 250 total,   2 running, 248 sleeping,   0 stopped,   0 zombie
%Cpu(s): 10.0 us, 22.0 sy,  0.0 ni, 30.0 id, 18.0 wa,  0.0 hi, 20.0 si,  0.0 st
MiB Mem :   1969.9 total,    265.8 free,    962.5 used,    741.6 buff/cache
MiB Swap:   1425.6 total,   1416.0 free,      9.6 used.    804.2 avail Mem

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  2391 ryu       20   0 2770156 164156  67796 R  31.8   8.1   0:34.09 gnome-shell
 12645 ryu       20   0   35784   3944   3212 R   4.5   0.2   0:00.08 top

sarコマンド

sysstatパッケージに含まれている、システムの統計情報を取得するコマンド
ロードアベレージ以外にも多彩な情報を取得できる。

$ sar -q -s 21:00:00
21時00分01秒   runq-sz  plist-sz   ldavg-1   ldavg-5  ldavg-15
21時05分01秒         1       203      0.19      0.20      0.16
21時10分01秒         0       203      0.21      0.18      0.17
21時15分01秒         2       209      0.11      0.18      0.17
21時20分01秒         1       208      0.38      0.20      0.18
21時25分01秒         0       210      0.10      0.20      0.18
平均値:          1       207      0.20      0.19      0.17

CPU使用率とLoadAverageの違い

算出方法

kernel/sched/loadavg.c
void calc_global_load(unsigned long ticks)
{
    unsigned long sample_window;
    long active, delta;

    sample_window = READ_ONCE(calc_load_update);
    if (time_before(jiffies, sample_window + 10))
        return;

    delta = calc_load_nohz_fold();
    if (delta)
        atomic_long_add(delta, &calc_load_tasks);

    // 現在アクティブなプロセス数を算出
    active = atomic_long_read(&calc_load_tasks);
    active = active > 0 ? active * FIXED_1 : 0;

    // それぞれ 1, 5, 15分間の平均値を格納
    // 実際の計算はcalc_load 関数内で行っている
    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);

    calc_global_nohz();
}

/*
* @param load 前回の Load Average
* @param exp 平均をとる期間ごとに定められた定数
* @param active 現在の Active なプロセス数
*/
static unsigned long
calc_load(unsigned long load, unsigned long exp, unsigned long active)
{
    load *= exp;
    load += active * (FIXED_1 - exp);
    return load >> FSHIFT;
}

余談余談① : カーネルスレッドとは

カーネルの補助的な処理を行うもの。特定のカーネル関数を実行したりする。
カーネルスレッドを生成する際はexecはしていない。ユーザーモードにならずカーネルスレッドとして生成される。

arch/x86/kernel/process.c
int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
        struct pt_regs regs;

        memset(&regs, 0, sizeof(regs));

        regs.si = (unsigned long) fn;
        regs.di = (unsigned long) arg;

#ifdef CONFIG_X86_32
        regs.ds = __USER_DS;
        regs.es = __USER_DS;
        regs.fs = __KERNEL_PERCPU;
        regs.gs = __KERNEL_STACK_CANARY;
#else
        regs.ss = __KERNEL_DS;
#endif

        regs.orig_ax = -1;
        regs.ip = (unsigned long) kernel_thread_helper;
        regs.cs = __KERNEL_CS | get_kernel_rpl();
        regs.flags = X86_EFLAGS_IF | X86_EFLAGS_BIT1;

        /* Ok, create the new process.. */
        return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);
}

余談②:

何度か出てくる用語スピンロックについて。

マルチプロセッサ環境において,各CPUが同じリソースに同時アクセスする際に用いられる排他制御の仕組み。
1つのリソースに対して1つのロック用の変数をメモリー上に展開します。ロック変数を取得できたCPUだけがリソースへアクセスできる。

スピン・ロックは,マルチプロセッサ・システムで手軽に排他制御を実現する方法としてよく利用されています。ただし,他のCPUがアクセス中は他のCPUは処理が実行できずにwait状態になります。故にEBUSYが頻繁に発生すると処理効率が悪くなるためリソース管理も別途必要になる(細かく分けるなど)。

参考:https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%94%E3%83%B3%E3%83%AD%E3%83%83%E3%82%AF

感想

サーバ側の負荷情報が実際どのように算出されれているのかの実装が分かった。
プロセスの状態だったりは意識したりすることが少ないのでこれからも読み進めていきたい。

参考サイト

マルチコア時代のロードアベレージの見方
Load Average はどうやって算出されているのか
LoadAverage(ロードアベレージ)について
torvalds/linux