DTrace pid providerを使ったnginxのデバッグ

この文章では読者がnginxの内部とDTraceについての一般的な知識を持っていると仮定します。

--with-debug オプション付きでビルドされたnginxはリクエストの処理についてすでに大量の情報を提供しますが、時にはコードパスの特定の部分をもっとじっくりとトレースしたり同時に他のデバッグ出力を省略したい時があります。DTrace pid provider (Solaris、mac OS Xで利用可能)は、コードの変更が必要なくタスクに役立つため、ユーザランドのプログラムの内部を探検するための便利なツールです。nginxの関数呼び出しをトレースして出力する単純はDTraceスクリプトは次のようになります:

#pragma D option flowindent

pid$target:nginx::entry {
}

pid$target:nginx::return {
}

しかし、DTraceの関数呼び出しのトレーシングの可能性は単に制限された有効な情報を提供するだけではありません。リアルタイムの関数引数の検査は一般的に興味深いものですですが、ちょっと複雑でもあります。次の例は読者にDTraceとDTraceを使ったnginxの挙動の解析の過程に慣れ親しんでもらうことを意図しています。

nginxにDTraceを使う一般的なシナリオの一つは次のものです: リクエストの行とリクエストの時間を記録するためにnginx worker プロセスにアタッチする。アタッチする関数はngx_http_process_request()で、質問の引数は ngx_http_request_t 構造体へのポインタです。そのようなリクエストの記録のためのDTraceスクリプトは次のようにシンプルです:

pid$target::*ngx_http_process_request:entry
{
    this->request = (ngx_http_request_t *)copyin(arg0, sizeof(ngx_http_request_t));
    this->request_line = stringof(copyin((uintptr_t)this->request->request_line.data,
                                         this->request->request_line.len));
    printf("request line = %s\n", this->request_line);
    printf("request start sec = %d\n", this->request->start_sec);
}

上の例のDTraceは ngx_http_process_request構造体についてのいくらかの知識を必要とすることに注意してください。残念ながら、DTarceスクリプトの中で特定の#include ディレクティブを使ってそれを(-C フラグを使って)Cプリプロセッサに渡すことは可能ですが、実際には動作しないでしょう。多くの互いに交差する依存のために、ほとんどすべてのnginxのヘッダファイルがインクルードされなければならないでしょう。それはそれで、configure スクリプトの設定に基づいて、nginxのヘッダはPCRE、OpenSSLと様々なシステムヘッダふぃアルをインクルードするでしょう。理論上は特定のnginxビルドに関するそれら全てのヘッダファイルがDTraceスクリプトの前処理でインクルードされるかも知れませんが、実際にはいくつかのヘッダファイルの未知の構文によってDTraceスクリプトはほぼ間違いなくコンパイルに失敗するでしょう。

上の問題はDTraceスクリプト内に関係する必要な構造体と方定義のみをインクルードすることで解決することができます。DTraceは構造のサイズ、型、フィールドのオフセットを知る必要があります。従って手動でDTraceで使う構造体の定義の最適化を行うことで、依存をもっと減らすことができます。

上のDTraceスクリプトの例を使って、適切に動作するためにどのような構造定義が必要なのかを見てみましょう。

まず、configureで生成されるobjs/ngx_auto_config.hファイルは 様々な#ifdefに影響する多数の定数を定義するため、インクルードする必要があります。結局、ngx_str_t, ngx_table_elt_t, ngx_uint_t などの幾つかの基本的なタイプや定義はDTraceスクリプトの最初に配置する必要があります。これらの定義は小さく、一般的に使われ、頻繁には変更されないだろうものです。

次に、他の構造体への多くのポインタを含むngx_http_process_request_t構造体があります。これらのポインタは実際にはこのスクリプトに関係無く、それらは同じサイズなので、それらを端にvoidポインタに置き換えることができます。とは言っても、定義を変更する代わりに適切なtypedefを追加する方が良いでしょう:

typedef ngx_http_upstream_t     void;
typedef ngx_http_request_body_t void;

最後だからといって重要ではないということではなく、二つメンバ構造体(ngx_http_headers_in_t, ngx_http_headers_out_t)の定義と、コールバック関数の定義と、定数の定義を追加する必要があります。

最終的なDTraceスクリプトはhereからダウンロードすることができます。

次の例はこのスクリプトの実行結果の出力を示しています:

# dtrace -C -I ./objs -s trace_process_request.d -p 4848
dtrace: script 'trace_process_request.d' matched 1 probe
CPU     ID                    FUNCTION:NAME
  1      4 .XAbmO.ngx_http_process_request:entry request line = GET / HTTP/1.1
request start sec = 1349162898

  0      4 .XAbmO.ngx_http_process_request:entry request line = GET /en/docs/nginx_dtrace_pid_provider.html HTTP/1.1
request start sec = 1349162899

同じような手法を使って、読者は他のnginx関数呼び出しのトレースができるに違いありません。

参照

TOP
inserted by FC2 system