as

Settings
Sign out
Notifications
Alexa
Amazon Appstore
AWS
Documentation
Support
Contact Us
My Cases
Develop
Test
Publish
Monetize
Engage users
Device specifications
Resources

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:

Setup

To get started, install the following software packages on your development computer:

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.

Copied to clipboard.

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;
}

Copied to clipboard.

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.

Copied to clipboard.

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);
}

Copied to clipboard.

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.

Copied to clipboard.

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);
        }

Copied to clipboard.

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.

Copied to clipboard.

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;
}  

Copied to clipboard.

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.

Copied to clipboard.

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);
}

Copied to clipboard.


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.

Copied to clipboard.

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;
}

Copied to clipboard.

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.

Copied to clipboard.

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;
}

Copied to clipboard.

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.

Copied to clipboard.

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;
    }

Copied to clipboard.

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.

Copied to clipboard.

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:";

Copied to clipboard.

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.

Copied to clipboard.

adb logcat | grep "Displayed <app_package_name>/<launcher_activity>"

Copied to clipboard.

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

  1. Run the logcat clear command.
     adb shell logcat -v threadtime -b all -c
    
  2. Download, launch, and sign in to the app.
     adb shell am start <app_package_name>/<launcher_activity>
    
  3. Run the logcat command.
     adb shell logcat -v threadtime -b all -d
    
  4. Capture the timer value by finding the log line with the following string.
     cool_app_launch_time
    
  5. After the app has fully launched, force stop the app.
     adb shell am force-stop <app_package_name>
    
  6. 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.

Copied to clipboard.

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");
}

Copied to clipboard.

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

  1. Make sure the app is running in the background.
  2. Run the logcat clear command.
     adb shell logcat -v threadtime -b all -c
    
  3. Launch and sign in to the app.
     adb shell am start <app_package_name>/<launcher_activity>
    
  4. Run the logcat command.
     adb shell logcat -v threadtime -b all -d 
    
  5. Capture the timer value by finding the log line with the following string.
     warm_app_warm_transition_launch_time
    
  6. After the app has fully launched, run the following command.
     adb shell input keyevent KEYCODE_HOME
    
  7. 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.

Copied to clipboard.

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");
}

Copied to clipboard.

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.

Copied to clipboard.

adb logcat | grep "Fully drawn <app_package_name>/<launcher_activity>"

Copied to clipboard.

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

  1. Make sure you are signed in to the app.
  2. Run the logcat clear command.
     adb shell logcat -v threadtime -b all -c
    
  3. Launch the app.
     adb shell am start <app_package_name>/<launcher_activity>
    
  4. Run the logcat command and capture the fully-drawn timer value.
     adb shell logcat -v threadtime -b all -d
    
  5. 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>
    
  6. 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

  1. After making sure you are signed in to the app, put the app in the background.
  2. Run the logcat clear command.
     adb shell logcat -v threadtime -b all -c
    
  3. Launch the app.
     adb shell am start <app_package_name>/<launcher_activity>
    
  4. Run the logcat command and capture the fully-drawn timer value.
     adb shell  logcat -v threadtime -b all -d
    
  5. After the app fully launches, run the command to move the app to background.
     adb shell input keyevent KEYCODE_HOME
    
  6. 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.

Copied to clipboard.

adb shell dumpsys meminfo | grep "<app_package_name>"

Copied to clipboard.

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

  1. Download, install, and sign in to the app (if applicable).
  2. Run the logcat clear command.
     adb shell logcat -v threadtime -b all -c
    
  3. Launch the app.
     adb shell am start <app_package_name>/<launcher_activity>
    
  4. Play the app's core contents (such as a game or video) for 15 minutes.
  5. Run the dumpsys command and capture the total_pss memory value.
     adb shell  dumpsys meminfo -a <app_package_name>
    
  6. After you capture the memory value, run the command to force stop the app.
     adb  shell am force-stop <app_package_name>
    
  7. 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

  1. Download, install, and sign in to the app (if applicable).
  2. Run the logcat clear command.
     adb shell logcat -v threadtime -b all -c
    
  3. Launch the app.
     adb shell am start <app_package_name>/<launcher_activity>
    
  4. Play the app's core contents (such as a game or video) for 10 minutes.
  5. Run the command to move the app to the background.
     adb shell input keyevent KEYCODE_HOME
    
  6. Wait 10–15 seconds for the app to stabilize in the background.
  7. Run the dumpsys command and capture the total_pss memory value.
     adb shell  dumpsys meminfo -a <app_package_name>
    
  8. 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.

Copied to clipboard.

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();
  }   
}

Copied to clipboard.

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()
    }
}

Last updated: Jan 30, 2026