DTrace pid プロバイダで nginx のデバッグを行う

この記事では、読者が nginx 内部とDTraceに関する基礎的な知識を持っていることを前提としています。

--with-debug オプションを使用してビルドされた nginx はすでにリクエスト処理に関する多くの情報を提供しますが、コードパスの特定の部分をより徹底的にトレースし、同時にデバッグ出力の残りを省略したい場合があります。DTrace pid プロバイダ(Solaris、macOS で利用可能)は、コード変更を必要とせず、このタスクに役立つため、ユーザーランドプログラムの内部を調べるのに役立つツールです。nginx 関数呼び出しをトレースして印刷する単純な DTrace スクリプトは次のようになります。

#pragma D option flowindent

pid$target:nginx::entry {
}

pid$target:nginx::return {
}

ただし、関数呼び出しのトレースに対する DTrace の機能は限られた量の有用な情報を提供するだけです。関数引数のリアルタイム検査は通常、より興味深いですが、もう少し複雑です。以下の例は、読者が DTrace に詳しくなり、DTrace を使用して nginx の動作を分析するプロセスについて理解を深めるのに役立つことを目的としています。

nginx で DTrace を使用する一般的なシナリオの 1 つは次のとおりです。nginx ワーカープロセスにアタッチして、リクエスト行とリクエスト開始時刻を記録します。アタッチする対応する関数は、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_request_t 構造体に関する一定の知識が必要であることに注意してください。残念ながら、DTrace スクリプトで特定の #include ディレクティブを使用して C プリプロセッサ(-C フラグ付き)に渡すことは可能ですが、実際には機能しません。多くの相互依存関係により、nginx ヘッダーファイルはほぼすべて含める必要があります。 nginx ヘッダーは、構成スクリプトの設定に基づいて、PCRE、OpenSSL、およびさまざまなシステムヘッダーファイルを含めます。理論的には、特定の nginx ビルドに関連するすべてのヘッダーファイルが DTrace スクリプトのプリプロセスとコンパイルに含まれる可能性がありますが、実際には、一部のヘッダーファイルの不明な構文が原因で DTrace スクリプトはコンパイルに失敗する可能性がほとんどです。

上記の問題は、関連する必要な構造体と型の定義のみを DTrace スクリプトに含めることで解決できます。DTrace は、構造体、型、およびフィールドオフセットのサイズを知る必要があります。したがって、DTrace で使用する構造体定義を手動で最適化することで、依存関係をさらに削減できます。

上記の DTrace スクリプトの例を使用して、適切に機能するために必要な構造体定義を見てみましょう。

まず、configure によって生成された objs/ngx_auto_config.h ファイルを含める必要があります。このファイルは、さまざまな #ifdef に影響を与える多くの定数を定義するためです。その後、ngx_str_tngx_table_elt_tngx_uint_t など、いくつかの基本的な型と定義を DTrace スクリプトの先頭に置く必要があります。これらの定義はコンパクトで、一般的に使用され、頻繁に変更される可能性が低いです。

次に、他の構造体への多くのポインタを含む ngx_http_request_t 構造体があります。これらのポインタはこのスクリプトにはまったく無関係であり、同じサイズであるため、それらは単に void ポインタに置き換えることができます。定義を変更するのではなく、適切な typedef を追加することをお勧めします。

typedef ngx_http_upstream_t     void;
typedef ngx_http_request_body_t void;

最後に、2 つのメンバー構造(ngx_http_headers_in_tngx_http_headers_out_t)、コールバック関数の宣言、定数の定義を追加する必要があります。

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

次の例は、このスクリプトを実行したときの出力を示しています。

# 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 関数呼び出しをトレースできます。

関連項目