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

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. レート制限を設定する: この特定のトリガーに 1 時間のレート制限を適用します (setRateLimitingPeriodHours(1))。これにより、アプリが 1 時間に 1 つ以上の起動プロファイルを記録することを防ぎます。
  4. リスナーを登録する: registerForAllProfilingResults を呼び出して、結果を処理する コールバックを定義します。このコールバックは、保存されたプロファイルのパスを getResultFilePath() を介して受け取ります。
  5. トリガーを追加する: ProfilingManager を使用して、トリガーリストを addProfilingTriggers に登録します。
  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() を呼び出すことでアクセスできます。

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

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

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

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

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

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

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

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

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

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

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