Performance/Fenix/Debug API

From MozillaWiki
Jump to: navigation, search

The built-in Debug API (documentation) can be used to capture profiles that:

  • execute fairly early in the profile (when the application can start executing code)
  • capture all Java threads
  • can be opened in the Firefox Profiler interface

The downsides are that:

  • has noticeable overhead (a few hundred ms in a 1.5-2.5s start up)
  • requires manually instrumenting the code

The Firefox Profiler is generally preferred but the Debug API can be used to work around some of its limitations. For an overview of other profiling tools, see Performance/Fenix/Getting Started.

Firefox Profiler interface

You can open *.trace files generated by the Debug API in the Firefox Profiler. Just go to https://profiler.firefox.com/ and click "Load a profile from a file". The profiler interface works nearly as well as profiles taken within the Firefox Profiler.

Usage for start up: Android 11 and later

On Android 11 and later OS versions, we need to save the profile data to a file within our data directory, prompt the user to create a file for us, and copy the profile data into that file.

First, you need to start tracing. Add the following to FenixApplication.kt to measure start up as early as possible:

    override fun attachBaseContext(base: Context) {
        if (base.isMainProcess()) {
            Debug.startMethodTracingSampling(
                base.filesDir.absolutePath + "/startup.trace",
                0,
                TimeUnit.MILLISECONDS.toMicros(1).toInt()
            )
        }
        super.attachBaseContext(base)
    }

Then, we need to stop tracing by calling Debug.stopMethodTracing(): where you call this depends on what you're trying to profile. Immediately after we stop the profiler, we need to prompt the user for a file we can save the profile into. An easy place to put it all is at the end of HomeActivity.onResume. Here is a sample of code you can add to that method:

    Debug.stopMethodTracing()
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/octet-stream"
        putExtra(Intent.EXTRA_TITLE, "startup.trace")
    }
    @Suppress("DEPRECATION")
    startActivityForResult(intent, 1234)

Then we need to add code to onActivityResult to get the file path the user selected and copy the profile data to that file:

    if (requestCode == 1234) {
        require(resultCode == Activity.RESULT_OK)
        data?.data?.also { uri ->
            // This is main thread IO: we should never do this in a production app.
            File(filesDir.absolutePath + "/startup.trace").inputStream().buffered().use { inputStream ->
                contentResolver.openOutputStream(uri)!!.buffered().use { outputStream ->
                    val buffer = ByteArray(1024 * 8)
                    while (true) {
                        val bytesRead = inputStream.read(buffer, 0, 1024 * 8)
                        if (bytesRead != -1) {
                            outputStream.write(buffer, 0, bytesRead)
                        } else {
                            break
                        }
                    }
                }
            }
        }
    }

Once the device has hit stopMethodTracing and you selected a save location, you can pull the profile from the device. For example, if you saved startup.trace to the downloads directory, you can probably run:

adb pull /sdcard/Download/startup.trace

This profile can then be opened by the Firefox Profiler (preferred) or the Android Studio profiler (with File -> Open).

Usage for start up: Android 10 and earlier

On Android 10 and earlier OS versions, we can use a simpler technique: we can write the profile data directly to external storage.

First, you need to start tracing. Add the following to FenixApplication.kt to measure start up as early as possible:

    override fun attachBaseContext(base: Context) {
        if (base.isMainProcess()) {
            Debug.startMethodTracingSampling(
                "startup",
                0,
                TimeUnit.MILLISECONDS.toMicros(1).toInt()
            )
        }
        super.attachBaseContext(base)
    }

Then, we need to stop tracing by calling Debug.stopMethodTracing(): where you call this depends on what you're trying to profile. An easy place to put it is at the end of HomeActivity.onResume.

The trace file will be saved to the device at /sdcard/<filename> by default. The app requires permission to access this so run this before launching the app for the first time:

adb shell pm grant org.mozilla.fenix android.permission.READ_EXTERNAL_STORAGE
adb shell pm grant org.mozilla.fenix android.permission.WRITE_EXTERNAL_STORAGE

Now launch the app!

Once the device has hit stopMethodTracing, you can pull the profile from the device. If you didn't change the file name provided to start* in the code sample above, you can run the following to get the profile:

adb pull /sdcard/startup.trace

This profile can then be opened by the Firefox Profiler (preferred) or the Android Studio profiler (with File -> Open).