TCP接続を集約表示するlstfでNetlinkにより実行速度が1.6倍になった

Linuxサーバ上でホスト間コネクションを集約表示するツール lstf をつくった - ゆううきメモ にて紹介したlstfのホスト上のTCPコネクション情報の取得処理において、/proc/net/tcpを読みだす代わりに、Netlinkソケットを利用することで、実行速度が1.6倍になった。lstfのバージョン0.4.0で使えるようになる。

実験

約40,000接続あるWebサーバ上にて、lstfコマンドの実行時間を名前解決時間を含まずに比較する。 実験環境はEC2のc4.2xlarge、Debian 8.10、Linuxカーネル3.16であり、リバースプロキシとしてnginxが動作している。

接続数は次のコマンドよりだいたい40,000接続であることを確認する。

[y_uuki@hoge ~]$ ss -tan | wc -l
39264

/proc/net/tcpを読む実装では、次のように実行時間は約500msであり、

[y_uuki@hoge ~]$ time ./lstf_old -n >/dev/null

real    0m0.532s
user    0m0.432s
sys 0m0.140s

Netlinkソケットを利用する実装では、次のように実行時間は約300msとなっており、約1.6倍の速度向上である。10回実行の平均値をとっても、1.68倍の速度向上となった。

[y_uuki@hoge ~]$ time ./lstf -n > /dev/null

real    0m0.318s
user    0m0.272s
sys 0m0.052s

netlinkを使った同様のパフォーマンス改善については、kernel: add a netlink interface to get information about processes (v2) [LWN.net]が参考になる。 この記事を真似て、perfにより解析したが、有意な結果が得られたなかったため、宿題としたい。

実装

Netlinkは、ユーザ空間のプロセスとカーネルとの通信をソケットインタフェースにより提供する。 ssコマンドやipコマンドを含むiproute2パッケージでは、netlinkを利用することで、デバイスを操作し、カーネル内の情報を取得している。*1 Netlinkソケットからソケットの関する情報を取得するには、Socket Monitoring Interfaceを使う。*2

Socket Monitoring Interfaceは、昔はinetファミリーのみのサポートだったが、Linuxカーネル3.3から様々なソケットタイプをサポートするようになった(raw socketなど, 1,2,3) だいたい新しいものをsock_diag、古いものをinet_diagとしているようにみえる。 それに伴い、若干インタフェースが変更されており、netlinkファミリーとして、TCPDIAG_FAMILYを指定していたところをSOCK_DIAG_BY_FAMILYとして指定したり、ユーザ空間からカーネルに送信するリクエスト構造体がinet_diag_req, inet_diag_req_v2となっている。 CentOS5などの古いカーネルでは、inet_diagのみ利用できる。 lstfでは、古いOSをサポートしたいため、inet_diagを利用した。

ユーザ空間のプログラミングモデルは、大雑把には、まずnetlinkソケットを作成し、Socket Monitoringのためのリクエスト構造体を作成し、netlinkメッセージ構造体として、sendto/sendmsgシステムコールカーネルに送信する。次にrecvmsgなどで受信し、バイト列をパースし必要な情報を得るという流れになる。 Go言語では、netlinkを扱うためのライブラリとして vishvananda/netlinkが有名である。 ただし、vishvananda/netlinkはSocket Monitoring Interfaceを提供していないため、ソケット情報を取得しようと思うと、コア部分の生に近いインタフェースでコードを書くことになる。 vishvananda/netlinkのコア部分(nlパッケージ)を使いつつ、inet_diagをひとしきり実装したが、 最終的には、Goのelastic/gosigarのlinuxパッケージを発見したため、これを利用し実装した。

ちなみに、lstfのv0.4.0には、ソケット情報の取得処理を2回走らせてしまっていたため、その無駄を省いた改善も入っている。これとnetlinkの改善を合わせると、全体で3倍強の速度改善になっている。

参考文献

いただいた反応

*1:一方で、netstatコマンドやifconfigコマンドを含むnet-toolsパッケージはioctlに基づいている

*2:Socket Monitoring Interfaceは、CRIUをサポートされるために加えられた機能らしい。