トリガーベースのプロファイリング

ProfilingManager は、システム トリガーに基づくプロファイルのキャプチャをサポートしています。システムは記録プロセスを管理し、結果のプロファイルをアプリに提供します。

トリガーはパフォーマンスが重要なイベントに関連付けられています。システム記録プロファイルは、これらのトリガーに関連付けられたクリティカル ユーザー ジャーニー(CUJ)の詳細なデバッグ情報を提供します。

過去のデータを取得する

多くのトリガーでは、イベントまでの過去のデータを分析する必要があります。トリガー自体は、根本原因ではなく、問題の結果であることがよくあります。トリガーが発生した後にのみプロファイリングを開始すると、根本原因がすでに失われている可能性があります。

たとえば、UI スレッドで長時間実行オペレーションを行うと、アプリケーション応答なし(ANR)エラーが発生します。システムが ANR を検出してアプリに通知するまでに、オペレーションが完了している可能性があります。その時点でプロファイルを開始すると、実際のブロック作業が欠落します。

一部のトリガーがいつ発生するかを正確に予測することは不可能であるため、事前にプロファイルを起動することはできません。

トリガーベースのキャプチャを使用する理由

プロファイリング トリガーを使用する主な理由は、アプリがイベントの発生前に手動で記録を開始できない予測不可能なイベントのデータをキャプチャするためです。プロファイリング トリガーは、次の目的で使用できます。

  • パフォーマンスの問題をデバッグする: ANR、メモリリーク、その他の安定性の問題を診断します。
  • クリティカル ユーザー ジャーニーを最適化する: アプリの起動など、フローを分析して改善します。
  • ユーザー行動を把握する: ユーザーがアプリを終了したなどのイベントに関する分析情報を取得します。

トリガーを設定する

次のコードは、TRIGGER_TYPE_APP_FULLY_DRAWN トリガーを登録してレート制限を適用する方法を示しています。

Kotlin

fun recordWithTrigger() {
    val profilingManager = applicationContext.getSystemService(ProfilingManager::class.java)

    val triggers = ArrayList<ProfilingTrigger>()

    val triggerBuilder = ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN)
        .setRateLimitingPeriodHours(1)

    triggers.add(triggerBuilder.build())

    val mainExecutor: Executor = Executors.newSingleThreadExecutor()

    val resultCallback = Consumer<ProfilingResult> { profilingResult ->
        if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.resultFilePath
            )
            setupProfileUploadWorker(profilingResult.resultFilePath)
        } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode=" + profilingResult.errorCode + " errormsg=" + profilingResult.errorMessage
            )
        }
    }

    profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

Java

public void recordWithTrigger() {
  ProfilingManager profilingManager = getApplicationContext().getSystemService(
      ProfilingManager.class);
  List<ProfilingTrigger> triggers = new ArrayList<>();
  ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder(
      ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN);
  triggerBuilder.setRateLimitingPeriodHours(1);
  triggers.add(triggerBuilder.build());

  Executor mainExecutor = Executors.newSingleThreadExecutor();
  Consumer<ProfilingResult> resultCallback =
      new Consumer<ProfilingResult>() {
        @Override
        public void accept(ProfilingResult profilingResult) {
          if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.getResultFilePath());
            setupProfileUploadWorker(profilingResult.getResultFilePath());
          } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode="
                    + profilingResult.getErrorCode()
                    + " errormsg="
                    + profilingResult.getErrorMessage());
          }
        }
      };
  profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback);
  profilingManager.addProfilingTriggers(triggers);

このコードは次の手順を実行します。

  1. マネージャーを取得する: ProfilingManager サービスを取得します。
  2. トリガーを定義する: TRIGGER_TYPE_APP_FULLY_DRAWNProfilingTrigger をビルドします。このイベントは、アプリが起動を完了し、インタラクティブになったことを報告したときに発生します。
  3. レート制限を設定する: この特定のトリガー(setRateLimitingPeriodHours(1))に 1 時間のレート制限を適用します。これにより、アプリが 1 時間あたり 1 つ以上の起動プロファイルを記録することを防ぎます。
  4. リスナーを登録: registerForAllProfilingResults を呼び出して、結果を処理するコールバックを定義します。このコールバックは、保存されたプロファイルのパスを getResultFilePath() 経由で受け取ります。
  5. トリガーを追加: addProfilingTriggers を使用して、トリガー リストを ProfilingManager に登録します。
  6. イベントを起動: reportFullyDrawn() を呼び出します。これにより、TRIGGER_TYPE_APP_FULLY_DRAWN イベントがシステムに送信され、システム バックグラウンド トレースが実行中で、レート制限クォータが利用可能であることを前提として、プロファイル収集がトリガーされます。このオプションの手順では、アプリがこのトリガーに対して reportFullyDrawn() を呼び出す必要があるため、エンドツーエンドのフローを示します。

トレースを取得する

トリガーベースのプロファイルは、他のプロファイルと同じディレクトリに保存されます。トリガーされたトレースのファイル名は次の形式になります。

profile_trigger_<profile_type_code>_<datetime>.<profile-type-name>

ADB を使用してファイルを取得できます。たとえば、ADB を使用してサンプルコードでキャプチャしたシステム トレースをプルするには、次のようにします。

adb pull /data/user/0/com.example.sampleapp/files/profiling/profile_trigger_1_2025-05-06-14-12-40.perfetto-trace

これらのトレースの可視化の詳細については、プロファイリング データを取得して分析するをご覧ください。

バックグラウンド トレースの仕組み

トリガー前のデータをキャプチャするため、OS はバックグラウンド トレースを定期的に開始します。このバックグラウンド トレースがアクティブな間にトリガーが発生し、アプリがそのトリガーに登録されている場合、システムはトレース プロファイルをアプリのディレクトリに保存します。プロファイルには、トリガーに至るまでの情報が含まれます。

プロファイルが保存されると、システムは registerForAllProfilingResults に提供されたコールバックを使用してアプリに通知します。このコールバックは、ProfilingResult#getResultFilePath() を呼び出すことでアクセスできる、キャプチャされたプロファイルのパスを提供します。

バックグラウンド トレース スナップショットの仕組みを示す図。トリガー イベントの前にリングバッファがデータをキャプチャしている。
図 1: バックグラウンド トレース スナップショットの仕組み。

デバイスのパフォーマンスとバッテリー駆動時間への影響を軽減するため、システムはバックグラウンド トレースを継続的に実行しません。代わりに、サンプリング方法を使用します。システムは、設定された期間内(最小期間と最大期間)でバックグラウンド トレースをランダムに開始します。これらのトレースをランダムに配置することで、トリガーの範囲が広がります。

システム トリガー プロファイルにはシステム定義の最大サイズがあるため、リングバッファを使用します。バッファがいっぱいになると、新しいトレースデータによって最も古いデータが上書きされます。図 1 に示すように、バッファが満杯になると、キャプチャされたトレースはバックグラウンド録画の全期間をカバーしないことがあります。代わりに、トリガーまでの最新のアクティビティを表します。

トリガー固有のレート制限を実装する

高頻度のトリガーは、アプリのレート制限の割り当てをすぐに消費する可能性があります。レート制限について理解を深めるには、レート制限の仕組みをご覧ください。単一のトリガータイプが割り当てを使い果たすのを防ぐには、トリガー固有のレート制限を実装します。

ProfilingManager は、アプリ定義のトリガー固有のレート制限をサポートしています。これにより、既存のレート制限に加えて、時間ベースのスロットリングの別のレイヤを追加できます。setRateLimitingPeriodHours API を使用して、トリガーの特定のクールダウン時間を設定します。クールダウンの有効期限が切れると、再度トリガーできます。

トリガーをローカルでデバッグする

バックグラウンド トレースはランダムなタイミングで実行されるため、ローカルでデバッグ トリガーを起動することは困難です。テスト用にバックグラウンド トレースを強制的に実行するには、次の ADB コマンドを使用します。

adb shell device_config put profiling_testing system_triggered_profiling.testing_package_name <com.example.myapp>

このコマンドは、指定されたパッケージの継続的なバックグラウンド トレースを開始するようにシステムに強制します。これにより、レート制限が許可されていれば、すべてのトリガーでプロファイルを収集できるようになります。

他のデバッグ オプション(ローカルでデバッグする際にレート制限を無効にするなど)を有効にすることもできます。詳細については、ローカル プロファイリングのデバッグ コマンドをご覧ください。