Tracing.kt
/*
* Copyright 2022 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.tracing.perfetto
import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.tracing.perfetto.PerfettoHandshake.EnableTracingResponse
import androidx.tracing.perfetto.PerfettoHandshake.ResponseExitCodes.RESULT_CODE_ALREADY_ENABLED
import androidx.tracing.perfetto.PerfettoHandshake.ResponseExitCodes.RESULT_CODE_ERROR_BINARY_MISSING
import androidx.tracing.perfetto.PerfettoHandshake.ResponseExitCodes.RESULT_CODE_ERROR_BINARY_VERIFICATION_ERROR
import androidx.tracing.perfetto.PerfettoHandshake.ResponseExitCodes.RESULT_CODE_ERROR_BINARY_VERSION_MISMATCH
import androidx.tracing.perfetto.PerfettoHandshake.ResponseExitCodes.RESULT_CODE_ERROR_OTHER
import androidx.tracing.perfetto.PerfettoHandshake.ResponseExitCodes.RESULT_CODE_SUCCESS
import androidx.tracing.perfetto.jni.PerfettoNative
import androidx.tracing.perfetto.security.IncorrectChecksumException
import androidx.tracing.perfetto.security.SafeLibLoader
import androidx.tracing.perfetto.security.UnapprovedLocationException
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.withLock
object Tracing {
/**
* Indicates whether the tracing library has been loaded and the app registered with
* Perfetto SDK.
*/
// Note: some of class' code relies on the field never changing from true -> false,
// which is realistic (at the time of writing this, we are unable to unload the library and
// unregister the app with Perfetto).
var isEnabled: Boolean = false
private set
/**
* Ensures that we enable tracing (load the tracing library and register with Perfetto) only
* once.
*
* Note: not intended for synchronization during tracing as not to impact performance.
*/
private val enableTracingLock = ReentrantReadWriteLock()
@RequiresApi(Build.VERSION_CODES.R) // TODO(234351579): Support API < 30
internal fun enable() = enable(null)
@RequiresApi(Build.VERSION_CODES.R) // TODO(234351579): Support API < 30
internal fun enable(file: File, context: Context) = enable(file to context)
@RequiresApi(Build.VERSION_CODES.R) // TODO(234351579): Support API < 30
private fun enable(descriptor: Pair<File, Context>?): EnableTracingResponse {
enableTracingLock.readLock().withLock {
if (isEnabled) return EnableTracingResponse(RESULT_CODE_ALREADY_ENABLED)
}
enableTracingLock.writeLock().withLock {
return enableImpl(descriptor)
}
}
/** Calling thread must obtain a write lock on [enableTracingLock] before calling this method */
@RequiresApi(Build.VERSION_CODES.R) // TODO(234351579): Support API < 30
private fun enableImpl(descriptor: Pair<File, Context>?): EnableTracingResponse {
if (!enableTracingLock.isWriteLockedByCurrentThread) throw RuntimeException()
if (isEnabled) return EnableTracingResponse(RESULT_CODE_ALREADY_ENABLED)
// Load library
try {
when (descriptor) {
null -> PerfettoNative.loadLib()
else -> descriptor.let { (file, context) ->
PerfettoNative.loadLib(file, SafeLibLoader(context))
}
}
} catch (t: Throwable) {
return when (t) {
is IncorrectChecksumException, is UnapprovedLocationException ->
EnableTracingResponse(RESULT_CODE_ERROR_BINARY_VERIFICATION_ERROR, t)
is UnsatisfiedLinkError ->
EnableTracingResponse(RESULT_CODE_ERROR_BINARY_MISSING, t)
is Exception ->
EnableTracingResponse(RESULT_CODE_ERROR_OTHER, t)
else -> throw t
}
}
// Verify binary/java version match
val nativeVersion = PerfettoNative.nativeVersion()
val javaVersion = PerfettoNative.Metadata.version
if (nativeVersion != javaVersion) {
return EnableTracingResponse(
RESULT_CODE_ERROR_BINARY_VERSION_MISMATCH,
"Binary and Java version mismatch. Binary: $nativeVersion. Java: $javaVersion"
)
}
// Register as a Perfetto SDK data-source
try {
PerfettoNative.nativeRegisterWithPerfetto()
} catch (e: Exception) {
return EnableTracingResponse(RESULT_CODE_ERROR_OTHER, e)
}
isEnabled = true
return EnableTracingResponse(RESULT_CODE_SUCCESS)
}
/** Writes a trace message to indicate that a given section of code has begun. */
fun traceEventStart(key: Int, traceInfo: String) {
if (isEnabled) {
PerfettoNative.nativeTraceEventBegin(key, traceInfo)
}
}
/** Writes a trace message to indicate that a given section of code has ended. */
fun traceEventEnd() {
if (isEnabled) PerfettoNative.nativeTraceEventEnd()
}
private fun errorMessage(t: Throwable): String = t.run {
javaClass.name + if (message != null) ": $message" else ""
}
internal fun EnableTracingResponse(exitCode: Int, message: String? = null) =
EnableTracingResponse(exitCode, PerfettoNative.Metadata.version, message)
internal fun EnableTracingResponse(exitCode: Int, exception: Throwable) =
EnableTracingResponse(exitCode, errorMessage(exception))
}