トレース

Trace は、実行中のプロセスからインストルメンテーション イベントをトレースするための構造化されたログ フレームワークです。

Windows
MacOS
Linux

Trace は、実行中のプロセスからインストルメンテーション イベントをトレースするための構造化されたログ フレームワークです。このフレームワークは、高頻度でトレースされるイベントのストリームを生成するように設計されおり、自己記述型で、簡単に消費して、簡単に共有できます。TraceLog および TraceAnalysis は、このフレームワークの主要な構成モジュールです。Trace は、Unreal Insights で主に利用されています。

Trace の概要

イベント

この完全なシステムは、ランタイム時の発行からその後の解析中の消費までのパイプラインを形成する要素で構成されています。最初の要素は、トレース イベントです。以下のように定義されます。

UE_TRACE_EVENT_BEGIN(LoggerName, EventName[, Flags])
    UE_TRACE_EVENT_FIELD(Type, FieldName)
UE_TRACE_EVENT_END()

EventName パラメータおよび FieldName パラメータはイベントを定義し、イベントが含む必要のあるフィールドを指定します。イベントは「loggers"(ロガー)」によってグループ化されています。これはセマンティックのみの概念で、イベントの名前空間を作成し、トレース ストリームを分析する際のサブスクリプションを容易にします。オプションの Flags は、イベントのトレース方法を変更します。

イベント フラグ

説明

NoSync

デフォルトでは、イベントは他のスレッドでトレースされているイベントと同期されます。NoSync フラグのあるイベントはこの同期をスキップします。分析時に他のスレッドとの一貫性が失われますが、サイズを抑えて、より迅速にトレースすることができます。

フィールド型には、標準の整数型または浮動小数点プリミティブ型 (uint32float など) のほか、配列型や文字列型を使用できます (配列や文字列については後述の「その他」セクションを参照してください)。フィールドとしてネストされた構造体/イベントはサポートされていません。

イベントは、通常、.cpp ファイルのグローバル スコープで定義されます。複数の翻訳ユニットからイベントをトレースする必要がある場合は、UE_TRACE_EVENT_BEGIN_[EXTERN|DEFINE] ペアを使用することができます。

ランタイム時のイベントのログは、次のように実行されます。

UE_TRACE_LOG(RainbowLogger, ZippyEvent, ItvChannel)
    << ZippyEvent.Field0(Value0)
    << ZippyEvent.Field1(BundleValue)
    << ZippyEvent.Field2(Data, Num);
    << ZippyEvent.Field3(String[, Num]);

Channel 「ItvChannel」が有効になっている場合、トレース ストリームに 'RainbowLogger.ZippyEvent' イベントが追加されます。フィールドに値を割り当てる際には、特にログ サイトが再入可能ではないため、追加のイベントをトレースするなどの、サイド エフェクトがないように注意してください。

イベントをトレースする場合、デルタ圧縮またはランレングス圧縮はありません。特定の UE_TRACE_LOG() 呼び出しで書き込まれていない場合でも、定義されたフィールドはすべて存在します。トレースされたイベントは本質的に #pragma pack(1) で宣言された構造体に似ています。デベロッパーは、これらの機能を最大限に活用するために戦略的に考えることをお勧めします。

このシステムは、マクロを多用して大量のボイラー プレートを非表示にし、デベロッパーがコード全体で #if#endif のペアを使用しなくても、トレースがオフのときに定義とログサイトが何もコンパイルされないようにします。

チャンネル

トレースの チャンネル は、ユーザーの関心に基づいてイベントのストリームを制限するのに役立ちます。これにより、ユーザーが観察しようとしているものに最も関連性の高いデータに焦点を当てることで、CPU とメモリの使用効率も向上します。以下の構文を使ってチャンネルを定義します。

UE_TRACE_CHANNEL(ItvChannel);

より具体的なユースケース向けには EXTERN/DEFINE ペアがあります。デフォルトでは、チャンネルは無効になっており、-trace=itv コマンド ライン オプションか Trace::ToggleChannel() API を使用して明示的に選択する必要があります。

チャンネルは、ログ サイトと組み合わせて複数のチャンネルでイベントのトレースを制御できるように、ビット単位の OR 演算子をオーバーライドします。UE_TRACE_LOG(..., ItvChannel|BbcChannel) は、"Itv" と "Bbc" の両方のチャンネルが有効になっている場合にのみイベントを発生させます。

見た目はビット単位の OR 演算子と一致しますが、この演算子の動作は部分的にロジカルな AND 演算に似ています。

分析

新しいイベントを定義し、関連する 1 つまたは複数のチャネルを有効にし、少なくとも 1 つのログサイトを追加したので、イベントを消費して分析できます。それには、TraceAnalysis コード使用します。

アナライザーは IAnalyzer インターフェースから派生し、主要な 2 つのメソッドを実行します。s

  • OnAnalysisBegin はイベントをサブスクライブする。

  • OnEvent はこれらのサブスクリプションを受け取る。

分析で、アナライザーがサブスクライブしたイベントが発生すると、アナライザーの OnEvent メソッドが呼び出されます。イベントのデータへのアクセスは、FEventContext.EventData.Get* メソッドを使用して行います。この API はトレース ストリームの自己記述型の性質、すなわち、トレース ストリームの解釈をバイナリやランタイムコードに依存しないという性質を反映しています。受信した各イベントは、関連付けられているスレッド情報と (非常にまれな状況では) 複数のタイミング情報も含んでいます。

トレース セッションでの分析の実行は、使用するアナライザーを記述し、作成された分析セッションにトレース データを供給することで、簡単に行えます。

struct :IInDataStream { ... } TraceData;
FGeorgeAnalyzer GeorgeAnalyzer;
FAnalysisContext Context;
Context.AddAnalyzer(GeorgeAnalyzer);
FAnalysisProcessor Processor = Context.Process(TraceData)
Processor.Wait();

システム ID は使用可能であるものの、重要なスレッド ID はシステム スレッド ID 同じではありません。これにより、OS がスレッド ID を再利用してしまう可能性を防ぐための特別な処理が不要になります。その結果、あるスレッドから次のスレッドへのシステム ID の再利用が発生する可能性はありますが、トレースからの ID は一意である必要があります。

レコーダー / ストア

TraceAnalysis モジュールは、トレース データを受信、格納、取得するための 1 つのサービスを形成する 2 つのコンポーネントで構成されています。実装では非同期 IO を使用しているため、多くのクライアント (主にインバウンドのトレース データ) に簡単に拡張することができます。ストアを設定して実行するのも次のように、簡単に行えます。

FStoreService::FDesc Desc = {
    StoreDir,     /* directory path on disk */
    RecorderPort, /* -1=off, 0=auto-assign */
    1,            /* Number of IOCP threads */
};
auto Store = TUniquePtr<FStoreService>(FStoreService::Create(Desc));

Recorder (レコーダー) は、トレース受信用の TCP ポートをリッスンします。ランタイムがレコーダーに接続すると、レコーダーはストアで新しいトレースを作成して、受信したデータをこの新しいトレースに中継し始めます。デフォルトではポート 1980 が使用されます。ただし、短いポートを割り当てるように OS に求めることもできます。

ストアの主な役割は、トレースのディレクトリを管理することです。ディレクトリを管理することにより、トレースの新規作成、利用可能なトレースの列挙、特定のトレースのデータの取得を行うことができます。ストアと通信するために構築された REST ライクなプロトコルがあります。このプロトコルは相互運用性を実現するため CBOR に基づいています。FStoreClient は、このプロトコルの実装です。

const auto* Store = GetWellKnownTraceStoreServer();
if (auto Client = FStoreClient::Connect(Store->Ip, Store->Port))
{
    for (int i = 0, n = Client.GetTraceCount(); i < n; ++i)
    {
        const auto* Info = Client.GetTraceInfo(i);
        /* ... */
    }
}

recorder-store コンポーネントはオプションです。ファイルへのトレースをランタイムに求めることができます。Python でレコーダーのようなスクリプトを記述して、ソケット経由でトレース データを受け取ることもできます。

ランタイムの詳細

通常のイベント

イベントが UE_TRACE_LOG() サイトでトレースされると、小さなヘッダとイベントのフィールドの値が現在のスレッドのローカル バッファに書き込まれます。これらの TLS バッファは適度にサイズ設定され、1 つのリストにまとめてリンクされています。Trace のワーカー スレッドはバッファのリストをトラバースし、コミットされたイベント データを送信します (そのため、完璧かつ完全に参照可能です)。

TLS を使用するメリットは、トレースしているスレッド同士が互いを阻害しない点です。ほぼ阻害することはありません。スレッド間の順序は、一部のイベント タイプでは重要で、トレース データの解析時に再構築する必要があります (アドレスの再利用が可能な割り当てイベントやフリー イベントなど)。トレースでは、同期化のためにアトミックに増分する整数、すなわち、各イベントの先頭に付いている 24 ビットのシリアル番号を使用します。デベロッパーは、イベント を設定するときに NoSync フラグを使用して、この機能をオプトアウトできます。そうすることで、関連するパフォーマンスコストの発生を回避できますが、分析中に他のスレッドと調整する機能が削除されます。すべてのイベント タイプは同期化を無効にすることが理想です。

ワーカー スレッド

ワーカー スレッドは、TLS や共有バッファリング スキームからトレースされたイベントを収集したり、重要イベントを圧縮してキャッシュしたり、コマンド制御通信を処理したり、宛先 IO (ソケットやファイル) を管理したりするために使用されます。これは、一定の間隔で実行され、フリー スレッド (例: エンジンのループの一部として開始されるジョブやタスクではないスレッド) です。

場合によっては、ワーカー スレッドでトレース イベントを消費してディスパッチさせる (つまり、Linux でのフォーク) のが適切ではない場合があります。イベントは、初期化時に bUseWorkerThread をオプトアウトすることができます。これは、デフォルトで FPlatformProcess::SupportsMultithreading() から返された値になります。無効になっている場合、Trace::Update() でイベントが消費されるようにトレースを更新するのは、ユーザーが行う必要があります。なお、更新が頻繁でない場合、トレースされたイベントをバッファリングするためのメモリ使用量が増大するので注意してください。デフォルトでは、これはすべて Core の TraceAuxiliary によって処理されます。

Transport (トランスポート)

トレースデータは Transport という名称のパケットで送信されます。各パケットは、イベントがどのスレッドに由来するかを示す内部識別子とサイズで開始します。パケットは、容量が小さすぎて LZ4 形式で圧縮してもメリットがない場合を除き、LZ4 形式で圧縮されています。

これらのパケットは、ランタイムによって 2 つの場所のいずれかに送信されます。つまり、-tracefile= 引数を指定するかまたは Trace::WriteTo 関数でプロシージャルにファイルに送信されるか、または TCP ソケット経由で (-tracehost=/Trace::SendTo()) ホストに送信されます。デフォルトの TCP ポートは 1980 です。

トレース ストリームの先頭には、非構造化メタデータのブロックとともに送信されるハンドシェイク データの小さなブロックがあります。

Command-Control (コマンドコントロール) および Late-connect (遅延接続)

Unreal Insights などのツールでは、データを受信しているトレース システムを制御する必要がある場合があります。このために、ランタイムは初歩的なコマンド/コントロール サーバーを実行します。これはほぼ Trace:: パブリック API を反映しています。TraceAnalysis には、通信を容易に実行できるようにするための次のような単純なクラスがあります。

FControlClient ControlClient;
if (ControlClient.Connect(Bungle.GetHostName())
{
    ControlClient.SendToggleChannel(L"ItvChannel", true);
    ControlClient.SendWriteTo(L"X:/Rainbow.utrace");
}

このシンプルなサーバーはポート 1985 をリッスンします。1985 が使用できない場合はポート番号を変更することができます。ポート番号は、ストリームのハンドシェイクに続くメタデータに埋め込まれています (TraceAnalysis のレコーダーからアクセスできる)。

ポートは (少なくともデフォルトでは) 既知の値であるため、実行中のインスタンスに接続してデータのトレースを開始するように指示することができます。この処理は 遅延接続 として知られています。

その他

配列

単一の可変長のフィールドを未指定サイズの配列として定義することで、トレース イベントに追加することができます。

UE_TRACE_EVENT_BEGIN(BoniLogger, BerkEvent)
    UE_TRACE_EVENT_FIELD(int32[], DruttField)
UE_TRACE_EVENT_END()

配列タイプのフィールドには、フィールドにデータが設定されていない場合、イベントのペイロードにストレージ コストはかかりません。配列データは、トレース ストリーム内のメイン イベントのデータに従い、分析時に再結合されます。配列フィールドをトレースするには、次のように、配列データへのポインタと、配列内の要素の数を示す整数カウントのみが必要です。

UE_TRACE_LOG(BoniLogger, BerkEvent, UpstairsChannel)
    << BerkEvent.DruttField(IntPtr, IntNum);

アタッチメント

当初、トレースは可変長フィールドをサポートしていませんでした。添付ファイルは、システムがイベントに追加する不透明なバイナリ ブロブとして導入されました。アタッチメントの使用は推奨されていません。代わりに、解析時に構造化および反映される利点のある 配列型のフィールド を使用してください。さらに、アタッチメントのサポートは、すべてのログされたイベントで負荷が生じるため、このオーバーヘッドを最適化するために、将来的にはオプトイン コンポーネントに変更される可能性があります。

文字列

トレース イベントでは、UE_TRACE_EVENT_FIELD() でイベント フィールドを宣言する際に、Trace::AnsiString 型または Trace::WideString 型を使用する文字列型フィールドをサポートしています。

UE_TRACE_EVENT_BEGIN(MyLogger, MyEvent)
    UE_TRACE_EVENT_FIELD(Trace::AnsiString, MyFieldA)
    UE_TRACE_EVENT_FIELD(Trace::WideString, MyFieldW)
UE_TRACE_EVENT_END()

文字列型フィールドはプリミティブ型フィールドとほぼ同じように記述されます。ただし、いくつかの追加要素があります。ASCII 型フィールドはワイド文字列を自動的に 7 ビット文字に切り捨て、オプションで文字列長を指定することができます (文字列長が既知である方がパフォーマンスのために望ましい) ます。

UE_TRACE_LOG(MyLogger, MyEvent)
    << MyFieldA(AnAnsiBuffer, [, ExplicitStringLen])
    << MyFieldW(WideName)

制限事項

トレース システムはグローバルであり、一度に 1 つの要素しかトレースできません。エディタで利用できるツールの一部がトレース システムを使用するようになったため、エディタが Unreal Insights の実行中に自動的に接続してトレースすることはなくなりました。エディタをプロファイリングする場合は、-tracehost=127.0.0.1 引数または trace.start インエディタ (プロジェクトのプラグインによって) で開始しなければなりません。

Unreal Engine 4 に関する注意事項

トレースされるイベントには、ワンショット イベントもあります。よくあるのは、CPU の名前など、ID を名前に割り当てるイベントです。一度しかトレースされないイベントのコンテキスト情報は失われてしまうため、残念ながら、トレースを停止して後で再開することができなくなります。このシナリオでは、一般的なインジケーターは、Unreal Insights の "unknown"(不明な) CPU イベントやスレッドとなります。trace.stop コマンドはトレースを停止するだけでなく、この制限を緩和するための方法としてすべてのチャンネルを無効にします。エンジンの今後ののバージョンには、「重要なイベント」の概念があります。それは、トレース接続が確立されると、1 回限りのイベントがキャッシュされて再送信され、ドロップイン/ドロップアウト プロファイリング ワークフローがシームレスにサポートされるというものです。

Unreal Engine のドキュメントを改善するために協力をお願いします!どのような改善を望んでいるかご意見をお聞かせください。
調査に参加する
閉じる