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

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_DRAWN の ProfilingTrigger をビルドします。このイベントは、アプリが起動を完了し、インタラクティブになったことを報告したときに発生します。
  3. レート制限を設定する: この特定のトリガー(setRateLimitingPeriodHours(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 を使用してファイルを pull できます。たとえば、ADB を使用してサンプルコードでキャプチャしたシステム トレースを pull するには、次のようになります。

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 に示すように、バッファがいっぱいになると、キャプチャされたトレースはバックグラウンド記録の全期間をカバーしない可能性があります。代わりに、トリガーに至るまでの最新のアクティビティを表します。

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

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

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

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

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

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

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

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