ブラウザから任意のプロジェクトにトレースをねじ込む方法

みなさんこんにちは!

私の名前はKostyaです。Yandex.Browserを開発しています。 最近、アカデムゴロドクのノボシビルスクオフィスで小規模なC ++パーティーが開催されました。そこでは、ブラウザの開発に使用するツールや、 忍者OWNERSなど、他の大規模プロジェクトに借りることができるものについて話しました。 開発中は、CPU負荷、メモリ消費、さまざまな操作の実行時間などのパフォーマンスを厳密に監視します。 同時に、さまざまなユーティリティが積極的に使用されますが、ブラウザの内部デバッグツール、たとえば内部ブラウ​​ザ://トレーシングページ(Yandex.Browserの場合、chrome:// ChromiumおよびChromeのトレーシング)も使用されます。

スクリーンショットブラウザー://トレース




要するに、このページでは、さまざまな機能の期間と、プロセス、スレッド、および渡された引数ごとの呼び出し数を追跡できます。 もちろん、これには魔法はありません。これを機能させるには、コードに特別なマクロを配置する必要があります。 これは非常に便利なツールであり、さまざまな問題を見つけることができるようです。 そのようなツールは他のプロジェクトで役に立つかもしれないと思うので、どのように適用できるかを示すことにしました。



なぜこれが必要なのですか?



もちろん、プロジェクトを追跡するためのさまざまなツールがありますが、それらにはすべて大きなマイナスがあります-特定のユーザーが使用することはできませんが、これは問題の理解と診断には非常に重要です。 ブラウザがブラウザで収集する診断://トレースはいつでも収集できます。 バグを繰り返すために特定の期間のみ含めることができ、それ以外の場合は大きなオーバーヘッドは発生しません。



何が必要ですか?



もちろん、まず、ソースコードが必要になります。 概して 、必要なのはその中のbase / trace_eventフォルダーにあり、ベースサブフォルダーの他のコンポーネント、たとえば、時間、スレッド、同期なども使用します。 クロムアセンブリからbase.dllへのリンクを非常に単純かつ単純に行うことにしました。 幸いなことに、労働者は言うまでもなく、自宅のコンピューターにクロムリポジトリもあります。 同じように行きたい人のために、異なるプラットフォーム向けのアセンブリの手順へのリンクがあります。 重要:コンポーネントアセンブリの場合、環境変数GYP_DEFINES = "component = shared_library"を設定する必要があります。

第二に、 ここで取得できるヘッダーファイルが必要です

または、これらの2つのポイントの代わりに、それを簡単にして、ベースからプロジェクトにファイルを追加できます。



トレースを有効にして使用する



まず、トレースを有効にする必要があります。 これを行うには、適用するカテゴリフィルターを作成します。 カテゴリは、監視対象操作のグループの識別子です。 現時点で追跡するグループを選択するには、カテゴリが必要です。 次に、TraceLogクラスのシングルトンでSetEnabledメソッドを呼び出す必要があります。

const char kCategoryName[] = "my_category"; void StartTracing() { base::trace_event::CategoryFilter category_filter(kCategoryName); base::trace_event::TraceLog::GetInstance()->SetEnabled( category_filter, base::trace_event::TraceLog::RECORDING_MODE, base::trace_event::TraceOptions(base::trace_event::RECORD_UNTIL_FULL)); }
      
      





ここで、実行時にTRACE_EVENTマクロを追跡し、適切な場所に追加する困難なタスクを考え出します。 このマクロは、ScopedTracerクラスのオブジェクトをインスタンス化するように展開されます。これは、ご想像のとおり、スコープ内での実行時間をレポートします。 詳細はこちらをご覧ください。

 float SomeHardcoreTask(int max_num) { TRACE_EVENT1(kCategoryName, "SomeHardcoreTask", "max_num", max_num); float x = 1.5f; for (int i = 0; i < max_num; ++i) x *= sin(x) / atan(x) * tanh(x) * sqrt(x); return x; } void SomeHardcoreAsyncTask( base::WaitableEvent* event, const base::Callback<void(float)>& out_cb) { TRACE_EVENT0(kCategoryName, "SomeHardcoreAsyncTask"); for (int i : {20000000, 5000000, 80000000}) out_cb.Run(SomeHardcoreTask(i)); event->Signal(); } void AsyncTaskCb(float x) { } void DoWork() { TRACE_EVENT0(kCategoryName, "DoWork"); base::Thread thread("HardcoreTaskThread"); thread.Start(); base::WaitableEvent event(false, false); thread.message_loop_proxy()->PostDelayedTask( FROM_HERE, base::Bind(&SomeHardcoreAsyncTask, &event, base::Bind(&AsyncTaskCb)), base::TimeDelta::FromSeconds(2)); for (int i : {10000000, 20000000, 50000000}) std::cout << SomeHardcoreTask(i); event.Wait(); }
      
      





最後の部分は残ります-トレースをオフにして、結果をディスクに保存する必要があります。 基本的にコードはここから取られいることをすぐに認めます

 const char kTracingJsonPath[] = "D:\\trace.json"; // -,          . void WriteTraceDataCollected( base::File* output_file, const scoped_refptr<base::RefCountedString>& events_str, bool has_more_events) { output_file->WriteAtCurrentPos(events_str->data().c_str(), events_str->data().length()); if (has_more_events) output_file->WriteAtCurrentPos(",", 1); } //       . void StopTracingAndFlushToDisk() { base::trace_event::TraceLog::GetInstance()->SetDisabled(); base::File output(base::FilePath::FromUTF8Unsafe(kTracingJsonPath), base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); CHECK(output.IsValid()); static const char kStart[] = "{\"traceEvents\":["; static const char kEnd[] = "]}"; output.WriteAtCurrentPos(kStart, strlen(kStart)); base::trace_event::TraceLog::GetInstance()->SetDisabled(); base::trace_event::TraceLog::GetInstance()->Flush( base::Bind(&WriteTraceDataCollected, &output)); output.WriteAtCurrentPos(kEnd, strlen(kEnd)); output.Close(); }
      
      







したがって、必要なものはすべてそこにあり、すべてを1つの山に集めるだけです。

 int main() { StartTracing(); DoWork(); StopTracingAndFlushToDisk(); return 0; }
      
      





すべてのコードをまとめて
 #define NOMINMAX #include <iostream> #include <base/files/file.h> #include <base/synchronization/waitable_event.h> #include <base/trace_event/trace_event.h> namespace { const char kCategoryName[] = "my_category"; const char kTracingJsonPath[] = "D:\\trace.json"; void WriteTraceDataCollected( base::File* output_file, const scoped_refptr<base::RefCountedString>& events_str, bool has_more_events) { output_file->WriteAtCurrentPos(events_str->data().c_str(), events_str->data().length()); if (has_more_events) output_file->WriteAtCurrentPos(",", 1); } void StartTracing() { base::trace_event::CategoryFilter category_filter(kCategoryName); base::trace_event::TraceLog::GetInstance()->SetEnabled( category_filter, base::trace_event::TraceLog::RECORDING_MODE, base::trace_event::TraceOptions(base::trace_event::RECORD_UNTIL_FULL)); } void StopTracingAndFlushToDisk() { base::trace_event::TraceLog::GetInstance()->SetDisabled(); base::File output(base::FilePath::FromUTF8Unsafe(kTracingJsonPath), base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); CHECK(output.IsValid()); static const char kStart[] = "{\"traceEvents\":["; static const char kEnd[] = "]}"; output.WriteAtCurrentPos(kStart, strlen(kStart)); base::trace_event::TraceLog::GetInstance()->SetDisabled(); base::trace_event::TraceLog::GetInstance()->Flush( base::Bind(&WriteTraceDataCollected, &output)); output.WriteAtCurrentPos(kEnd, strlen(kEnd)); output.Close(); } float SomeHardcoreTask(int max_num) { TRACE_EVENT1(kCategoryName, "SomeHardcoreTask", "max_num", max_num); float x = 1.5f; for (int i = 0; i < max_num; ++i) x *= sin(x) / atan(x) * tanh(x) * sqrt(x); return x; } void SomeHardcoreAsyncTask( base::WaitableEvent* event, const base::Callback<void(float)>& out_cb) { TRACE_EVENT0(kCategoryName, "SomeHardcoreAsyncTask"); for (int i : {20000000, 5000000, 80000000}) out_cb.Run(SomeHardcoreTask(i)); event->Signal(); } void AsyncTaskCb(float x) { } void DoWork() { TRACE_EVENT0(kCategoryName, "DoWork"); base::Thread thread("HardcoreTaskThread"); thread.Start(); base::WaitableEvent event(false, false); thread.message_loop_proxy()->PostDelayedTask( FROM_HERE, base::Bind(&SomeHardcoreAsyncTask, &event, base::Bind(&AsyncTaskCb)), base::TimeDelta::FromSeconds(2)); for (int i : {10000000, 20000000, 50000000}) std::cout << SomeHardcoreTask(i); event.Wait(); } } // namespace int main() { StartTracing(); DoWork(); StopTracingAndFlushToDisk(); return 0; }
      
      









結果



その結果、ブラウザで開くことができるtrace.jsonファイルを取得します。//トレースして、次を確認します。





おわりに



ご覧のとおり、クロムから非常に便利な機能を借用して適用することは非常に簡単であることがわかりました。 これに加えて、プロジェクトに何らかの方法で適用できるクロムソースには、多くの興味深いアプローチと単純に既製のソリューションがあることを強調したいと思います。

他の誰かが同様の例を持っている場合、コメントでそれらについて教えてください。 たとえば、Yandex YT分散コンピューティングフレームワークは、ファイルによってわずかに変更されたクロムコールバックを使用していることを自分で追加できます。 また、私はクロムに基づいてクラウドドライブのクライアントを作ったインド人の話も聞いたことがありますが、今はその証拠が見つかりません。



便利なリンク



https://www.chromium.org/developers/design-documents

https://code.google.com/p/chromium/codesearch#chromium/src/base/

https://www.chromium.org/developers/how-tos/trace-event-profiling-tool



All Articles