[fenix] Added ProfilerUtils
parent
e6fa624820
commit
48d2188d8c
@ -0,0 +1,152 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.perf
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import mozilla.components.concept.fetch.MutableHeaders
|
||||
import mozilla.components.concept.fetch.Request
|
||||
import mozilla.components.concept.fetch.Response
|
||||
import org.json.JSONObject
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.components
|
||||
import java.io.BufferedReader
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
private const val PROFILER_API = "https://api.profiler.firefox.com/compressed-store"
|
||||
private const val PROFILER_SERVER_HEADER = "application/vnd.firefox-profiler+json;version=1.0"
|
||||
private const val TOKEN = "profileToken"
|
||||
private const val PROFILER_DATA_URL = "https://profiler.firefox.com/public/"
|
||||
|
||||
private val firefox_features = arrayOf(
|
||||
"screenshots", "js", "leaf", "stackwalk", "cpu", "java",
|
||||
"processcpu", "ipcmessages", "java"
|
||||
)
|
||||
private val firefox_threads = arrayOf(
|
||||
"GeckoMain", "Compositor", "Renderer",
|
||||
"SwComposite", "DOM Worker"
|
||||
)
|
||||
|
||||
private val graphics_features = arrayOf("java", "ipcmessages")
|
||||
private val graphics_threads = arrayOf(
|
||||
"GeckoMain", "Compositor", "Renderer", "SwComposite",
|
||||
"RenderBackend", "SceneBuilder", "WrWorker", "CanvasWorkers",
|
||||
)
|
||||
|
||||
private val media_features = arrayOf(
|
||||
"js", "leaf", "stackwalk", "cpu", "audiocallbacktracing",
|
||||
"ipcmessages", "processcpu", "java"
|
||||
)
|
||||
private val media_threads = arrayOf(
|
||||
"cubeb", "audio", "camera", "capture", "Compositor", "GeckoMain", "gmp", "graph", "grph",
|
||||
"InotifyEventThread", "IPDL Background", "media", "ModuleProcessThread", "PacerThread",
|
||||
"RemVidChild", "RenderBackend", "Renderer", "Socket Thread", "SwComposite",
|
||||
"webrtc"
|
||||
)
|
||||
|
||||
private val networking_features = arrayOf(
|
||||
"screenshots", "js", "leaf", "stackwalk", "cpu", "java",
|
||||
"processcpu", "ipcmessages"
|
||||
)
|
||||
|
||||
private val networking_threads = arrayOf(
|
||||
"Compositor", "DNS Resolver", "DOM Worker", "GeckoMain",
|
||||
"Renderer", "Socket Thread", "StreamTrans", "SwComposite", "TRR Background"
|
||||
)
|
||||
|
||||
/**
|
||||
* Profiler settings enum for grouping features and settings together
|
||||
*/
|
||||
enum class ProfilerSettings(val threads: Array<String>, val features: Array<String>) {
|
||||
Firefox(firefox_threads, firefox_features),
|
||||
Graphics(graphics_threads, graphics_features),
|
||||
Media(media_threads, media_features),
|
||||
Networking(networking_threads, networking_features);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection of functionality to parse and save the profile returned by GeckoView.
|
||||
*/
|
||||
object ProfilerUtils {
|
||||
|
||||
private fun saveProfileUrlToClipboardAndToast(profileResult: ByteArray, context: Context): String {
|
||||
//The profile is saved to a temporary file since our fetch API takes a file or a string.
|
||||
// Converting the ByteArray to a String would hurt the encoding, which we need to preserve.
|
||||
val outputFile = createTemporaryFile(profileResult, context)
|
||||
val response = networkCallToProfilerServer(outputFile, context)
|
||||
val profileToken = decodeProfileToken(response)
|
||||
outputFile.delete()
|
||||
return PROFILER_DATA_URL + profileToken
|
||||
}
|
||||
|
||||
private fun finishProfileSave(context: Context, url: String, onUrlFinish: (Int) -> Unit) {
|
||||
val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clipData = ClipData.newPlainText("Profiler URL", url)
|
||||
clipboardManager.setPrimaryClip(clipData)
|
||||
onUrlFinish(R.string.profiler_uploaded_url_to_clipboard)
|
||||
}
|
||||
|
||||
private fun createTemporaryFile(profileResult: ByteArray, context: Context): File {
|
||||
val outputDir = context.cacheDir
|
||||
val outputFile = File.createTempFile("tempProfile", ".json", outputDir)
|
||||
FileOutputStream(outputFile).use { fileOutputStream ->
|
||||
fileOutputStream.write(profileResult)
|
||||
return outputFile
|
||||
}
|
||||
}
|
||||
|
||||
private fun networkCallToProfilerServer(outputFile: File, context: Context): Response {
|
||||
val request = Request(
|
||||
url = PROFILER_API,
|
||||
method = Request.Method.POST,
|
||||
headers = MutableHeaders(
|
||||
"Accept" to PROFILER_SERVER_HEADER
|
||||
),
|
||||
body = Request.Body.fromFile(outputFile)
|
||||
)
|
||||
return context.components.core.client.fetch(request)
|
||||
}
|
||||
|
||||
private fun decodeProfileToken(response: Response): String {
|
||||
val jwtToken = StringBuilder()
|
||||
response.body.useBufferedReader {
|
||||
val jwt = it.readText()
|
||||
val jwtSplit = jwt.split(".")
|
||||
val decodedBytes = Base64.decode(jwtSplit[1], Base64.DEFAULT)
|
||||
val decodedStr = decodedBytes.decodeToString()
|
||||
val jsonObject = JSONObject(decodedStr)
|
||||
jwtToken.append(jsonObject[TOKEN])
|
||||
}
|
||||
return jwtToken.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* This will either save the profile locally or send it as a URL to the Firefox profiler server
|
||||
*
|
||||
* @param context Activity context to get access to the profiler API through components.core...
|
||||
* @param profile Data returned from GeckoView as a GZIP ByteArray
|
||||
* @param onUrlFinish function passed in to display a toast with the relevant information once the profile is saved
|
||||
*/
|
||||
fun handleProfileSave(
|
||||
context: Context,
|
||||
profile: ByteArray,
|
||||
onUrlFinish: (Int) -> Unit
|
||||
) {
|
||||
try {
|
||||
val url = saveProfileUrlToClipboardAndToast(profile, context)
|
||||
finishProfileSave(context, url, onUrlFinish)
|
||||
} catch (e: IOException) {
|
||||
onUrlFinish(R.string.profiler_io_error)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue