Performance/Fenix/Debug API: Difference between revisions
(Add call to super in Debug API) |
(→Usage for start up: add Android 11+ instructions) |
||
| (5 intermediate revisions by the same user not shown) | |||
| Line 2: | Line 2: | ||
* execute fairly early in the profile (when the application can start executing code) | * execute fairly early in the profile (when the application can start executing code) | ||
* capture all Java threads | * capture all Java threads | ||
* can be opened the Firefox Profiler interface | * can be opened in the Firefox Profiler interface | ||
The downsides are that: | The downsides are that: | ||
* has | * has noticeable overhead (a few hundred ms in a 1.5-2.5s start up) | ||
* requires manually instrumenting the code | * requires manually instrumenting the code | ||
| Line 13: | Line 13: | ||
You can open <code>*.trace</code> 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. | You can open <code>*.trace</code> 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 == | == 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 <code>FenixApplication.kt</code> to measure start up as early as possible: | |||
<syntaxhighlight lang="kotlin"> | |||
override fun attachBaseContext(base: Context) { | |||
if (base.isMainProcess()) { | |||
Debug.startMethodTracingSampling( | |||
base.filesDir.absolutePath + "/startup.trace", | |||
0, | |||
TimeUnit.MILLISECONDS.toMicros(1).toInt() | |||
) | |||
} | |||
super.attachBaseContext(base) | |||
} | |||
</syntaxhighlight> | |||
Then, we need to stop tracing by calling <code>Debug.stopMethodTracing()</code>: 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 <code>HomeActivity.onResume</code>. Here is a sample of code you can add to that method: | |||
<syntaxhighlight lang="kotlin"> | |||
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) | |||
</syntaxhighlight> | |||
Then we need to add code to <code>onActivityResult</code> to get the file path the user selected and copy the profile data to that file: | |||
<syntaxhighlight lang="kotlin"> | |||
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 | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
Once the device has hit <code>stopMethodTracing</code> and you selected a save location, you can pull the profile from the device. For example, if you saved <code>startup.trace</code> to the downloads directory, you can probably run: | |||
<syntaxhighlight lang="sh"> | |||
adb pull /sdcard/Download/startup.trace | |||
</syntaxhighlight> | |||
This profile can then be [[#Firefox Profiler interface|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 <code>FenixApplication.kt</code> to measure start up as early as possible: | First, you need to start tracing. Add the following to <code>FenixApplication.kt</code> to measure start up as early as possible: | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="kotlin"> | ||
override fun attachBaseContext(base: Context) { | |||
if (base.isMainProcess()) { | |||
Debug.startMethodTracingSampling( | |||
"startup", | |||
0, | |||
TimeUnit.MILLISECONDS.toMicros(1).toInt() | |||
) | |||
} | |||
super.attachBaseContext(base) | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Latest revision as of 20:58, 28 June 2022
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).