Fireタブレット向けのアプリのパフォーマンススクリプト
パフォーマンステストとは、Amazon Fire OSデバイスでの互換性、信頼性、速度、応答時間、安定性、リソース使用量といった領域を対象にアプリをテストするプロセスです。このテストは、アプリのパフォーマンスのボトルネックを特定して対処するために使用できます。パフォーマンステストには、主要業績評価指標(KPI)の収集と評価が含まれます。KPI指標を収集するには、Amazonデバイスで特定の手順を実行した後、ログなどのデバイスリソースを使用して指標を検索または計算します。
アプリをAmazonアプリストアに申請する前に、必ずパフォーマンステストを実行してください。このページでは、さまざまなカテゴリーのKPIをテストする手順と、自動化に使用できるサンプルコードを示します。このガイドで取り上げるKPIは次のとおりです。
- レイテンシ - 最初のフレームまでの時間(TTFF)
- 使用準備完了 - 表示完了までの時間(TTFD)
- アプリのコア機能(ビデオストリーミングやゲームプレイなど)を使用した後のメモリ
セットアップ
最初に、開発用コンピューターに次のソフトウェアパッケージをインストールします。
- Amazon Corretto
- Android Studio(セットアップ中にプラットフォームツールをインストールしてください)
- Appium
ソフトウェアパッケージのインストールに加えて、次のことを行う必要があります。
- JAVA_HOMEフォルダとANDROID_HOMEフォルダのパスを設定します。
- デバイスで開発者モードを有効にし、USBデバッグを有効にします。手順については、開発者オプションを有効にするを参照してください。
- 接続されているデバイスのシリアル番号を取得します。物理的に接続されているデバイスのシリアル番号を表示するには、Android Debug Bridge(ADB)コマンドの
adb devices -lを使用できます。
テスト戦略
テストでは、アプリランチャーインテントまたはMonkeyツールを使用して、アプリの起動と強制停止を数回繰り返します。イテレーションごとに、ADB Logcatログのキャプチャ、ナビゲーションアクションの実行、バイタルとADBログからのタイマー値のキャプチャ、アプリを強制停止する前のメモリとRAMの使用量のキャプチャなど、特定のアクションを実行する必要があります。このループは、開発者が構成したイテレーション回数分続きます。テスト結果にはネットワークの状態、システムの負荷、その他の要因が影響する可能性があるため、複数回のイテレーションを行うことで外部要因による干渉を平均化します。
指標の平均値を計算するにあたり、Amazonでは、テストのカテゴリーごとに以下の最小イテレーション回数を実行することを推奨しています。
| パフォーマンステストのカテゴリー | 推奨される最小イテレーション回数 |
|---|---|
| レイテンシ - 最初のフレームまでの時間(TTFF) | 50 |
| 使用準備完了 - 表示完了までの時間(TTFD) | 10 |
| メモリ | 5 |
次のデバイスをテストに使用します。
- Fire OS 8: Fire HD 10(2023)
- Fire OS 8: Fire Max 11(2023)
パフォーマンステストでキャプチャされたログ、スクリーンショット、その他のアーティファクトは、デバッグの目的やデータとして使用できます。Appiumデバイスオブジェクトは、ティアダウンの一環として強制停止されます。
以下のセクションでは、テスト自動化スクリプトに追加できるサンプルコードを示します。
デバイスタイプの取得
次のサンプルコードは、接続されているデバイスのデバイスタイプを取得する方法を示しています。
public String get_device_type() {
String deviceType = null;
try (BufferedReader read = new BufferedReader(new InputStreamReader
(Runtime.getRuntime().exec
("adb -s "+ DSN +" shell getprop ro.build.configuration")
.getInputStream())))
{
String outputLines = read.readLine();
switch (outputLines) {
case "tv":
deviceType = "FTV";
break;
case "tablet":
deviceType = "Tablet";
break;
}
}
catch (Exception e) {
System.out.println("デバイスタイプ情報の取得中に例外が発生しました:" + e);
}
return deviceType;
}
import java.io.BufferedReader
import java.io.InputStreamReader
fun getDeviceType(): String? {
var deviceType: String? = null
try {
BufferedReader(InputStreamReader(Runtime.getRuntime().exec("adb -s $DSN shell getprop ro.build.configuration").inputStream)).use { read ->
val outputLines = read.readLine()
when (outputLines) {
"tv" -> deviceType = "FTV"
"tablet" -> deviceType = "Tablet"
}
}
} catch (e: Exception) {
println("デバイスタイプ情報の取得中に例外が発生しました: $e")
}
return deviceType
}
メインランチャーアクティビティのコンポーネント名の取得
次のサンプルコードは、メインランチャーアクティビティのコンポーネント名を取得する方法を示しています。このメソッドは、テスト対象のアプリのメインアクティビティを取得し、アプリパッケージとメインアクティビティの名前を組み合わせてコンポーネント名を作成します。
try (BufferedReader read = new BufferedReader(new InputStreamReader
(Runtime.getRuntime().exec("adb -s "+ DSN +" shell pm dump "+ appPackage +" | grep -A 1 MAIN").getInputStream()))) {
String outputLine = null;
String line;
while ((line = read.readLine()) != null) {
if (line.contains(appPackage + "/")) {
outputLine = line;
break;
}
}
outputLine = outputLine.split("/")[1];
String mainActivity = outputLine.split(" ")[0];
String componentName = appPackage + "/" + mainActivity;
return componentName;
}
catch (Exception e) {
System.out.println("アプリのメインアクティビティの取得中に例外が発生しました" + e);
}
import java.io.BufferedReader
import java.io.InputStreamReader
try {
val process = Runtime.getRuntime().exec("adb -s $DSN shell pm dump $appPackage | grep -A 1 MAIN")
val inputStream = process.inputStream
val reader = BufferedReader(InputStreamReader(inputStream))
var line: String? = null
var outputLine: String? = null
while (reader.readLine().also { line = it } != null) {
if (line!!.contains("$appPackage/")) {
outputLine = line
break
}
}
outputLine = outputLine!!.split("/")[1]
val mainActivity = outputLine.split(" ")[0]
val componentName = "$appPackage/$mainActivity"
componentName
} catch (e: Exception) {
println("アプリのメインアクティビティの取得中に例外が発生しました:$e")
}
メインランチャーアクティビティのコンポーネント名を使用したアプリの起動
メインランチャーアクティビティのコンポーネント名を使用してアプリを起動するには、次のサンプルコードを使用します。このコードでは、前のセクションで定義したcomponentName変数を使用します。この変数は、アプリパッケージとメインアクティビティを組み合わせることで作成されたコンポーネント名を表します。
try (BufferedReader read = new BufferedReader(new InputStreamReader
(Runtime.getRuntime().exec("adb -s "+ DSN +" shell am start -n " + componentName).getInputStream()))) {
String deviceName = getDeviceName(DSN);
String line;
while ((line = read.readLine()) != null) {
if (line.startsWith("Starting: Intent")) {
System.out.println("コンポーネント名を使用したアプリの起動が正常に完了しました - " + componentName);
break;
} else if (line.contains("Error")) {
System.out.println("App Launch Error");
}
}
} catch (Exception e) {
System.out.println("アプリの起動中に例外が発生しました:" + e);
}
import java.io.BufferedReader
import java.io.InputStreamReader
val process = Runtime.getRuntime().exec("adb -s $DSN shell am start -n $componentName")
val inputStream = process.inputStream
val reader = BufferedReader(InputStreamReader(inputStream))
try {
val deviceName = getDeviceName(DSN)
var line: String? = null
while (reader.readLine().also { line = it } != null) {
if (line!!.startsWith("Starting: Intent")) {
println("コンポーネント名を使用したアプリの起動が正常に完了しました - $componentName")
break
} else if (line!!.contains("Error")) {
println("App Launch Error")
}
}
} catch (e: Exception) {
println("アプリの起動中に例外が発生しました:$e")
}
Monkeyツールを使用したアプリの起動
次のコードは、Monkeyツールを使用してアプリを起動する方法を示しています。
try {
String monkeyCommand = null;
if (DEVICE_TYPE.equals(FTV)) {
monkeyCommand = " shell monkey --pct-syskeys 0 -p "
}
else {
monkeyCommand = " shell monkey -p "
}
BufferedReader launchRead = new BufferedReader(new InputStreamReader
(Runtime.getRuntime().exec("adb -s "+ DSN + monkeyCommand + appPackage +" -c android.intent.category.LAUNCHER 1").getInputStream()));
String line;
while ((line = launchRead.readLine()) != null) {
if (line.contains("Events injected")) {
System.out.println("Monkeyツールを使用してアプリが正常に起動されました - " + appPackage);
launchRead.close();
return true;
}
else if (line.contains("エラー") || line.contains("アクティビティが見つかりませんでした")) {
System.out.println("Monkeyツールからアプリを起動中にエラーが発生しました。起動にインテントを使用しています");
launchRead.close();
return false;
}
}
}
catch (Exception e) {
System.out.println("Monkeyを使用してアプリを起動中に例外が発生しました" + e);
return false;
}
try {
val monkeyCommand: String?
if (DEVICE_TYPE == FTV) {
monkeyCommand = " shell monkey --pct-syskeys 0 -p "
}
else {
monkeyCommand = " shell monkey -p "
}
val launchRead = BufferedReader(InputStreamReader(
Runtime.getRuntime().exec("adb -s $DSN $monkeyCommand $appPackage -c android.intent.category.LAUNCHER 1").inputStream))
var line: String?
while (launchRead.readLine().also { line = it } != null) {
if (line!!.contains("Events injected")) {
println("Monkeyツールを使用してアプリが正常に起動されました - $appPackage")
launchRead.close()
return true
}
else if (line!!.contains("エラー") || line!!.contains("アクティビティが見つかりませんでした")) {
println("Monkeyツールからアプリを起動中にエラーが発生しました。起動にインテントを使用しています")
launchRead.close()
return false
}
}
}
catch (e: Exception) {
println("Monkeyを使用してアプリを起動中に例外が発生しました $e")
return false
}
アプリの強制停止
次のサンプルコードは、アプリを強制停止する方法を示しています。
try {
Runtime.getRuntime().exec("adb -s "+ DSN +" shell am force-stop " + appPackage);
System.out.println("アプリを強制停止しました - " + appPackage);
}
catch (Exception e) {
System.out.println("アプリを強制停止中に例外が発生しました" + e);
}
try {
Runtime.getRuntime().exec("adb -s ${DSN} shell am force-stop ${appPackage}")
println("アプリを強制停止しました - $appPackage")
} catch (e: Exception) {
println("アプリを強制停止中に例外が発生しました: $e")
}
一般的なコマンド
以下のセクションでは、パフォーマンステストに使用できるコマンドの例を示します。
ADBログのキャプチャ
次のサンプルコードは、threadtime形式を使用するADBログをキャプチャする方法を示しています。
public String ADB_LOGCAT_DUMP = "adb shell logcat -v threadtime -b all -d";
public String ADB_LOGCAT_CLEAR = "adb shell logcat -v threadtime -b all -c";
//以下のメソッドは、デバイスのDSN値を指定してadbコマンドを追加するために使用されます
public Process adb(String DSN, String message) {
Process process = null;
try {
process = Runtime.getRuntime().exec("adb -s " + DSN + message);
}
catch (Exception e) {
System.out.println("adbコマンドの実行中に例外が発生しました" + e);
}
return process;
}
public const val ADB_LOGCAT_DUMP: String = "adb shell logcat -v threadtime -b all -d"
public const val ADB_LOGCAT_CLEAR: String = "adb shell logcat -v threadtime -b all -c"
//以下の関数は、デバイスのDSN値を指定してadbコマンドを追加するために使用されます
fun adb(DSN: String, message: String): Process? {
return try {
Runtime.getRuntime().exec("adb -s $DSN$message")
}
catch (e: Exception) {
println("adbコマンドの実行中に例外が発生しました$e")
null
}
}
バイタルバッファーログからタイマー値をキャプチャ
次のサンプルコードは、バイタルバッファーからパフォーマンスレイテンシタイマー値を取り出す方法を示しています。
public int get_vitals_timer(File logFile, String appPackage, String metricSearch) {
BufferedReader reader = null;
String line_Metric;
int timer = 0;
try {
reader = new BufferedReader(new FileReader(logFile))
while ((line_Metric = reader.readLine()) != null) {
if (line_Metric.contains("performance:" + metricSearch)
&& line_Metric.contains("key=" + appPackage)) {
timer = splitToTimer(line_Metric);
}
}
}
catch (Exception e) {
System.out.println(e);
}
finally {
reader.close();
}
return timer;
}
import java.io.File
fun getVitalsTimer(logFile: File, appPackage: String, metricSearch: String): Int {
var reader: BufferedReader? = null
var lineMetric: String
var timer = 0
try {
reader = logFile.bufferedReader()
while (reader.readLine().also { lineMetric = it } != null) {
if (lineMetric.contains("performance:$metricSearch") && lineMetric.contains("key=$appPackage")) {
timer = splitToTimer(lineMetric)
}
}
} catch (e: Exception) {
println(e)
} finally {
reader?.close()
}
return timer
}
アクティビティマネージャーのバッファーログからタイマー値をキャプチャ
次のコードは、アクティビティまたはウィンドウマネージャーのバッファーを使用して、アプリのタイマー値を取り出す方法を示しています。
public int get_displayed_timer(File adb_log_file, String appPackage) {
BufferedReader br = null;
int timer = 0;
String displayedMarker = null;
try {
br = new BufferedReader(new FileReader(adb_log_file));
while ((displayedMarker = br.readLine()) != null) {
if ((displayedMarker.contains(AM_ACTIVITY_LAUNCH_TIME) ||
displayedMarker.contains(WM_ACTIVITY_LAUNCH_TIME) ||
displayedMarker.contains(WARM_ACTIVITY_LAUNCH_TIME)) &&
displayedMarker.contains(appPackage))
break;
}
if (displayedMarker != null) {
displayedMarker = displayedMarker.split("]")[0].split("\\[")[1];
ffValue = Integer.parseInt(displayedMarker);
}
} catch (Exception e) {
System.out.println(e);
} finally {
br.close();
}
return timer;
}
public int get_displayed_timer(File adb_log_file, String appPackage) {
BufferedReader br = null;
int timer = 0;
String displayedMarker = null;
try {
br = new BufferedReader(new FileReader(adb_log_file));
while ((displayedMarker = br.readLine()) != null) {
if ((displayedMarker.contains(AM_ACTIVITY_LAUNCH_TIME) ||
displayedMarker.contains(WM_ACTIVITY_LAUNCH_TIME) ||
displayedMarker.contains(WARM_ACTIVITY_LAUNCH_TIME)) &&
displayedMarker.contains(appPackage))
break;
}
if (displayedMarker != null) {
displayedMarker = displayedMarker.split("]")[0].split("\\[")[1];
ffValue = Integer.parseInt(displayedMarker);
}
} catch (Exception e) {
System.out.println(e);
} finally {
br.close();
}
return timer;
}
ログでよく使用される略語
アプリをプログラムによって起動する前に、アプリのパッケージ名やメインアクティビティなど、いくつかの基本パラメーターを確認しておく必要があります。ログによく出現する値を、自動化で使用しやすいように定数として追加することもできます。以下は、パフォーマンステストの自動化スクリプトに追加できる定数です。
public String COOL_APP = "cool_app_launch_time";
public String COOL_ACTIVITY = "cool_activity_launch_time";
public String WARM_APP_WARM = "warm_app_warm_transition_launch_time";
public String WARM_APP_COOL = "warm_app_cool_transition_launch_time";
public String AM_ACTIVITY_LAUNCH_TIME = "am_activity_launch_time:";
public String WM_ACTIVITY_LAUNCH_TIME = "wm_activity_launch_time:";
public String WARM_ACTIVITY_LAUNCH_TIME = "performance:warm_activity_launch_time:";
public String AM_FULLY_DRAWN = "am_activity_fully_drawn_time:";
public String WM_FULLY_DRAWN = "wm_activity_fully_drawn_time:";
const val COOL_APP: String = "cool_app_launch_time"
const val COOL_ACTIVITY: String = "cool_activity_launch_time"
const val WARM_APP_WARM: String = "warm_app_warm_transition_launch_time"
const val WARM_APP_COOL: String = "warm_app_cool_transition_launch_time"
const val AM_ACTIVITY_LAUNCH_TIME: String = "am_activity_launch_time:"
const val WM_ACTIVITY_LAUNCH_TIME: String = "wm_activity_launch_time:"
const val WARM_ACTIVITY_LAUNCH_TIME: String = "performance:warm_activity_launch_time:"
const val AM_FULLY_DRAWN: String = "am_activity_fully_drawn_time:"
const val WM_FULLY_DRAWN: String = "wm_activity_fully_drawn_time:"
レイテンシ - 最初のフレームまでの時間
レイテンシKPI(最初のフレームまでの時間(TTFF))は、アプリが起動してから最初の視覚フレームが表示されるまでにかかる時間を測定します。このKPIでは、コールド起動時とウォーム起動時の測定値を取得して、さまざまなシナリオでユーザーが体感する実際的な動作を再現することを目指しています。
最初のフレームの描画までの時間を取得する方法
次のadbコマンドは、最初のフレームが描画されるまでの時間を取得する方法を示しています。
adb logcat | grep "Displayed APP_PACKAGE_NAME/LAUNCHER_ACTIVITY"
adb logcat | findstr "Displayed APP_PACKAGE_NAME\LAUNCHER_ACTIVITY"
出力の例
ActivityManager: Displayed APP_PACKAGE_NAME/LAUNCHER_ACTIVITY +930ms (total +849ms)
シナリオ: コールドスタート - 最初のフレームまでの時間
TTFFコールドスタートは、アプリプロセスが停止するかデバイスが再起動された後、アプリが起動して最初のフレームを表示するまでにかかる時間を表します。コールドスタートをテストするときは、アプリが強制停止された後にアプリを起動します。これにより、初回使用または新規起動のシナリオがシミュレートされます。コールドスタート起動は通常、アプリでサービスを再読み込みする必要があるため、ウォームスタート起動よりも時間がかかります。
コールドスタート起動での最初のフレームまでの時間を測定するには
- logcatの消去コマンドを実行します。
adb shell logcat -v threadtime -b all -c - アプリをダウンロードして起動し、アプリにサインインします。
adb shell am start <アプリパッケージ>/<アプリのメインアクティビティ> - logcatコマンドを実行します。
adb shell logcat -v threadtime -b all -d - 次の文字列を含むログ行を検索して、タイマー値をキャプチャします。
cool_app_launch_time - アプリが完全に起動したら、アプリを強制停止します。
adb shell am force-stop <アプリパッケージ> - この手順を50回繰り返します。
コールドスタートのイテレーション
次のサンプルコードでは、構成されたイテレーション回数にわたり、コールドスタート時のレイテンシテストを実行してレイテンシ値を出力します。
for (int i = 0; i < iterations; i++) {
if (!launchAppUsingMonkey(DSN, appPackage))
launchAppUsingIntent(DSN, appIntent);
Thread.sleep(10);
BufferedReader read = new BufferedReader
(new InputStreamReader(Runtime.getRuntime()
.exec("adb -s "+ DSN +" shell logcat -v threadtime -b all -d")
.getInputStream()));
String line_CoolApp;
String line_CoolActivity;
log_file_writer(read);
File adb_log_file = new File( kpi_log_file_path + "/KPI_Log.txt");
if (deviceType == "Tablet") {
timer = get_vitals_timer(adb_log_file, appPackage, COOL_APP);
} else if (deviceType == "FTV") {
timer = get_displayed_timer(adb_log_file, appPackage);
if (timer == 0) {
timer = get_vitals_timer(adb_log_file, appPackage, COOL_APP);
}
}
if (timer == 0) {
timer = get_vitals_timer(adb_log_file, appPackage, COOL_ACTIVITY);
}
forceStopApp(DSN, appPackage);
Thread.sleep(10);
Runtime.getRuntime().exec("adb -s "+ DSN +" shell logcat -b all -c");
}
for (int i = 0; i < iterations; i++) {
if (!launchAppUsingMonkey(DSN, appPackage)) {
launchAppUsingIntent(DSN, appIntent)
}
Thread.sleep(10)
val read = BufferedReader(InputStreamReader(Runtime.getRuntime().exec("adb -s ${DSN} shell logcat -v threadtime -b all -d").getInputStream()))
val line_CoolApp = read.readLine()
val line_CoolActivity = read.readLine()
log_file_writer(read)
val adb_log_file = File(kpi_log_file_path + "/KPI_Log.txt")
when (deviceType) {
"Tablet" -> timer = get_vitals_timer(adb_log_file, appPackage, COOL_APP)
"FTV" -> timer = get_displayed_timer(adb_log_file, appPackage)
.takeIf { it != 0 }
?: get_vitals_timer(adb_log_file, appPackage, COOL_APP)
}
if (timer == 0) {
timer = get_vitals_timer(adb_log_file, appPackage, COOL_ACTIVITY)
}
forceStopApp(DSN, appPackage)
Thread.sleep(10)
Runtime.getRuntime().exec("adb -s ${DSN} shell logcat -b all -c")
}
デバイスのコールドスタート時のバイタルログ
以下は、デバイスのコールドスタート時のバイタルログの例です。
03-03 12:42:32.589 892 992 I Vlog : PhoneWindowManager:ScreenTime:fgtracking=false;DV;1,Timer=1.0;TI;1,unit=count;DV;1,metadata=!{"d"#{"groupId"#"<APP_PACKAGE_NAME>"$"schemaId"#"123"$"startTimeMs"#"1.677827544773E12"$"packageName"#"<APP_PACKAGE_NAME>"$"endTimeMs"#"1.677827552587E12"$"durationMs"#"7813.0"}};DV;1:HI
03-03 12:42:33.657 892 1092 I Vlog : performance:cool_app_launch_time:fgtracking=false;DV;1,key=<APP_PACKAGE_NAME>;DV;1,**Timer****=****1333.0**;TI;1,unit=ms;DV;1,metadata=App_Metadata!{"d"#{"groupId"#"abc"$"schemaId"#"abc"$"app_version"#"123"$"isChild"#"false"}$"m"#{"prev"#"<APP_PACKAGE_NAME>"$"context"#"abc"}};DV;1:HI
03-03 12:42:33.661 892 1092 I Vlog : performance:user_app_launch_cool:fgtracking=false;DV;1,key=<APP_PACKAGE_NAME>;DV;1,Counter=1;CT;1,unit=count;DV;1,metadata=App_Metadata!{"d"#{"groupId"#"abc"$"schemaId"#"abc"$"isChild"#"false"}};DV;1:HI
03-03 12:49:20.880 892 1092 I Vlog : performance:cool_app_launch_time:fgtracking=false;DV;1,key=<APP_PACKAGE_NAME>;DV;1,**Timer****=****1225.0**;TI;1,unit=ms;DV;1,metadata=App_Metadata!{"d"#{"groupId"#"abc"$"schemaId"#"abc"$"app_version"#"123"$"isChild"#"false"}$"m"#{"prev"#"<APP_PACKAGE_NAME>"$"context"#"abc"}};DV;1:HI
03-03 12:49:20.918 892 1092 I Vlog : performance:user_app_launch_cool:fgtracking=false;DV;1,key=<APP_PACKAGE_NAME>;DV;1,Counter=1;CT;1,unit=count;DV;1,metadata=App_Metadata!{"d"#{"groupId"#"abc"$"schemaId"#"abc"$"isChild"#"false"}};DV;1:HI
最初のフレームまでの時間を取得するには、最初のフレームの描画までの時間を取得する方法を参照してください。
シナリオ: ウォームスタート - 最初のフレームまでの時間
TTFFウォームスタートは、アプリプロセスが既にバックグラウンドで実行されているときに、アプリが起動して最初のフレームを表示するまでにかかる時間を表します。この起動アクティビティの一部として、システムはアプリをバックグラウンドからフォアグラウンドに移動させます。ウォームスタートをテストするときは、アプリがバックグラウンドに移行した後にアプリを起動します。ウォームスタート起動は通常、アプリに既にサービスがキャッシュされているため、コールドスタート起動よりも高速です。
ウォームスタート起動での最初のフレームまでの時間を測定するには
- アプリがバックグラウンドで実行されていることを確認します。
- logcatの消去コマンドを実行します。
adb shell logcat -v threadtime -b all -c - アプリを起動し、アプリにサインインします。
adb shell am start <アプリパッケージ>/<アプリのメインアクティビティ> - logcatコマンドを実行します。
adb shell logcat -v threadtime -b all -d - 次の文字列を含むログ行を検索して、タイマー値をキャプチャします。
warm_app_warm_transition_launch_time - アプリが完全に起動したら、次のコマンドを実行します。
adb shell input keyevent KEYCODE_HOME - この手順を50回繰り返します。
ウォームスタートのイテレーション
次のサンプルコードでは、構成されたイテレーション回数にわたり、ウォームスタート時のレイテンシテストを実行してレイテンシ値を出力します。
for (int i = 0; i < iterations; i++) {
if (!launchAppUsingMonkey(DSN, appPackage))
launchAppUsingIntent(DSN, appIntent);
Thread.sleep(10);
BufferedReader read = new BufferedReader
(new InputStreamReader(Runtime.getRuntime()
.exec("adb -s "+ DSN +" shell logcat -v threadtime -b all -d")
.getInputStream()));
String line_CoolApp;
String line_CoolActivity;
log_file_writer(read);
String adb_log_file = kpi_log_file_path + "/KPI_Log.txt";
if (deviceType == "Tablet") {
timer = get_vitals_timer(adb_log_file, appPackage, WARM_APP_WARM);
} else if (deviceType == "FTV") {
timer = get_displayed_timer(adb_log_file, appPackage);
if (timer == 0) {
timer = get_vitals_timer(adb_log_file, appPackage, WARM_APP_WARM);
}
}
if (timer == 0) {
timer = get_vitals_timer(adb_log_file, appPackage, WARM_APP_COOL);
}
Runtime.getRuntime().exec("adb -s "+ DSN +" shell input keyevent KEYCODE_HOME");
Thread.sleep(10);
Runtime.getRuntime().exec("adb -s "+ DSN +" shell logcat -b all -c");
}
for (int i = 0; i < iterations; i++) {
if (!launchAppUsingMonkey(DSN, appPackage))
launchAppUsingIntent(DSN, appIntent)
Thread.sleep(10)
val read = BufferedReader(InputStreamReader(Runtime.getRuntime().exec("adb -s ${DSN} shell logcat -v threadtime -b all -d").getInputStream()))
val line_CoolApp = read.readLine()
val line_CoolActivity = read.readLine()
log_file_writer(read)
val adb_log_file = kpi_log_file_path + "/KPI_Log.txt"
when (deviceType) {
"Tablet" -> timer = get_vitals_timer(adb_log_file, appPackage, WARM_APP_WARM)
"FTV" -> timer = get_displayed_timer(adb_log_file, appPackage)
.takeIf { it != 0 } ?: get_vitals_timer(adb_log_file, appPackage, WARM_APP_WARM)
}
if (timer == 0) {
timer = get_vitals_timer(adb_log_file, appPackage, WARM_APP_COOL)
}
Runtime.getRuntime().exec("adb -s ${DSN} shell input keyevent KEYCODE_HOME")
Thread.sleep(10)
Runtime.getRuntime().exec("adb -s ${DSN} shell logcat -b all -c")
}
デバイスのウォームスタート時のバイタルログ
以下は、デバイスのウォームスタート時のバイタルログの例です。
03-03 12:51:16.367 892 1066 I Vlog : Thermal:ScreenOn_SensorThrottling_P2P_Off:fgtracking=false;DV;1,key=0;DV;1,Timer=8.072222222222222E-4;TI;1,unit=hours;DV;1,metadata=<APP_PACKAGE_NAME>!{"d"#{"groupId"#"123"$"schemaId"#"123"$"zone"#"soc"}};DV;1:HI
03-03 12:51:16.367 892 1066 I Vlog : Thermal:ScreenOn_SensorThrottling_P2P_Off:fgtracking=false;DV;1,key=0;DV;1,Timer=8.072222222222222E-4;TI;1,unit=hours;DV;1,metadata=<APP_PACKAGE_NAME>!{"d"#{"groupId"#"123"$"schemaId"#"123"$"zone"#"bottom"}};DV;1:HI
03-03 12:51:16.367 892 1066 I Vlog : Thermal:ScreenOn_SensorThrottling_P2P_Off:fgtracking=false;DV;1,key=0;DV;1,Timer=8.072222222222222E-4;TI;1,unit=hours;DV;1,metadata=<APP_PACKAGE_NAME>!{"d"#{"groupId"#"123"$"schemaId"#"123"$"zone"#"side"}};DV;1:HI
03-03 12:51:16.368 892 1066 I Vlog : Thermal:Screen_On_Thermal_Throttling_P2P_Off:fgtracking=false;DV;1,key=0;DV;1,Timer=8.075E-4;TI;1,unit=hours;DV;1,metadata=<APP_PACKAGE_NAME>!{"d"#{"groupId"#"123"$"schemaId"#"abc"}};DV;1:HI
03-03 12:51:16.384 892 1092 I Vlog : performance:warm_app_warm_transition_launch_time:fgtracking=false;DV;1,key=<APP_PACKAGE_NAME>;DV;1,**Timer****=****191.0**;TI;1,unit=ms;DV;1,metadata=<APP_PACKAGE_NAME>!{"d"#{"groupId"#"123"$"schemaId"#"abc"$"app_version"#"123"$"isChild"#"false"}$"m"#{"prev"#"<APP_PACKAGE_NAME>"$"context"#"1234"}};DV;1:HI
03-03 12:51:16.395 892 1092 I Vlog : performance:user_app_launch_warm:fgtracking=false;DV;1,key=<APP_PACKAGE_NAME>;DV;1,Counter=1;CT;1,unit=count;DV;1,metadata=<APP_PACKAGE_NAME>!{"d"#{"groupId"#"123"$"schemaId"#"abc"$"isChild"#"false"}};DV;1:HI
最初のフレームまでの時間を取得するには、最初のフレームの描画までの時間を取得する方法を参照してください。
使用準備完了 - 表示完了までの時間
使用準備完了(RTU)KPI(表示完了までの時間(TTFD))は、アプリが起動してから使用準備完了状態になるまでにかかる時間を測定します。使用準備完了状態とは、たとえば、アプリにサインインできる状態になったときや、アプリのホームページが使用可能になったときが考えられます。RTU指標は、アプリの起動パフォーマンスの問題を特定するために役立つ可能性があります。このKPIでは、コールド起動時とウォーム起動時の測定値を取得して、さまざまなシナリオでユーザーが体感する実際的な動作を再現することを目指しています。
ユーザーのサインインが必要なアプリの場合、RTU KPIの測定はサインイン後のユースケースについて行ってください。
描画完了状態を検出する方法
アプリにreportFullyDrawn()メソッドを実装している場合は、次のadbコマンドを使用して描画完了状態を検出できます。
adb logcat | grep "Fully drawn APP_PACKAGE_NAME/LAUNCHER_ACTIVITY"
adb logcat | findstr "Fully drawn APP_PACKAGE_NAME\LAUNCHER_ACTIVITY"
出力サンプル
ActivityManager: Fully drawn APP_PACKAGE_NAME/LAUNCHER_ACTIVITY +930ms (total +849ms)
シナリオ: RTUコールドスタート - 表示完了までの時間
RTUコールドスタートは、アプリプロセスが停止するかデバイスが再起動された後、アプリが起動して描画が完了し、ユーザーが操作できるようになるまでにかかる時間を表します。コールドスタートをテストするときは、アプリが強制停止された後にアプリを起動します。これにより、初回使用または新規起動のシナリオがシミュレートされます。コールドスタート起動は通常、アプリでサービスを再読み込みする必要があるため、ウォームスタート起動よりも時間がかかります。
テスト手順
- アプリにサインイン済みであることを確認します。
- logcatの消去コマンドを実行します。
adb shell logcat -v threadtime -b all -c - アプリを起動します。
adb shell am start <アプリパッケージ>/<アプリのメインアクティビティ> - logcatコマンドを実行して、描画完了のタイマー値を取得します。
adb shell logcat -v threadtime -b all -d - アプリが完全に起動し、タイマー値がキャプチャされたら、次のコマンドを実行してアプリを強制停止します。
adb shell am force-stop <アプリパッケージ> - この手順を10回繰り返します。
シナリオ: RTUウォームスタート - 表示完了までの時間
RTUウォームスタートは、アプリプロセスが既にバックグラウンドで実行されているときに、アプリが起動して描画が完了し、ユーザーが操作できるようになるまでにかかる時間を表します。この起動アクティビティの一部として、システムはアプリをバックグラウンドからフォアグラウンドに移動させます。ウォームスタートをテストするときは、アプリがバックグラウンドに移行した後にアプリを起動します。ウォームスタート起動は通常、アプリに既にサービスがキャッシュされているため、コールドスタート起動よりも高速です。
テスト手順
- アプリにサインインしていることを確認してから、アプリをバックグラウンドに移行させます。
- logcatの消去コマンドを実行します。
adb shell logcat -v threadtime -b all -c - アプリを起動します。
adb shell am start <アプリパッケージ>/<アプリのメインアクティビティ> - logcatコマンドを実行して、描画完了のタイマー値を取得します。
adb shell logcat -v threadtime -b all -d - アプリが完全に起動したら、アプリをバックグラウンドに移行させるコマンドを実行します。
adb shell input keyevent KEYCODE_HOME - この手順を10回繰り返します。
表示完了までの時間を取得するには、描画完了状態を検出する方法を参照してください。
RTU ADBログ
以下は、Amazon Fireタブレットデバイスでのゲームの描画完了アクティビティを表すログの例です。
**`10`****`-`****`06`****` `****`10`****`:`****`28`****`:`****`03.932`****` `****`678`****` `****`703`****` I `****`ActivityTaskManager`****`:`****` `****`Fully`****` drawn `****`<APP_PACKAGE_NAME`****`>`****`:`****` `****`+`****`5s36ms`**
メモリ
メモリKPIは、アプリのメモリ消費量の詳細な概要を提供します。このKPIは、メモリ値に加えて、フォアグラウンドまたはバックグラウンドのCPU使用率、RAM使用量、RAM空き容量、その他の詳細も測定します。このKPIでは、アプリがフォアグラウンドとバックグラウンドにあるときの測定値を取得して、さまざまなシナリオでユーザーが体感する実際的な動作を再現することを目指しています。
メモリ使用量を計算する方法
次のadbコマンドは、フォアグラウンドとバックグラウンドの両方のユースケースで、アプリのメモリ使用量を計算する方法を示しています。
adb shell dumpsys meminfo | grep "APP PACKAGE"
adb shell dumpsys meminfo | findstr "APP PACKAGE"
出力サンプル
81,773K: APP PACKAGE (pid 21917 / activities)
シナリオ: フォアグラウンドメモリ
フォアグラウンドメモリKPIは、フォアグラウンドにあるときのアプリのメモリ消費量をキャプチャします。これを測定するには、アプリを開き、ビデオ再生またはゲームプレイを15分間行ってから、アプリのメモリ消費量を計算します。
テスト手順
- アプリをダウンロードしてインストールし、該当する場合はアプリにサインインします。
- logcatの消去コマンドを実行します。
adb shell logcat -v threadtime -b all -c - アプリを起動します。
adb shell am start <アプリパッケージ>/<アプリのメインアクティビティ> - アプリのコアコンテンツ(ゲームやビデオなど)を15分間再生します。
dumpsysコマンドを実行し、total_pssメモリ値をキャプチャします。adb shell dumpsys meminfo -a <アプリパッケージ>- メモリ値をキャプチャしたら、アプリを強制停止するコマンドを実行します。
adb shell am force-stop <アプリパッケージ> - この手順を5回繰り返します。
シナリオ: バックグラウンドメモリ
バックグラウンドメモリKPIは、バックグラウンドにあるときのアプリのメモリ消費量をキャプチャします。これを測定するには、アプリを開き、ビデオ再生またはゲームプレイを10分間行い、アプリをバックグラウンドに移行させてから、アプリのメモリ消費量を計算します。バックグラウンドメモリ使用量にはしきい値が定義されていませんが、システムのメモリ(RAM)が不足すると、アプリのバックグラウンドメモリ使用量がバックグラウンドアプリの停止を決定する要因となります。システムがフォアグランドのタスクや優先度の高いタスクにメモリを必要としている場合、バックグランドメモリ使用量の最も多いアプリが最初に停止されます。
テスト手順
- アプリをダウンロードしてインストールし、該当する場合はアプリにサインインします。
- logcatの消去コマンドを実行します。
adb shell logcat -v threadtime -b all -c - アプリを起動します。
adb shell am start <アプリパッケージ>/<アプリのメインアクティビティ> - アプリのコアコンテンツ(ゲームやビデオなど)を10分間再生します。
- アプリをバックグラウンドに移行させるコマンドを実行します。
adb shell input keyevent KEYCODE_HOME - アプリがバックグラウンドで安定するまで10~15秒待ちます。
dumpsysコマンドを実行し、total_pssメモリ値をキャプチャします。adb shell dumpsys meminfo -a <アプリパッケージ>- この手順を5回繰り返します。
メモリADBダンプログ
比例セットサイズ(PSS)の合計は、デバイス上のアプリによって消費されるメモリ量です。PSSの合計は、アプリがフォアグラウンドまたはバックグラウンドにあるときのメモリ消費量の計算に使用されます。
Pss Pss Shared Private Shared Private SwapPss Heap Heap Heap
Total Clean Dirty Dirty Clean Clean Dirty Size Alloc Free
Native Heap 115268 0 384 115020 100 208 22 151552 119143 32408
Dalvik Heap 15846 0 264 15124 140 676 11 21026 14882 6144
Dalvik Other 8864 0 40 8864 0 0 0
Stack 136 0 4 136 0 0 0
Ashmem 132 0 264 0 12 0 0
Other dev 48 0 156 0 0 48 0
.so mmap 15819 9796 656 596 26112 9796 20
.apk mmap 2103 432 0 0 26868 432 0
.dex mmap 39396 37468 0 4 17052 37468 0
.oat mmap 1592 452 0 0 13724 452 0
.art mmap 2699 304 808 1956 12044 304 0
Other mmap 274 0 12 4 636 244 0
GL mtrack 42152 0 0 42152 0 0 0
Unknown 2695 0 92 2684 60 0 0
TOTAL 247077 48452 2680 186540 96748 49628 53 172578 134025 38552
KPIログファイルの作成
1回のイテレーションのKPIログファイルを作成するは、次のサンプルコードを使用します。
FileオブジェクトとFileWriterオブジェクトを作成しないようにして、FileWriterオブジェクトを閉じずに使用します。
public void log_file_writer(BufferedReader bf_read) {
File adb_log_file = new File(kpi_log_file_path + "/KPI_Log.txt");
FileWriter fileWriter = new FileWriter(adb_log_file);
try {
String reader = null;
while ((reader = bf_read.readLine()) != null) {
fileWriter.write(reader.trim() + "\n");
}
}
catch (Exception e) {
System.out.println(e);
}
finally {
fileWriter.flush();
fileWriter.close();
bf_read.close();
}
}
import java.io.File
import java.io.FileWriter
import java.io.BufferedReader
fun logFileWriter(bfRead: BufferedReader) {
val adbLogFile = File("$kpi_log_file_path/KPI_Log.txt")
val fileWriter = FileWriter(adbLogFile)
try {
var reader: String?
while (bfRead.readLine().also { reader = it } != null) {
fileWriter.write(reader?.trim() + "\n")
}
} catch (e: Exception) {
println(e)
} finally {
fileWriter.flush()
fileWriter.close()
bfRead.close()
}
}
関連トピック
- adbコマンドの詳細については、AndroidデベロッパードキュメントのAndroid Debug Bridge(adb)を参照してください。
Last updated: 2026年1月30日

