ns-3.30でフローモニターを使ってスループットを計算

【　[ns-3.30の使い方](https://qiita.com/dorapon2000/items/72dcc08eb3c8857f5603) → [1](https://qiita.com/dorapon2000/items/5c0c0a399aeee629be63) → [2](https://qiita.com/dorapon2000/items/4b51c1776f65b3fc7d26) → [3](https://qiita.com/dorapon2000/items/87c1037f7da7c1676837) → [4](https://qiita.com/dorapon2000/items/4b95e5658d537c2b5e72) → [5](https://qiita.com/dorapon2000/items/fe8151f7ee6bcc30c72e) → [6](https://qiita.com/dorapon2000/items/494164b004e2f42a4553) → [7]　】

ネットワークシミュレータであるns3の説明をいくつかにわけて投稿しています．この投稿は「7. フローモニター」です．

# 7. フローモニター

ネットワークシミュレーションをする上で，スループットやパケット損失率は間違いなく必要になるでしょう．ns3ではフローモニターという，フローごとのネットワーク統計を取ってくれる便利なモジュールがあります．ここでは，フローモニターの使い方をサンプルコードを使って説明します．

- フローモニターを使ってスループットを測定
- フローモニターの仕組み
- まとめ

### 文献

- https://www.nsnam.org/docs/release/3.30/models/html/flow-monitor.html
- https://gitlab.com/nsnam/ns-3-dev/blob/ns-3.30/examples/tcp/tcp-pacing.cc

## フローモニターを使ってスループットを測定

サンプルコードは[examples/tcp/tcp-pacing.cc](https://gitlab.com/nsnam/ns-3-dev/blob/ns-3.30/examples/tcp/tcp-pacing.cc)です．フローモニター以外の部分は本題ではないため，トポロジーと簡単なシナリオの説明だけにとどめます．

![tcppacing_topology.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32402/bce5f17b-1204-8241-c740-4e34aa0ba60d.png)


2つのノードがP2Pで接続されたシンプルなトポロジー上で，片方からもう片方へ向けてTCPトラッフィックを4秒間流します．リンク帯域は40Gbpsで遅延は0.01ミリ秒です．ただし，TCPペーシング[^1]が有効になっており，上限4Gbpsまでしか流さないため，計測されるスループットは4Gbps以下であることが期待されます．

実行してみましょう．私の環境でシミュレーションが終わるまでに22分かかりました．

```sh
$ cp examples/tcp/tcp-pacing.cc scratch/my-tcp-pacing.cc
$ ./waf my-tcp-pacing
Flow 1 (10.1.1.1 -> 10.1.1.2)
  Tx Packets: 3731227
  Tx Bytes:   2193960408
  TxOffered:  1950.19 Mbps
  Rx Packets: 3731217
  Rx Bytes:   2193954528
  Throughput: 1950.18 Mbps
```

一番下の方にスループットが表示されていますが，これこそフローモニターによって取得したデータをもとに計算した結果です．どうやら4秒間のスループットは2Gbpsのようです．

## フローモニターの仕組み

[tcp-pacing.cc](https://gitlab.com/nsnam/ns-3-dev/blob/ns-3.30/examples/tcp/tcp-pacing.cc)を適当なエディタで開いてください（my-tcp-pacingでもOK）．

![code_flowmonitor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32402/534e8f7c-3742-1a66-c301-ff841a7e8253.png)


フローモニターのコードはSimulator::Run()の前後2箇所です．

### Run()前

```c++:tcp-pacing.cc
  FlowMonitorHelper flowmon;
  Ptr<FlowMonitor> monitor = flowmon.InstallAll ();
```

フローモニターはFlowMonitorHelperを通して使います．InstallAll()によって全てのノードから送信されるフローのデータを集計できます．注意点として，InstallAll()をするのは__Simulator::Run()の前__です．シミュレーションを実行する前に，データを集計するノードをフローモニターに伝えなくてはいけません．また，FlowMonitorHelperを2つ宣言してもいけません．

### Run()後

ここからは__Simulator::Run()の後__に書いてください．フローモニターからデータを取得できるのは，シミュレーションが終わった後です．

```c++:tcp-pacing.cc
  monitor->CheckForLostPackets ();
  Ptr<Ipv4FlowClassifier> classifier = DynamicCast<Ipv4FlowClassifier> (flowmon.GetClassifier ());
  FlowMonitor::FlowStatsContainer stats = monitor->GetFlowStats ();
  for (std::map<FlowId, FlowMonitor::FlowStats>::const_iterator i = stats.begin (); i != stats.end (); ++i)
    {
      Ipv4FlowClassifier::FiveTuple t = classifier->FindFlow (i->first);
```

134〜139行目はフローモニターのおまじないです．仕組みを知りたい方は各オブジェクトのドキュメントを参照してください．forはフローごとに回っており，iからそのフローのデータを取得できます．今回の例だと1TCPフローしかないため，10.1.1.2→10.1.1.1（SYN）と10.1.1.1→10.1.1.2（DATA）で2回for文が回ります．

```c++:tcp-pacing.cc
      if (t.sourceAddress == "10.1.1.2")
        {
          continue;
        }
```

tにはフローiの(sourceAddress, sourcePort, destinationAddress, destinationPort, protocol)情報が入っています．protocolはTCPなら6，UDPなら17です．ここでは，SYN方向のスループットは表示してもしょうがないため，DATA方向のみを表示しています．

```c++:tcp-pacing.cc
      std::cout << "Flow " << i->first  << " (" << t.sourceAddress << " -> " << t.destinationAddress << ")\n";
      std::cout << "  Tx Packets: " << i->second.txPackets << "\n";
      std::cout << "  Tx Bytes:   " << i->second.txBytes << "\n";
      std::cout << "  TxOffered:  " << i->second.txBytes * 8.0 / 9.0 / 1000 / 1000  << " Mbps\n";
      std::cout << "  Rx Packets: " << i->second.rxPackets << "\n";
      std::cout << "  Rx Bytes:   " << i->second.rxBytes << "\n";
      std::cout << "  Throughput: " << i->second.rxBytes * 8.0 / 9.0 / 1000 / 1000  << " Mbps\n";
    }
```

iからデータを取得し表示しています．iはstd::map<FlowId, FlowMonitor::FlowStats>型のタプルであり，後ろにデータ（FlowStats）が入っているため，i->secondでアクセスします．[FlowStats](https://www.nsnam.org/docs/release/3.30/doxygen/structns3_1_1_flow_monitor_1_1_flow_stats.html)からは様々なデータを取得できます．

- [txPackets](https://www.nsnam.org/docs/release/3.30/doxygen/structns3_1_1_flow_monitor_1_1_flow_stats.html#ad2692665c1bb2e79be11bf704c3cc2be) - フローiの送信パケット数合計
- [txBytes](https://www.nsnam.org/docs/release/3.30/doxygen/structns3_1_1_flow_monitor_1_1_flow_stats.html#ad2cfc997073dcd4a723b54c26e4ca2c8) - 送信バイト数合計
- [rxPackets](https://www.nsnam.org/docs/release/3.30/doxygen/structns3_1_1_flow_monitor_1_1_flow_stats.html#a175aa3e372c0e265a7f75f68e5316ee0) - 受信パケット数合計
- [rxBytes](https://www.nsnam.org/docs/release/3.30/doxygen/structns3_1_1_flow_monitor_1_1_flow_stats.html#a9da466d3f73f946521be4a0964c403ad) - 受信パケット数合計
- [delaySum](https://www.nsnam.org/docs/release/3.30/doxygen/structns3_1_1_flow_monitor_1_1_flow_stats.html#ab1bf26cc10f8a0bb4a549d4d47188d15) - end-to-end遅延の合計
- [lostPackets](https://www.nsnam.org/docs/release/3.30/doxygen/structns3_1_1_flow_monitor_1_1_flow_stats.html#a3a711adf30cb63ed20b5b3a8a9d5f5c6) - 喪失パケット数合計
- [timeLastRxPacket](https://www.nsnam.org/docs/release/3.30/doxygen/structns3_1_1_flow_monitor_1_1_flow_stats.html#a96fdd2ad422c4428fe058b905f4e11dd) - 最後のパケットを受信した時刻
- [timeFirstRxPacket](https://www.nsnam.org/docs/release/3.30/doxygen/structns3_1_1_flow_monitor_1_1_flow_stats.html#ac67159b5cc3efc9cb59f48293d9db61c) - 最初のパケットを受信した時刻
- などなど

最後のスループットは以下のように計算されています．

> Mbps = 受信バイト数 × 8bit / 9秒？ / 10^6

ここは謎で，送信時間4秒で割るべきところを，なぜか9で割っています．おそらくサンプルコードが間違っているのだとは思いますが，4秒で割ると4.387Gbpsになり，TCPペーシングで指定した4Gbps以上に送信していたことになります．もしかすると，timeLastRxPacket - timeFirstRxPacketの値をいれると正しい値がでるのかもしれません．

とにかく，フローモニターを使えばスループットやパケット損失率，平均伝搬遅延などを計算できます．

## まとめ

フローモニターは，既存のコードにフローモニターのコードを追加するだけで，簡単にスループットを計算できる便利なモジュールです．トレーシングシステムと組み合わせて，様々なデータを取得できます．

ここまでの記事でns3のいろは一通り説明できたかと思います．次からはいくつかのモジュールをピックアップして説明していきます．



[^1]: TCPのバーストトラフィックを回避するために，転送速度に上限を設ける仕組み．私も気になったので，調べてブログにまとめました．https://dorapon2000.hatenablog.com/entry/2019/09/27/162336