App Performance Scripts for Fire Tablet
Performance testing is the process of app testing in areas such as compatibility, reliability speed, response time, stability, and resource usage on Amazon Fire OS devices. You can use this testing to identify and address your app's performance bottlenecks. Performance testing involves gathering and evaluating key performance indicator (KPI) metrics. To gather KPI metrics, you run a specific set of steps on an Amazon device and then find or calculate the metrics using device resources, such as logs.
Make sure to run performance tests before submitting your app to Amazon Appstore. This page provides steps to test different categories of KPIs and includes example code that you can use in automation. This guide covers the following KPIs:
- Latency - time to first frame (TTFF)
- Ready-to-use - time to full display (TTFD)
- Memory after using the app's core functionality (for example, video streaming or game play)
Setup
To get started, install the following software packages on your development computer:
- Amazon Corretto
- Android Studio (make sure to install platform tools during setup)
- Appium
In addition to the installing the software packages, you will need to:
- Set up a path for JAVA_HOME and ANDROID_HOME folders.
- Enable developer mode on the device and enable USB Debugging. For instructions, see Enable Developer Options.
- Capture the serial number of the attached device. To list the serial numbers of physically attached devices, you can use the Android Debug Bridge (ADB) command
adb devices -l.
Test strategy
During testing, you launch the app and force stop it several times by using the app launcher intent or the Monkey tool. Between each iteration, you must perform certain actions, such as capturing ADB Logcat logs, performing navigation actions, capturing timer values from vitals and ADB logs, and capturing memory and RAM usage before force stopping the app. This loop continues for the number of iterations you configure. As network conditions, system load, and other factors can impact the test results, use multiple iterations to average out the interference from external factors.
To calculate metric averages, Amazon recommends running a minimum number of iterations for the following test categories.
| Performance test category | Minimum number of iterations recommended |
|---|---|
| Latency - time to first frame (TTFF) | 50 |
| Ready-to-use - time to full display (TTFD) | 10 |
| Memory | 5 |
Devices to test:
- Fire OS 8: Fire HD 10 (2023)
- Fire OS 8: Fire Max 11 (2023)
You can use the logs, screenshots, and other artifacts captured from the performance tests for debugging or data use. The Appium device object is force stopped as part of tear-down.
The following sections contain code examples that you can add to your test automation script.
Get device type
The following example code shows how to get the device type of the connected device.
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("Exception while getting device type info: " + 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("Exception while getting device type info: $e")
}
return deviceType
}
Retrieve component name of the main launcher activity
The following example code shows how to retrieve the component name of the main launcher activity. This method fetches the main activity of the app under test and constructs the component name by combining the names of the app package and main activity.
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("There was an exception while retrieving App Main Activity" + 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("There was an exception while retrieving App Main Activity: $e")
}
Launch an app using the component name of the main launcher activity
Use the following example code to launch an app using the component name of the main launcher activity. The code uses the componentName variable defined in the previous section, which creates the component name by combining app package and main activity.
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("App Launch successful using - " + componentName);
break;
} else if (line.contains("Error")) {
System.out.println("App Launch Error");
}
}
} catch (Exception e) {
System.out.println("There was an exception while launching the app:" + 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("App Launch successful using - $componentName")
break
} else if (line!!.contains("Error")) {
println("App Launch Error")
}
}
} catch (e: Exception) {
println("There was an exception while launching the app: $e")
}
Launch an app using the Monkey tool
The following code shows how to launch an app using the Monkey tool.
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("App Launch successful using Monkey Tool - " + appPackage);
launchRead.close();
return true;
}
else if (line.contains("Error") || line.contains("No activities found")) {
System.out.println("Error while launching app through Monkey Tool, using Intent to launch");
launchRead.close();
return false;
}
}
}
catch (Exception e) {
System.out.println("There was an exception while launching the app using 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("App Launch successful using Monkey Tool - $appPackage")
launchRead.close()
return true
}
else if (line!!.contains("Error") || line!!.contains("No activities found")) {
println("Error while launching app through Monkey Tool, using Intent to launch")
launchRead.close()
return false
}
}
}
catch (e: Exception) {
println("There was an exception while launching the app using Monkey $e")
return false
}
Force stop an app
The following example code shows how to force stop an app.
try {
Runtime.getRuntime().exec("adb -s "+ DSN +" shell am force-stop " + appPackage);
System.out.println("App force stopped - " + appPackage);
}
catch (Exception e) {
System.out.println("There was an exception in force stopping app" + e);
}
try {
Runtime.getRuntime().exec("adb -s ${DSN} shell am force-stop ${appPackage}")
println("App force stopped - $appPackage")
} catch (e: Exception) {
println("There was an exception in force stopping the app: $e")
}
General commands
The following sections provide examples of commands you can use in performance testing.
Capture ADB logs
The following example code shows how to capture ADB logs that use the threadtime format.
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";
//Below method is used to append adb commands with the DSN value of the Device
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("Exception while executing adb commands" + 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"
//Below function is used to append adb commands with the DSN value of the Device
fun adb(DSN: String, message: String): Process? {
return try {
Runtime.getRuntime().exec("adb -s $DSN$message")
}
catch (e: Exception) {
println("Exception while executing adb commands$e")
null
}
}
Capture timer values from vital buffer logs
The following example code shows how to filter out the performance latency timer value from the vital buffers.
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
}
Capture timer values from activity manager buffer logs
The following code shows how to filter out the timer values of the app by using the activity or window manager buffer.
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;
}
Common abbreviations used in logs
Before launching an app programmatically, you must have some basic parameters such as the app's package name and main activity. To help with automation, you can also add constants for values you commonly encounter in the logs. The following are constants you can add in your automation script for performance testing.
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:"
Latency - time to first frame
The latency KPI, time to first frame (TTFF), measures how long it takes for an app to display its first visual frame after it launches. By taking measurements during cold and warm launches, this KPI aims to replicate realistic user behavior in different scenarios.
Find the time to first frame drawn
The following ADB command shows how to find the time to first frame drawn.
adb logcat | grep "Displayed <app_package_name>/<launcher_activity>"
adb logcat | findstr "Displayed <app_package_name>\<launcher_activity>"
Example output
ActivityManager: Displayed <app_package_name>/<launcher_activity> +930ms (total +849ms)
Scenario: Cold start - time to first frame
A TTFF cold start is the time it takes for an app to launch and display its first frame after the app process has been stopped or the device has been rebooted. When testing a cold start, you start the app after it is force stopped, which simulates a first use or fresh launch scenario. A cold start launch typically takes longer than a warm start launch because the app must reload services.
To measure the time to first frame for a cold start launch
- Run the logcat clear command.
adb shell logcat -v threadtime -b all -c - Download, launch, and sign in to the app.
adb shell am start <app_package_name>/<launcher_activity> - Run the logcat command.
adb shell logcat -v threadtime -b all -d - Capture the timer value by finding the log line with the following string.
cool_app_launch_time - After the app has fully launched, force stop the app.
adb shell am force-stop <app_package_name> - Repeat the steps for 50 iterations.
Cold start iterations
The following example code runs the latency cold start test and prints the latency values for the configured number of iterations.
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")
}
Device cold start vital logs
The following is an example of device cold start vital logs.
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
To find the time to first frame, see Find the time to first frame drawn.
Scenario: Warm start - time to first frame
A TTFF warm start is the time it takes for an app to launch and display its first frame when the app process is already running in the background. The system brings the app from the background to the foreground as part of this launch activity. When testing a warm start, you launch the app after it has been in the background. A warm start launch is typically faster than a cold start launch because the app already has services cached.
To measure the time to first frame for a warm start launch
- Make sure the app is running in the background.
- Run the logcat clear command.
adb shell logcat -v threadtime -b all -c - Launch and sign in to the app.
adb shell am start <app_package_name>/<launcher_activity> - Run the logcat command.
adb shell logcat -v threadtime -b all -d - Capture the timer value by finding the log line with the following string.
warm_app_warm_transition_launch_time - After the app has fully launched, run the following command.
adb shell input keyevent KEYCODE_HOME - Repeat the steps for 50 iterations.
Warm start iterations
The following example code runs the latency warm start test and prints the latency values for the configured number of iterations.
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")
}
Device warm start vital logs
The following is an example of device warm start vital logs.
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
To find the time to first frame, see Find the time to first frame drawn.
Ready-to-use - time to full display
The ready-to-use (RTU) KPI, time to full display (TTFD), measures the time an app takes from launch to the ready-to-use state. For example, a ready-to-use state can be when the app's sign-in or home page is usable. RTU metrics can help identify launch performance issues in an app. By taking measurements during cold and warm launches, this KPI aims to replicate realistic user behavior in different scenarios.
For apps that require a user to sign in, measure the RTU KPIs for the post sign-in use case.
Detect the fully drawn state
If you implemented the reportFullyDrawn() method in your app, you can use the following ADB command to detect the fully drawn state.
adb logcat | grep "Fully drawn <app_package_name>/<launcher_activity>"
adb logcat | findstr "Fully drawn <app_package_name>\<launcher_activity>"
Sample output
ActivityManager: Fully drawn <app_package_name>/<launcher_activity> +930ms (total +849ms)
Scenario: RTU cold start - time to full display
An RTU cold start is the time it takes for an app to launch, become fully drawn, and be ready for user interaction after the app process has been stopped or the device has been rebooted. When testing a cold start, you start the app after it is force stopped, which simulates a first use or fresh launch scenario. A cold start launch typically takes longer than a warm start launch because the app must reload services.
Test steps
- Make sure you are signed in to the app.
- Run the logcat clear command.
adb shell logcat -v threadtime -b all -c - Launch the app.
adb shell am start <app_package_name>/<launcher_activity> - Run the logcat command and capture the fully-drawn timer value.
adb shell logcat -v threadtime -b all -d - After the app fully launches and you capture the timer value, run the following command to force stop the app.
adb shell am force-stop <app_package_name> - Repeat the steps for 10 iterations.
Scenario: RTU warm start - time to full display
An RTU warm start is the time it takes for an app to launch, become fully drawn, and be ready for user interaction when the app process is already running in the background. The system brings the app from the background to the foreground as part of this launch activity. When testing a warm start, you launch the app after it has been in the background. A warm start launch is typically faster than a cold start launch because the app already has services cached.
Test steps
- After making sure you are signed in to the app, put the app in the background.
- Run the logcat clear command.
adb shell logcat -v threadtime -b all -c - Launch the app.
adb shell am start <app_package_name>/<launcher_activity> - Run the logcat command and capture the fully-drawn timer value.
adb shell logcat -v threadtime -b all -d - After the app fully launches, run the command to move the app to background.
adb shell input keyevent KEYCODE_HOME - Repeat the steps for 10 iterations.
To find the time to full display, see Detect the Fully Drawn State.
RTU ADB logs
The following is an example log for a fully drawn activity for a game on an Amazon Fire tablet device.
**`10`****`-`****`06`****` `****`10`****`:`****`28`****`:`****`03.932`****` `****`678`****` `****`703`****` I `****`ActivityTaskManager`****`:`****` `****`Fully`****` drawn `****`<APP_PACKAGE_NAME`****`>`****`:`****` `****`+`****`5s36ms`**
Memory
The memory KPI provides a detailed overview of the app's memory consumption. In addition to memory values, this KPI also measures foreground and background CPU usage, RAM usage, RAM free, and other specifics. By taking measurements when the app is in the foreground and background, this KPI aims to replicate realistic user behavior in different scenarios.
Calculate memory usage
The following ADB command shows how to calculate app memory usage for both foreground and background use cases.
adb shell dumpsys meminfo | grep "<app_package_name>"
adb shell dumpsys meminfo | findstr "<app_package_name>"
Sample output
81,773K: <app_package_name> (pid 21917 / activities)
Scenario: Foreground memory
The foreground memory KPI captures the app's memory consumption while in the foreground. To measure this, you open the app and play a video or game for 15 minutes and then calculate the app's memory consumption.
Test steps
- Download, install, and sign in to the app (if applicable).
- Run the logcat clear command.
adb shell logcat -v threadtime -b all -c - Launch the app.
adb shell am start <app_package_name>/<launcher_activity> - Play the app's core contents (such as a game or video) for 15 minutes.
- Run the
dumpsyscommand and capture thetotal_pssmemory value.adb shell dumpsys meminfo -a <app_package_name> - After you capture the memory value, run the command to force stop the app.
adb shell am force-stop <app_package_name> - Repeat the steps for 5 iterations.
Scenario: Background memory
The background memory KPI captures the app's memory consumption while in the background. To measure this, you open the app and play a video or game for 10 minutes, background the app, and then calculate the app's memory consumption. While there is no defined threshold for background memory consumption, the amount of memory an app uses in the background is a deciding factor for stopping a background app when the system is running low on memory (RAM). Apps with the highest background memory consumption are the first to be stopped when the system needs more memory for its foreground and other priority tasks.
Test steps
- Download, install, and sign in to the app (if applicable).
- Run the logcat clear command.
adb shell logcat -v threadtime -b all -c - Launch the app.
adb shell am start <app_package_name>/<launcher_activity> - Play the app's core contents (such as a game or video) for 10 minutes.
- Run the command to move the app to the background.
adb shell input keyevent KEYCODE_HOME - Wait 10–15 seconds for the app to stabilize in the background.
- Run the
dumpsyscommand and capture thetotal_pssmemory value.adb shell dumpsys meminfo -a <app_package_name> - Repeat the steps for 5 iterations.
Memory ADB dump log
The proportional set size (PSS) total is the amount of memory consumed by the app on the device. PSS total is used to calculate the memory consumption of an app when it is in the foreground or background.
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
Write a KPI log file
Use the following example code to write a KPI log file for one iteration.
File and FileWriter objects are not created, and remember not to close the FileWriter object.
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()
}
}
Related topics
- To learn more about ADB commands, see Android Debug Bridge (adb) in the Android developer documentation.
Last updated: Jan 30, 2026

