iostat の値はどこから来るのか


独自研究ですので、間違っていたらご指摘ください。

## デバイスドライバの仕組み

iostat を見る前に、デバイスドライバの層構造を確認しましょう。

```
                 システムコール
  -------------------------------------------
                      VFS
  -------------------------------------------
       ファイルシステム（ext3, xfs など）
  -------------------------------------------
         ブロックデバイス汎用ドライバ            +
  -------------------------------------------    |
   SCSI 上位層（sd*ドライバ、st*ドライバなど）   |
  -------------------------------------------    | iostat で
                  SCSI 中間層                    | 計上
  -------------------------------------------    |
        SCSI 下位層（lpfc ドライバなど）         |
  -------------------------------------------    |
                 物理デバイス                    +
```

LVM や dm-multipath などがあると、さらに層は増えます。
これらの層の実装が複雑に絡み合って I/O 処理が実現されております。

iostat の値の計上はもっぱらブロックデバイス汎用ドライバのレイヤで行われます。

つまり、iostat の項目には、ファイルシステムで要した時間などは一切含まれていないということです。
また、SCSI レイヤなどでの細かい粒度での値も一切記録されていません。

### ブロックデバイス汎用ドライバの仕組み

ブロックデバイスドライバは上位層から受け取った bio （I/O 操作要求の単位）をもとに、
複数の bio からなる request を作成し、request queue に投入します。
（bio を既存の request に結合する場合もあります。）

詳細は「詳解 Linux カーネル」の第 13 章が参考になります。


iostat はここで作成された request を I/O 操作の基本の単位として計算します。

```
                              +-----+     +-----+     +-----+
                              | APP |     | APP |     | APP |
                              +-----+     +-----+     +-----+
   上                            |           |           |
   位                            +-----------+-----------+
   層                                        |
                                             |
  ----------------------------------------- bio ------------------------------------
                                             |
   ブ                                        V
   ロ                     make_request_fn() ---- blk_queue_bio() :
   ッ                           bio から request を新規作成、
   ク   もしくは既存の request へ併合（先頭に併合するなら front_merge, 末尾なら back_merge という）    +
   デ                                        |                                                         :
   バ                                        V                                                         :
   イ        <-----------------------------------------------------------------                        :
   ス        <                         request_queue                                                   :
   汎        <----------+-+----------+-+----------+-+----------+-+-------------                        :
   用        | request  | | request  | | request  | | request  | | request  |                 +        :
   ド        +----------+ +----------+ +----------+ +----------+ +----------+                 :        :
   ラ        |bio|bio|..| |bio|bio|..| |bio|bio|..| |bio|bio|..| |bio|bio|..|                 :        :
   イ        +---+---+--+ +---+---+--+ +---+---+--+ +---+---+--+ +---+---+--+                 :        :
   バ         ↑                                                                    +         :        :
              ｜                                                                    :         :        : await
  ------------｜------------------------------------------------------------------- :         :        :
              ｜                                                                    :         :        :
           ブロック                                                                 :         :        :
   下         ↓                                                                    : wsec/s  : r/s    :
   位        +--------------------------------------------------------------+       : rsec/s  : w/s    :
   層        |                             SCSI                             |       : svctm   :        :
             +--------------------------------------------------------------+       :         :        :
                                            ↑                                      :         :        :
                                       SCSI コマンド                                :         :        :
                                            ↓                                      :         :        :
             +--------------------------------------------------------------+       :         :        :
             |                             DISC                             |       +         +        +
             +--------------------------------------------------------------+

```

## iostat 値はどこから来るのか

以下は iostat の実行例です。

```bash
  # iostat -dx
  Linux 3.10.0-327.el7.x86_64 (******)  2019年05月20日  _x86_64_        (2 CPU)
  
  Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
  fd0               0.00     0.00    0.00    0.00     0.00     0.00     8.00     0.00   57.00   57.00    0.00  57.00   0.00
  sdb               0.00     0.00    0.02    0.00     0.16     0.00    15.40     0.00    1.01    1.01    0.00   0.81   0.00
  sda               0.00     0.15    1.08    1.74    26.28    12.53    27.49     0.02    7.34    2.48   10.35   1.34   0.38
```

これらの値は /proc/diskstats から取得されます。
/proc/diskstats は統計値なので、iostat に出力する際には /proc/diskstats を 2 回採取し、その差から増加量を算出しています。

```bash
  # cat /proc/diskstats
     2       0 fd0 2 0 16 114 0 0 0 0 0 114 114
     8      16 sdb 493 0 7592 500 0 0 0 0 0 399 499
     8      17 sdb1 167 0 2656 168 0 0 0 0 0 160 168
     8      18 sdb2 163 0 2624 176 0 0 0 0 0 158 175
     8       0 sda 26012 29 1266124 64425 42214 3652 606259 436725 2 91582 4875552 487555
     8       1 sda1 2311 0 59398 3163 4 0 4096 85 0 2269 3242
     8       2 sda2 22131 29 1180448 59310 42203 3652 602149 436616 2 88931 482037
     8       3 sda3 163 0 2624 201 0 0 0 0 0 188 200
     8       4 sda4 1316 0 22046 1669 7 0 14 24 0 1343 1690
```

diskstats の各項目の説明は以下にあります。

https://www.kernel.org/doc/Documentation/iostats.txt

### svctm、await について

たとえば、svctm、await は /proc/diskstats の値をもとに、以下のように計算されます。

```
  ■ 単位時間に完了した I/O リクエスト数が 1 以上なら
  
       svctm = tot_ticks / nr_ios
       await = (rd_ticks + wr_ticks) / nr_ios
  
  ■ 単位時間に完了した I/O リクエスト数が 0 なら
  
       svctm = 0.0
       await = 0.0
```
  
* nr_ios    : 単位時間中に完了した I/O リクエスト数
* rd_ticks  : 単位時間中に完了した読み込みリクエストが、生成されてから完了するまでに要した時間
* wr_ticks  : 単位時間中に完了した書き込みリクエストが、生成されてから完了するまでに要した時間
* tot_ticks : 単位時間中にブロックデバイスドライバ層より下のレイヤで I/O に要した時間

### /proc/diskstats 

iostat の各項目を理解するにあたって大切なのは、/proc/diskstats の各項目の値がどのタイミングでどのように増えるかです。
/proc/diskstats の値はもっぱらカーネルのブロックデバイス汎用ドライバ層で計上されます。

※/proc/ 以下は、getdents(2) などで要求されるたび、カーネルがオンデマンドで作成しています。
　いずれも実際に存在するファイルではありません。
　統計情報も、オンデマンドで fake なファイルを作って、カーネルがメモリ上に保持している値を載せて、アプリケーションに返しています。

ソースの調査結果は割愛しますが、以下のように計上されております。

* rd_ticks, wr_ticks ： request 作成時の時刻と、完了時の時刻の差分から計上
* tot_ticks          ： 計上用関数を都度呼び出し、呼び出したときに I/O 中のフラグ（in_flight）が立っていれば計上

図にすると以下のようになります。

```
  上位層                       \                                                  /
                                \                                                /
  ===============================\==============================================/============
                                 bio                                           /
  ブロック                         \                                          /
  デバイス    blk_make_request() request ........ rd_ticks, wr_ticks ......../ blk_finish_request()
  レイヤ                             \                                      /
                                      \                                    /
                                    -----------------                     /
                                       request queue                     /
                                    -----------------                   /
                                         \             +-+             /
                    blk_dequeue_request() \.tot_ticks./   \.tot_ticks./
  =========================================\=========/=====\=========/=================================
  SCSI                                      \       /       \       /
  レイヤ以下                                 \-I/O-/         \-I/O-/

```