`systemtap-toolkit` と `stapxx`: データを使って難しい問題を解決する方法は?
API7.ai
December 22, 2022
前回の記事で紹介したように、サーバーサイド開発エンジニアとして、私たちは動的デバッグツールセットの深い学習は行わず、主に使用レベルに留まり、せいぜい簡単な定型スクリプトを書く程度です。CPUキャッシュ、アーキテクチャ、コンパイラなどの低レベルな部分は、パフォーマンスエンジニアの領域です。
OpenRestyには、openresty-systemtap-toolkit
とstapxx
という2つのオープンソースプロジェクトがあります。これらは、NGINXとOpenRestyのリアルタイム分析と診断のためのsystemtapベースのラップツールセットです。これらは、on-CPU、off-CPU、共有ディクショナリ、ガベージコレクション、リクエストレイテンシ、メモリプーリング、コネクションプーリング、ファイルアクセスなどの一般的な機能とデバッグシナリオをカバーしています。
この記事では、これらのツールとその使用方法を概観し、NGINXとOpenRestyのトラブルシューティング問題に遭遇した際に、問題を特定するためのツールを迅速に見つけるのに役立てます。OpenRestyの世界では、これらのツールの使用を学ぶことは、確実に先を行く方法であり、他の開発者とコミュニケーションを取る非常に効果的な方法です。なぜなら、ツールによって生成されるデータは、私たちが言葉で説明できるものよりも正確で詳細だからです。
ただし、OpenRestyバージョン1.15.8
ではLuaJIT GC64モードがデフォルトで有効になっていますが、openresty-systemtap-toolkit
とstapxx
はそれに追随していないため、内部のツールが正しく動作しません。そのため、これらのツールを使用する際は、古い1.13
バージョンのOpenRestyを使用することをお勧めします。
ほとんどのオープンソースプロジェクトの貢献者はパートタイムであり、ツールが動作し続けることを保証する義務はありません。これは、オープンソースプロジェクトを使用する際に注意する必要があります。
例: shared dict
今日の投稿を始めるにあたり、おそらく最も馴染みがあり、最も簡単に始められるツールであるngx-lua-shdict
を例に取り上げます。
ngx-lua-shdict
は、NGINXのshared dict
を分析し、その操作を追跡するツールです。-f
オプションを使用して、dict
とkey
を指定し、shared dict
内のデータを取得できます。--raw
オプションを使用すると、指定したkey
の生の値をエクスポートできます。
以下は、shared dict
からデータを取得するコマンドラインの例です。
# NGINX WorkerのPIDが5050と仮定
$ ./ngx-lua-shdict -p 5050 -f --dict dogs --key Jim --luajit20
Tracing 5050 (/opt/nginx/sbin/nginx)...
type: LUA_TBOOLEAN
value: true
expires: 1372719243270
flags: 0xa
同様に、-w
オプションを使用して、指定したkey
に対するdictの書き込みを追跡できます:
$./ngx-lua-shdict -p 5050 -w --key Jim --luajit20
Tracing 5050 (/opt/nginx/sbin/nginx)...
Hit Ctrl-C to end
set Jim exptime=4626322717216342016
replace Jim exptime=4626322717216342016
^C
このツールがどのように実装されているかを見てみましょう。ngx-lua-shdict
はPerlスクリプトですが、実装はPerlとは関係ありません。Perlは、stap
スクリプトを生成して実行するために使用されています。
open my $in, "|stap $stap_args -x $pid -" or die "Cannot run stap: $!\n";
これをPython、PHP、Go、または好きな言語で書くこともできます。stap
スクリプトのキーポイントは、以下のコード行です:
probe process("$nginx_path").function("ngx_http_lua_shdict_set_helper")
これは、前に説明したprobe
で、ngx_http_lua_shdict_set_helper
関数をプローブしています。この関数の呼び出しは、すべてlua-nginx-module
モジュールのlua-nginx-module/src/ngx_http_lua_shdict.c
ファイルにあります。
static int
ngx_http_lua_shdict_add(lua_State *L)
{
return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_ADD);
}
static int
ngx_http_lua_shdict_safe_add(lua_State *L)
{
return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_ADD
|NGX_HTTP_LUA_SHDICT_SAFE_STORE);
}
static int
ngx_http_lua_shdict_replace(lua_State *L)
{
return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_REPLACE);
}
このようにして、この関数をプローブすることで、共有ディクショナリのすべての操作を追跡できます。
on-CPU, off-CPU
OpenRestyを使用する際に最もよく遭遇する問題は、パフォーマンスに関するものです。パフォーマンスが低い、つまりQPSが低い場合、主に2つのタイプがあります。CPU使用率が高すぎる場合と、CPU使用率が低すぎる場合です。前者のボトルネックは、以前に紹介したパフォーマンス最適化手法を使用していないことが原因である可能性がありますが、後者はブロッキング関数が原因である可能性があります。対応するon-CPUとoff-CPUのフレームグラフは、根本的な原因を特定するのに役立ちます。
Cレベルのon-CPUフレームグラフを生成するには、systemtap-toolkit
のsample-bt
を使用する必要があります。一方、Luaレベルのon-CPUフレームグラフは、stapxx
のlj-lua-stacks
によって生成されます。
sample-bt
の使用方法を例に取り上げます。sample-bt
は、指定した任意のユーザープロセス(NGINXとOpenRestyプロセスだけでなく)のコールスタックをサンプリングするスクリプトです。
たとえば、実行中のNGINX Workerプロセス(PIDが8736
)を5秒間サンプリングするには、以下のコードを使用します:
$ ./sample-bt -p 8736 -t 5 -u > a.bt
WARNING: Tracing 8736 (/opt/nginx/sbin/nginx) in user-space only...
WARNING: Missing unwind data for module, rerun with 'stap -d stap_df60590ce8827444bfebaf5ea938b5a_11577'
WARNING: Time's up. Quitting now...(it may take a while)
WARNING: Number of errors: 0, skipped probes: 24
これにより、結果ファイルa.bt
が出力され、FlameGraphツールセットを使用してフレームグラフを生成できます:
stackcollapse-stap.pl a.bt > a.cbt
flamegraph.pl a.cbt > a.svg
a.svg
は生成されたフレームグラフで、ブラウザで開いて表示できます。ただし、サンプリング期間中は、一定数のリクエストを維持する必要があります。そうでないと、サンプル数が0
の場合、フレームグラフを生成できません。
次に、off-CPUのサンプリング方法を見てみましょう。systemtap-toolkit
のsample-bt-off-cpu
スクリプトを使用します。これはsample-bt
と似ています:
$ ./sample-bt-off-cpu -p 10901 -t 5 > a.bt
WARNING: Tracing 10901 (/opt/nginx/sbin/nginx)...
WARNING: _stp_read_address failed to access memory location
WARNING: Time's up. Quitting now...(it may take a while)
WARNING: Number of errors: 0, skipped probes: 23
stapxx
では、レイテンシを分析するツールはepoll-loop-blocking-distr
で、指定したユーザープロセスをサンプリングし、連続するepoll_wait
システムコール間のレイテンシ分布を出力します。
$ ./samples/epoll-loop-blocking-distr.sxx -x 19647 --arg time=60
Start tracing 19647...
Please wait for 60 seconds.
Distribution of epoll loop blocking latencies (in milliseconds)
max/avg/min: 1097/0/0
value |-------------------------------------------------- count
0 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 18471
1 |@@@@@@@@ 3273
2 |@ 473
4 | 119
8 | 67
16 | 51
32 | 35
64 | 20
128 | 23
256 | 9
512 | 2
1024 | 2
2048 | 0
4096 | 0
この出力から、ほとんどの遅延は1ミリ秒未満ですが、200ミリ秒を超えるものもいくつかあり、これらに注意する必要があります。
アップストリームとフェーズの追跡
OpenRestyのコード自体で発生する可能性のあるパフォーマンス問題に加えて、OpenRestyがcosocket
やproxy_pass
などのアップストリームモジュールを通じてアップストリームサービスと通信する際に、アップストリームサービス自体に大きな遅延がある場合、全体のパフォーマンスに大きな影響を与える可能性があります。
この場合、ngx-lua-tcp-recv-time
、ngx-lua-udp-recv-time
、ngx-single-req-latency
などのツールを使用して分析できます。以下はngx-single-req-latency
の例です。
このツールは、ツールセット内の他のほとんどのツールとは少し異なります。他のツールは、大量のサンプルと統計分析に基づいて、分布に関する数学的な結論を導き出します。一方、ngx-single-req-latency
は、個々のリクエストを分析し、OpenRestyの各フェーズ(rewrite
、access
、content
フェーズなど)およびアップストリームで個々のリクエストに費やされた時間を追跡します。
具体的な例を見てみましょう:
# ./stap++ツールをPATHに追加:
$ export PATH=$PWD:$PATH
# nginxワーカープロセスのpidが27327と仮定
$ ./samples/ngx-single-req-latency.sxx -x 27327
Start tracing process 27327 (/opt/nginx/sbin/nginx)...
POST /api_json
total: 143596us, accept() ~ header-read: 43048us, rewrite: 8us, pre-access: 7us, access: 6us, content: 100507us
upstream: connect=29us, time-to-first-byte=99157us, read=103us
$ ./samples/ngx-single-req-latency.sxx -x 27327
Start tracing process 27327 (/opt/nginx/sbin/nginx)...
GET /robots.txt
total: 61198us, accept() ~ header-read: 33410us, rewrite: 7us, pre-access: 7us, access: 5us, content: 27750us
upstream: connect=30us, time-to-first-byte=18955us, read=96us
このツールは、開始後に最初に遭遇したリクエストを追跡します。出力はopentracing
に非常に似ており、systemtap-toolkit
とstapxx
を、OpenRestyにおける非侵入型のAPM(アプリケーションパフォーマンス管理)と考えることもできます。
まとめ
今日紹介した一般的なツールに加えて、OpenRestyには自然に多くのツールが提供されており、自分で探して学ぶことができます。
OpenRestyと他の開発言語やプラットフォームの間には、トレーシング技術に関して大きな違いがあります。これは、ゆっくりと理解していくことができます。
コードをクリーンで安定した状態に保ち、プローブを追加しないでください。代わりに、外部の動的トレーシング技術を通じてサンプリングしてください。
OpenRestyを使用している間、問題を追跡および分析するためにどのようなツールを使用しましたか?この記事を共有し、一緒に進歩しましょう。