blob: 68803a4c2a3bceb547da11115cf99126bef86f04 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.benchmark.macro.perfetto
import android.util.Log
import androidx.benchmark.Outputs
import androidx.benchmark.Shell
import androidx.benchmark.perfetto.PerfettoHelper
import androidx.benchmark.userspaceTrace
import org.jetbrains.annotations.TestOnly
import java.io.File
/**
* Enables parsing perfetto traces on-device
*/
internal object PerfettoTraceProcessor {
private const val TAG = "PerfettoTraceProcessor"
/**
* The actual [File] path to the `trace_processor_shell`.
*
* Lazily copies the `trace_processor_shell` and enables parsing of the perfetto trace files.
*/
@get:TestOnly
val shellPath: String by lazy {
// Checks for ABI support
PerfettoHelper.createExecutable("trace_processor_shell")
}
private fun validateTracePath(absoluteTracePath: String) {
require(!absoluteTracePath.contains(" ")) {
"Trace path must not contain spaces: $absoluteTracePath"
}
}
fun getJsonMetrics(absoluteTracePath: String, metric: String): String {
validateTracePath(absoluteTracePath)
require(!metric.contains(" ")) {
"Metric must not contain spaces: $metric"
}
val command = "$shellPath --run-metric $metric $absoluteTracePath --metrics-output=json"
Log.d(TAG, "Executing command $command")
val json = userspaceTrace("trace_processor_shell") {
Shell.executeCommand(command)
.trim() // trim to enable empty check below
}
Log.d(TAG, "Trace Processor result: \n\n $json")
if (json.isEmpty()) {
throw IllegalStateException(
"Empty json result from Trace Processor - " +
"possibly malformed command? Command: $command"
)
}
return json
}
/**
* Query a trace for a list of slices - name, timestamp, and duration.
*/
fun querySlices(
absoluteTracePath: String,
vararg sliceNames: String
): List<Slice> {
val whereClause = sliceNames
.joinToString(separator = " OR ") {
"slice.name = '$it'"
}
return Slice.parseListFromQueryResult(
queryResult = rawQuery(
absoluteTracePath = absoluteTracePath,
query = """
SELECT slice.name,ts,dur
FROM slice
JOIN thread_track ON thread_track.id = slice.track_id
WHERE $whereClause
""".trimMargin()
)
)
}
internal fun rawQuery(
absoluteTracePath: String,
query: String
): String {
validateTracePath(absoluteTracePath)
val queryFile = File(Outputs.dirUsableByAppAndShell, "trace_processor_query.sql")
try {
queryFile.writeText(query)
val command = "$shellPath --query-file ${queryFile.absolutePath} $absoluteTracePath"
return userspaceTrace("trace_processor_shell") {
Shell.executeCommand(command)
}
} finally {
queryFile.delete()
}
}
}