iPerf で測定した結果の集計について

# はじめに

とある案件で、製品に関するネットワークの性能、具体的には、転送速度 (Gbps) や、処理速度 (PPS: Packet Per Second) を測定する機会がありました。
その中では、[iPerf](https://iperf.fr/) を使って測定したのですが、その使い方や集計方法について、調べた内容をまとめておきます。

:::note warn
**注意**
手探り感 Max な内容ですので、「いや、それ間違ってるよ」「正しくはこうだよ」等あれば、ぜひコメントください！
:::

また、実際にやってみた感想として、正直なところ、お金はかかりますが、専用機器を購入する方が、より簡単に、より精度の良い結果を得られると思いました。なので、多少、融通が利かなくても、低コストに測定したいという場合の参考にしていただければと思います。


# iPerf について

iPerf は、IP ネットワーク上において、有効な帯域幅を測定するためのツールです。
主要な Linux ディストリビューションの他、Windows だけでなく、Android や iPhone/iPad にも対応しているようです。

https://iperf.fr/

また、大きく iPerf 2 と iPerf 3 があり、相互の互換性が無く、機能やコマンド オプションにも差異があるため、利用する場合には、どちらのバージョンを使っているのか、意識して利用する必要があります。
具体的な差異については、↓下記の公式ドキュメントの冒頭部分に記載がありました。

https://iperf.fr/iperf-doc.php

今回は、iPerf 2 の方を利用しておりますので、もし、お手元の環境が iPerf 3 の場合には、ご注意ください。


# 前提

今回、測定に使用した VM の Guest OS は、Ubuntu 22.04 LTS Server です。

```test
$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.3 LTS"
```

また、前述の通り、利用した iPerf のバージョンは iPerf 2 になります。

```text
$ iperf --version
iperf version 2.1.5 (3 December 2021) pthreads
```


# 使い方

まず、iPerf は、Server - Client 構成で、測定するツールになります。
先に Server 側において、Server モードで iPerf を実行し、Client 側から Client モードで iPerf を実行することで、測定を開始できます。
そのため、測定したい区間、例えば、物理スイッチやルーター、仮想ネットワーク等を、iPerf の Server と Client で挟むような構成が必要です。

```text
+--------+                          +--------+
| Server | <----(測定したい区間)----> | Client |
+--------+                          +--------+
```



また、測定に利用するプロトコルは、TCP or UDP を (iPerf 3 では SCTP も) 選択でき、それぞれ IPv4 と IPv6 ネットワーク上で実行できます。
TCP と UDP は、Server 実行時および Client 実行に、明示的にオプション指定が必要です。
IPv4 と IPv6 は、Client 実行時に、ターゲットなる Server の IP アドレスの種類で切り替えることになります。iPerf 3 の場合は、オプションで明示的に IPv4 or IPv6 を指定することも可能です。

今回、私が試した範囲ですが、UDP モードは、チューニングが難しく、なかなか高いスループットを出せませんでした。そのため、基本的には、TCP モードで計測することにし、以降の記載も TCP モードの利用を想定しています。
ただし、理由は後述しますが、TCP モードの場合、Linux OS では、ショートパケットのケースで測定できないため、UDP モードで頑張る必要があるケースもありそうです。


## インストール

Ubuntu では、apt パッケージが提供されておりますので、それを使ってインストールします。

```bash
sudo apt install iperf
```

:::note warn
**注意**
iPerf 3 をインストールする場合には、明示的に `iperf3` とパッケージ名で指定する必要があります。
:::


## 実行方法

まず、Server を起動します。

```bash: Server
iperf -s
```

特に指定が無い場合、TCP モードで起動しますが、UDP モードで実行する場合には、さらに `-u` オプションを指定します。

次に、Client を実行します。

```bash: Client
iperf -c SERVER-IP -i INTERVAL -t TIME -P PARALLEL -M MSS
```

```text: 実行例
$ iperf -c 192.168.0.1 -i 10 -P 10 -t 60 -M 1460
------------------------------------------------------------
Client connecting to 192.168.0.1, TCP port 5001
TCP window size: 85.0 KByte (default)
------------------------------------------------------------
[  1] local 192.168.0.2 port 59310 connected with 192.168.0.1 port 5001 (MSS=1448)
[  5] local 192.168.0.2 port 59326 connected with 192.168.0.1 port 5001 (MSS=1448)
[ 10] local 192.168.0.2 port 59338 connected with 192.168.0.1 port 5001 (MSS=1448)
[  6] local 192.168.0.2 port 59362 connected with 192.168.0.1 port 5001 (MSS=1448)
[  4] local 192.168.0.2 port 59298 connected with 192.168.0.1 port 5001 (MSS=1448)
[  7] local 192.168.0.2 port 59376 connected with 192.168.0.1 port 5001 (MSS=1448)
[  3] local 192.168.0.2 port 59350 connected with 192.168.0.1 port 5001 (MSS=1448)
[  9] local 192.168.0.2 port 59380 connected with 192.168.0.1 port 5001 (MSS=1448)
[  8] local 192.168.0.2 port 59364 connected with 192.168.0.1 port 5001 (MSS=1448)
[  2] local 192.168.0.2 port 59300 connected with 192.168.0.1 port 5001 (MSS=1448)
[ ID] Interval       Transfer     Bandwidth
[  2] 0.0000-10.0000 sec  2.63 GBytes  2.26 Gbits/sec
[  9] 0.0000-10.0000 sec  2.46 GBytes  2.12 Gbits/sec
[  1] 0.0000-10.0000 sec  2.46 GBytes  2.11 Gbits/sec
[  5] 0.0000-10.0000 sec  2.56 GBytes  2.20 Gbits/sec
[  3] 0.0000-10.0000 sec  2.69 GBytes  2.31 Gbits/sec
[ 10] 0.0000-10.0000 sec  2.39 GBytes  2.05 Gbits/sec
[  6] 0.0000-10.0000 sec  2.39 GBytes  2.05 Gbits/sec
[  4] 0.0000-10.0000 sec  2.42 GBytes  2.08 Gbits/sec
[  8] 0.0000-10.0000 sec  3.55 GBytes  3.05 Gbits/sec
[  7] 0.0000-10.0000 sec  2.59 GBytes  2.22 Gbits/sec
[SUM] 0.0000-10.0000 sec  26.1 GBytes  22.4 Gbits/sec
...(略)...
```

* `-c SERVER-IP`
    * Server の IP アドレスを指定します。
* `-i INTERVAL`
    * 上記のようなレポートを、何秒毎に出力するかを指定します。
    * 指定しないと、周期的なレポート表示はしません。
* `-t TIME`
    * トラヒックを何秒間かけ続けるかを指定します。
    * デフォルトは、10秒間です。
* `-P PARALLEL`
    * セッションを複数張って、トラヒックを送信するかを指定します。
    * デフォルトは、1本のみです。
* `-M MSS`
    * 送信する TCP パケットのペイロードのサイズを指定します。
    * ドキュメントには、「通常は MTU - 40 Bytes (IP Header + TCP Header 分) だ」とあります。
    * が、実際に送信されるパケットでは、付与される TCP オプションの関係で、指定した値から -12 Bytes されるようです。

```text: 実行例
$ iperf -c 192.168.0.1 -i 10 -P 10 -t 60 -M 1460  <--- 指定した MSS は 1460
------------------------------------------------------------
Client connecting to 192.168.0.1, TCP port 5001
TCP window size: 85.0 KByte (default)
------------------------------------------------------------
[  1] local 192.168.0.2 port 59310 connected with 192.168.0.1 port 5001 (MSS=1448)  <--- 指定した MSS - 12 になっている
...(略)...
```

その他、今回は使いませんでしたが、下記のオプションも便利そうでした。

* `-d`
    * Client -> Server の向きだけでなく、Server -> Client の向きでも、トラヒックをかけます。


## パケットサイズの指定

RFC 2544 Benchmarking Methodology for Network Interconnect Devices 等を参照すると、ネットワーク性能の測定、特に、Ethernet での測定においては、64, 128, 256, 512, 1024, 1280, 1518 (Bytes) を使うべきとされています。
そのため、下記のようなイメージで、よく見かける横軸 (x軸) に Ethernet Frame Size (Bytes)、縦軸 (y軸) にスループットや PPS をとったグラフを書くためには、iPerf で指定する MSS のサイズを、それぞれ指定の Frame Size に合うように計算して、オプション指定してあげる必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245234/2064dd86-2204-cc82-d553-aff9b9582b35.png)

よって、まず、ネットワークを流れるデータの構造を確認していきます。
VLAN や Geneve 等が使われていない、シンプルな Ethernet では、下記のような構造になっているはずです。
TCP Option が、わざわざ強調されて書かれてますが、理由は後述します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245234/e34e58ae-6973-855d-83d1-374194ed15a1.png)

Ethernet Frame Size を、それぞれ 1518, 1280, ... と調整していきますので、

MMS (Bytes) = Ethernet Frame Size - 18 (Ether Header + FCS) - 20 (IP Header) - 20 (TCP Header) 

と、計算していけば、iPerf の `-M MSS` オプションで指定する MSS の値が計算できます。
しかし、`-M MSS` オプションの紹介で記載の通り、実際に送信される TCP Payload のサイズは、TCP Option の関係で、-12 Bytes されています。そのため、後々、PPS を計算するために、その分も加味した値も、あわせて計算しておきます。

計算した結果は、それぞれ下記のようになるはずです。

| Ethernet Frame Size (Bytes) | MTU (Bytes) | MMS (Bytes) | MMS - 12 (Bytes)
|-:|-:|-:|-:|
|1518|1500|1460|1448|
|1280|1262|1222|1210|
|1024|1006|966|954|
|512|494|454|442|
|256|238|198|186|
|(追加) 146|128|88*|76|
|128|110|70|65|
|64|46|6|-6|

ただし、iPerf の TCP モードで実行していく場合、いくつか留意事項があります。

まず、Ethernet Frame Size が 64 Bytes の場合に、実質の TCP Payload のサイズ (MMS - 12) が、マイナスの値になっています。正しい値でしょうか？

実際には、Linux OS を使っている場合、そもそもこのパターンまで、試験を進めることができません。
というのも、Linux OS では、プログラムから指定できる MSS の最小値が 88 Bytes になっています。

https://github.com/torvalds/linux/blob/3d7cb6b04c3f3115719235cc6866b10326de34cd/include/net/tcp.h#L67

```c
/* Minimal accepted MSS. It is (60+60+8) - (20+20). */
#define TCP_MIN_MSS		88U
```

よって、iPerf の TCP モードを使っている場合、RFC 2544 にある Ethernet Frame Size 128, 64 Bytes は試験できないため、上記の表では、最小のパケットサイズとして、Ethernet Frame Size 146 Bytes を追加しています。


# 計算方法

実際に iPerf を実行し、測定結果が得られたら、それらを集計し、ネットワーク性能の限界値、いわゆる Line Rate と比較していきます。


## Gbps -> MPPS 換算

まず、iPerf の TCP モードで得られる結果は、下記のように、基本的には、スループット (Gbps) です。

```text: 実行例
$ iperf -c 192.168.0.1 -i 10 -P 10 -t 60 -M 1460

...(略)...

[  9] 0.0000-60.0260 sec  15.6 GBytes  2.23 Gbits/sec
[  5] 0.0000-60.0247 sec  16.3 GBytes  2.33 Gbits/sec
[  3] 0.0000-60.0243 sec  15.8 GBytes  2.26 Gbits/sec
[  6] 0.0000-60.0251 sec  15.4 GBytes  2.20 Gbits/sec
[  8] 0.0000-60.0251 sec  16.7 GBytes  2.39 Gbits/sec
[  1] 0.0000-60.0260 sec  15.0 GBytes  2.15 Gbits/sec
[ 10] 0.0000-60.0256 sec  16.4 GBytes  2.35 Gbits/sec
[  7] 0.0000-60.0249 sec  15.1 GBytes  2.17 Gbits/sec
[  4] 0.0000-60.0251 sec  14.9 GBytes  2.14 Gbits/sec
[  2] 0.0000-60.0250 sec  15.6 GBytes  2.23 Gbits/sec
[SUM] 0.0000-60.0035 sec   157 GBytes  22.5 Gbits/sec   <--- 最終レポート
[ CT] final connect times (min/avg/max/stdev) = 0.627/1.794/2.292/0.494 ms (tot/err) = 10/0
```

この結果が、Line Rate 達しているか、判断するためには、処理速度の (MPPS: Mega Packet Per Second) に換算し、理論値と比較してみる必要があります。
実際に、上記の例だと、25 Gbps の NIC を使っている環境なのですが、22.5 Gbps となっており、ぱっと見では届いていないように見えます。

Gbps を MPPS に換算する式は下記の通りです。

```math
Rate (MPPS) = \frac{Throughput (Gbps) * 1000 (M/G)}{PacketSize (Bytes) * 8 (bit/Bytes)}
```

ここで、Throughput は、iPerf で得られた結果で良いですが、PacketSize はどうなるでしょうか？Etnernet Frame Size の 1518 Bytes でしょうか？
色々調べた結果、「[iPerf のレポートは TCP のペイロード分のみ](https://github.com/esnet/iperf/issues/809#issuecomment-438683863)」とのことなので、MSS を使えば良い... のですが、`-M MSS` オプションで指定した値ではなく、前述の通り TCP Option の関係で、-12 Bytes されているので、切り詰められた値で計算します。

```math
Rate (MPPS) = \frac{22.5 (Gbps) * 1000 (M/G)}{1448 (Bytes) * 8 (bit/Bytes)} \approx 1.94 (MPPS)
```

測定方法自体、精度が良い方法では無いですし、特に、複数の Server - Client のペアで負荷をかけ、その合計を取っている場合には、より精度が怪しくなるため、有効数字は信用できるものでは無いと考えています。


## Line Rate 計算

次に、比較対象となる理論値 Line Rate を計算していきます。
計算式は、前述の通り、

```math
Rate (MPPS) = \frac{Throughput (Gbps) * 1000 (M/G)}{PacketSize (Bytes) * 8 (bit/Bytes)}
```

となるはずで、Throughput の部分には、理想的に 25 Gbps が出たとして計算します。
一方で、PacketSize に関しては、L1 (物理) レイヤーでのサイズで計算する必要があるため、L2 の Ethernet Frame Size に、Preamble (8 Bytes)、Epilog (1 Byte) と IFG (11 Bytes) の合計 20 Bytes が、追加になりますので、注意が必要です。
もちろん、VLAN や Geneve を利用している場合には、さらにそれらのヘッダ分が追加になるので、加算する必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245234/e34e58ae-6973-855d-83d1-374194ed15a1.png)

これらを考慮して、25 Gbps の NIC を使っている場合で、Line Rate (MPPS) を計算すると、下記のようになります。

|Ethernet Frame Size (Bytes)|Ethernet + Preamble, etc|Line Rate (MPPS)
|-:|-:|-:|
1518|1538|2.03
1280|1300|2.40
1024|1044|2.99
512|532|5.87
256|276|11.32
146|166|18.83
128|148|21.11
64|84|37.20


## 実測値と理論値の比較

前述の通り、そもそも精度が良い測定方法ではないため、厳密な測定を行うには怪しいアプローチになります。

しかし、今回の例ですと、理論値 2.03 MPPS のところ、実測値 1.94 MPPS を計測できました。
まだ iPerf を 1ペアのみの結果ですので、iPerf のペアを増やしていくことで、もう少し理論値に近づけそうな値です。
実際に、2, 3, 4 とペア数を増やしていくと、スループットで 23.5 Gbps、つまり 2.03 MPPS を計測できました。

調査した結果の学びとしては、単純にスループットだけですと、理想的には 25 Gbps が期待されるところ、実測値 23.5 Gbps、つまり 1.5 Gbps 差がある中で、Line Rate に達していると判断しています。iPerf の仕組みを把握していないと、どうしてもこの差を説明しきれず、負荷をかけきれていないことを疑ってしまいますが、iPerf のレポートが、どの部分のデータ転送量を見ているのか、実測値と理論値で、それぞれどの値を使って計算すべきか、注意する必要があることが分かりました。

繰り返しになりますが、試行錯誤しながら調べた内容になりますので、間違い等あれば、ご指摘いただけると助かります！