XRay Instrumentation

バージョン:

2016-11-08現在 1

はじめに

XRayは、コンパイラによって挿入された計測ポイントと、計測を動的に有効化および無効化できるランタイムライブラリを組み合わせた関数呼び出しトレースシステムです。

XRayに関するより高度な情報は、XRayホワイトペーパーをご覧ください。

このドキュメントでは、LLVMに実装されているXRayの使用方法について説明します。

LLVMにおけるXRay

XRayは3つの主要な部分で構成されています。

  • コンパイラによって挿入された計測ポイント。

  • ランタイムでのトレースの有効化/無効化のためのランタイムライブラリ。

  • トレースを分析するためのツールスイート。

    注記: 2018年7月25日現在、XRayはLinux上で動作する以下のアーキテクチャでのみ使用可能です: x86_64、arm7(thumbなし)、aarch64、powerpc64le、mips、mipsel、mips64、mips64el、NetBSD: x86_64、FreeBSD: x86_64、OpenBSD: x86_64。

コンパイラによって挿入された計測ポイントは、最終的に生成されたバイナリ内のnopスレッドの形で提供され、これらの計測ポイントを指すエントリを含むxray_instr_mapというELFセクションが含まれています。ランタイムライブラリは、xray_instr_mapのエントリにアクセスし、ランタイムで計測ポイントを上書きできる必要があります。

XRayの使い方

XRayはいくつかの方法で使用できます。

  • C/C++/Objective-C/Objective-C++アプリケーションへのインストルメンテーション。

  • 正しい関数属性を持つLLVM IRの生成。

このセクションの残りの部分では、これらの主な方法と、後でXRayでインストルメントされたバイナリでXRayの動作をカスタマイズする方法について説明します。

C/C++/Objective-Cアプリケーションへのインストルメンテーション

アプリケーションにXRayインストルメンテーションを適用する最も簡単な方法は、clang呼び出しで-fxray-instrumentフラグを有効にすることです。

例:

clang -fxray-instrument ...

デフォルトでは、少なくとも200個の命令を含む(またはループを含む)関数がXRay計測ポイントを取得します。-fxray-instruction-threshold=フラグを使用してその数を調整できます。

clang -fxray-instrument -fxray-instruction-threshold=1 ...

ループ検出は-fxray-ignore-loopsで無効にし、命令のしきい値のみを使用できます。ソースレベルの属性を使用して、バイナリ内の特定の関数を常にインストルメントするか、決してインストルメントしないように指定することもできます。GCCスタイルの属性またはC++11スタイルの属性を使用して実行できます。

[[clang::xray_always_instrument]] void always_instrumented();

[[clang::xray_never_instrument]] void never_instrumented();

void alt_always_instrumented() __attribute__((xray_always_instrument));

void alt_never_instrumented() __attribute__((xray_never_instrument));

バイナリをリンクする際には、XRayランタイムライブラリを手動でリンクするか、-fxray-instrumentフラグを使用してclangで自動的にリンクすることができます。または、コンパイラrtからXRayランタイムライブラリを静的にリンクすることもできます。これらのアーカイブファイルはlibclang_rt.xray-{arch}という名前になり、ここで{arch}はclangでサポートされているニーモニックです(x86_64、arm7など)。

LLVM関数属性

LLVM IRを直接使用している場合は、関数にfunction-instrument文字列属性を追加して、C/C++/Objective-Cソースレベルの属性と同様の効果を得ることができます。

define i32 @always_instrument() uwtable "function-instrument"="xray-always" {
  ; ...
}

define i32 @never_instrument() uwtable "function-instrument"="xray-never" {
  ; ...
}

xray-instruction-threshold属性を設定し、インストルメントする前に関数に含まれるべき命令の数に関する数値文字列値を提供することもできます。

define i32 @maybe_instrument() uwtable "xray-instruction-threshold"="2" {
  ; ...
}

特別なケースファイル

属性は、元のソースファイルに追加する代わりに、特別なケースファイルを使用して付与できます。これを使用して、特定の関数とクラスを、ファイルから常に、決して、または最初の引数ログ付きでインストルメントするようにマークできます。ファイルの形式を以下に示します。

# Comments are supported
[always]
fun:always_instrument
fun:log_arg1=arg1 # Log the first argument for the function

[never]
fun:never_instrument

これらのファイルは、clangへの-fxray-attr-list=フラグを介して提供できます。フラグの複数のインスタンスを使用して、複数のファイルを読み込むことができます。

XRayランタイムライブラリ

XRayランタイムライブラリはcompiler-rtプロジェクトの一部であり、挿入された計測ポイントのパッチ適用とパッチ解除を実行するランタイムコンポーネントを実装しています。clangを使用してバイナリと-fxray-instrumentフラグをリンクすると、XRayランタイムが自動的にリンクされます。

XRayランタイムのデフォルトの実装では、mainが開始する前にXRayインストルメンテーションが有効になります。これは、寿命の短いアプリケーションで機能します。この実装では、すべての関数エントリと終了イベントが記録されるため、結果のトレースに多くのレコードが含まれる可能性があります。

また、デフォルトでは、XRayトレースのファイル名はxray-log.XXXXXXであり、XXXXXXの部分はランダムに生成されます。

これらのオプションはXRAY_OPTIONS環境変数で制御できます。以下にオプションとそのデフォルト値を示します。

オプション

タイプ

デフォルト

説明

patch_premain

bool

false

mainの前に計測ポイントをパッチするかどうか。

xray_mode

const char*

""

mainの前にインストールして初期化するデフォルトモード。

xray_logfile_base

const char*

xray-log.

XRayログファイルのファイル名ベース。

verbosity

int

0

ランタイムの冗長レベル。

XRayランタイムに付属するデフォルトのログ実装を使用しない場合、またはXRayインストルメンテーションの実行時期/方法を制御する場合は、XRay APIを直接使用して実行できます。これを行うには、compiler-rtのxrayディレクトリからxray_log_interface.hを含める必要があります。重要なAPI関数を以下に示します。

  • __xray_log_register_mode(...): 文字列モード識別子に対してログ実装を登録します。実装はxray/xray_log_interface.hで定義されているXRayLogImplのインスタンスです。

  • __xray_log_select_mode(...): インストールするモードを選択します。文字列モード識別子に関連付けられています。__xray_log_register_mode(...)で登録された実装のみ、この関数で選択できます。

  • __xray_log_init_mode(...): この関数を使用すると、インストール済みのログ実装を初期化および再初期化できます。詳細はxray/xray_log_interface.h(XRay compiler-rtのインストールの一部)を参照してください。

ログ実装が初期化されると、__xray_log_finalize()関数を使用して実装を最終化することで「停止」できます。最終化ルーチンは初期化の逆です。最終化されると、__xray_log_flushLog()関数を使用して実装のデータをクリアできます。メモリ内処理をサポートする実装では、__xray_log_set_buffer_iterator(...)を介してデータへのアクセスを提供するイテレーター関数を登録する必要があります。これにより、__xray_log_process_buffers(...)関数を呼び出すコードは、メモリ内のデータを処理できます。

これらすべてはxray/xray_log_interface.hヘッダーでより詳細に説明されています。

基本モード

XRayは、アプリケーションの実行をトレースし、定期的に単一のログに追加する基本的なログモードをサポートしています。このモードは、XRAY_OPTIONS環境変数でxray_mode=xray-basicを設定することでインストール/有効化できます。patch_premain=trueと組み合わせることで、アプリケーションの開始から終了までトレースできます。

__xray_log_select_mode(...)を介してインストールされた他のすべてのモードと同様に、実装は__xray_log_init_mode(...)関数を使用して、モード文字列とフラグオプションを提供することで構成できます。基本モード固有のデフォルトは、XRAY_BASIC_OPTIONS環境変数で提供できます。

フライトデータレコーダーモード

XRayは、アプリケーションが固定量のメモリ相当のイベントのみをキャプチャできるログモードをサポートしています。フライトデータレコーダー(FDR)モードは、固定サイズの循環キューバッファにデータを記録し続け、バッファが最終化されてフラッシュされるまでプログラムでデータを利用できる航空機の「ブラックボックス」と非常によく似ています。アプリケーションでFDRモードを使用するには、XRAY_OPTIONS環境変数でxray_mode変数をxray-fdrに設定します。FDRモード実装への追加オプションは、XRAY_FDR_OPTIONS環境変数で提供できます。プログラムによる構成は、選択/インストール後、__xray_log_init_mode("xray-fdr", <configuration string>)を呼び出すことによって行うことができます。

バッファがディスクにフラッシュされると、結果はXRay FDR形式で記述されたバイナリートレース形式になります。

FDRモードがオンの場合、ログ実装が最終化されるまでメモリバッファの書き込みと再利用を続けます。最終化されると、後でフラッシュして再初期化できます。プログラムでこれを行うには、以下のワークフローに従います。

// Patch the sleds, if we haven't yet.
auto patch_status = __xray_patch();

// Maybe handle the patch_status errors.

// When we want to flush the log, we need to finalize it first, to give
// threads a chance to return buffers to the queue.
auto finalize_status = __xray_log_finalize();
if (finalize_status != XRAY_LOG_FINALIZED) {
  // maybe retry, or bail out.
}

// At this point, we are sure that the log is finalized, so we may try
// flushing the log.
auto flush_status = __xray_log_flushLog();
if (flush_status != XRAY_LOG_FLUSHED) {
  // maybe retry, or bail out.
}

FDRモード実装のデフォルト設定では、基本的なログ実装と同様の名前のログが作成されますが、ログフォーマットは異なります。将来、機能や記録タイプを追加する際にも、すべてのトレース分析ツール(およびトレース読み込みライブラリ)は、FDRモードフォーマットのすべてのバージョンをサポートします。

注記:今後、サポートするログバージョンの更新に伴い、永続的なサポートを約束するものではありません。フォーマットの非推奨化については、開発者メーリングリストで発表および議論されます。

トレース分析ツール

現在、LLVMにはトレース分析ツールの基礎となるものが存在し、tools/llvm-xrayディレクトリにあります。llvm-xrayツールは現在、以下のサブコマンドをサポートしています。

  • extract:バイナリからインストゥルメンテーションマップを抽出し、YAMLとして返します。

  • account:様々なソートオプションと出力フォーマット(CSV、YAML、コンソールフレンドリーなTEXTをサポート)を使用して、基本的な関数呼び出しアカウンティング統計を実行します。

  • convert:XRayログファイルをあるフォーマットから別のフォーマットに変換します。バイナリXRayトレース(基本モードとFDRモードの両方)をYAML、flame-graphフレンドリーなテキストフォーマット、およびChrome Trace Viewer (catapult) <https://github.com/catapult-project/catapult>フォーマットに変換できます。

  • graph:XRayトレースで見つかった関数間の関数呼び出し関係のDOTグラフを生成します。

  • stack:XRayトレースの関数呼び出しのタイムラインから関数呼び出しスタックを再構築します。

これらのサブコマンドは、LLVM配布物と共に配布されるXRayライブラリのコンポーネントを使用しています。それらは以下の通りです。

  • llvm/XRay/Trace.h:サポートされている形式のXRayトレースを、便利なメモリ内表現に簡単にロードするためのトレース読み込みライブラリ。トレースを処理するすべての分析ツールがこの実装を使用します。

  • llvm/XRay/Graph.h:graphサブコマンドが、エッジと頂点に関連付けられた統計を持つ関数呼び出しグラフを簡単に表現するために使用する、準汎用グラフ型。

  • llvm/XRay/InstrumentationMap.h:XRayでインストゥルメント化されたオブジェクトファイルとバイナリ内のインストゥルメンテーションマップを分析するための便利なツール。extractstackサブコマンドはこのライブラリを使用します。

バイナリサイズの最小化

XRayは、function-entryfunction-exitcustomtypedを含むいくつかの異なるインストゥルメンテーションポイントをサポートしています。これらは、-fxray-instrumentation-bundle=フラグを使用して個別に有効にできます。例えば、関数エントリとカスタムポイントのみをインストゥルメント化したい場合は、以下を指定できます。

clang -fxray-instrument -fxray-instrumentation-bundle=function-entry,custom ...

これにより、他のスレッドタイプは完全に省略され、バイナリサイズが削減されます。インストゥルメンテーショングループを使用して、関数のサンプリングされたサブセットのみをインストゥルメント化することもできます。例えば、利用可能な関数の4分の1のみをインストゥルメント化するには、以下を実行します。

clang -fxray-instrument -fxray-function-groups=4

サブセットは、関数名のハッシュに基づいて任意に選択されます。異なるサブセットをサンプリングするには、-fxray-selected-function-group=を0からxray-function-groups-1の範囲のグループ番号と共に指定できます。これらのオプションを組み合わせて使用することで、インストゥルメント化されたサブセットが異なる複数のバイナリを作成できます。実行時にトレースする関数を制御する必要があるだけの場合、XRayランタイムライブラリの__xray_patch_function()メソッドを使用して、必要な関数を個別にパッチおよびアンパッチする方が適切です。

今後の作業

XRayインストゥルメンテーションシステムを中心に構築されたツールセットを拡張するための多くの取り組みが進行中です。

トレース分析ツール

  • XRayトレースからの調査結果を視覚化するツールを統合または開発するための作業が進められています。特に、stackツールは、各コールスタックの時間をグラフ化して調査できる出力フォーマットを生成するように拡張されています。

  • インストゥルメント化されたバイナリが大きい場合、生成されたXRayトレースのサイズが急速に大きくなる可能性があります。分析ツールがトレースを精査し、関連情報のみを提示するための、プルーニング技術とヒューリスティックの統合に取り組んでいます。

より多くのプラットフォーム

より多くのアーキテクチャとオペレーティングシステムへのXRayの移植に関する貢献を期待しています。