1 /* <lambda>null2 * Copyright 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package androidx.tracing.perfetto 17 18 import android.content.Context 19 import android.os.Build 20 import androidx.annotation.RequiresApi 21 import androidx.tracing.perfetto.internal.handshake.protocol.Response 22 import androidx.tracing.perfetto.internal.handshake.protocol.ResponseResultCodes.RESULT_CODE_ALREADY_ENABLED 23 import androidx.tracing.perfetto.internal.handshake.protocol.ResponseResultCodes.RESULT_CODE_ERROR_BINARY_MISSING 24 import androidx.tracing.perfetto.internal.handshake.protocol.ResponseResultCodes.RESULT_CODE_ERROR_BINARY_VERIFICATION_ERROR 25 import androidx.tracing.perfetto.internal.handshake.protocol.ResponseResultCodes.RESULT_CODE_ERROR_BINARY_VERSION_MISMATCH 26 import androidx.tracing.perfetto.internal.handshake.protocol.ResponseResultCodes.RESULT_CODE_ERROR_OTHER 27 import androidx.tracing.perfetto.internal.handshake.protocol.ResponseResultCodes.RESULT_CODE_SUCCESS 28 import androidx.tracing.perfetto.jni.PerfettoNative 29 import androidx.tracing.perfetto.security.IncorrectChecksumException 30 import androidx.tracing.perfetto.security.SafeLibLoader 31 import java.io.File 32 import java.util.concurrent.locks.ReentrantReadWriteLock 33 import kotlin.concurrent.withLock 34 35 /** Allows for emitting trace events using Perfetto SDK. */ 36 object PerfettoSdkTrace { 37 /** 38 * Checks whether the tracing library has been loaded and the app has been registered with 39 * Perfetto SDK tracing server. This is useful to avoid intermediate string creation for trace 40 * sections that require formatting. It is not necessary to guard all Trace method calls as they 41 * internally already check this. However it is recommended to use this to prevent creating any 42 * temporary objects that would then be passed to those methods to reduce runtime cost when 43 * tracing isn't enabled. 44 * 45 * @return true if tracing is currently enabled, false otherwise 46 */ 47 // Note: some of class' code relies on the field never changing from true -> false, 48 // which is realistic (at the time of writing this, we are unable to unload the library and 49 // unregister the app with Perfetto). 50 var isEnabled: Boolean = false 51 private set 52 53 /** 54 * Ensures that we enable tracing (load the tracing library and register with Perfetto) only 55 * once. 56 * 57 * Note: not intended for synchronization during tracing as not to impact performance. 58 */ 59 private val enableTracingLock = ReentrantReadWriteLock() 60 61 @RequiresApi(Build.VERSION_CODES.R) // TODO(234351579): Support API < 30 62 internal fun enable() = enable(null) 63 64 @RequiresApi(Build.VERSION_CODES.R) // TODO(234351579): Support API < 30 65 internal fun enable(file: File, context: Context) = enable(file to context) 66 67 @RequiresApi(Build.VERSION_CODES.R) // TODO(234351579): Support API < 30 68 private fun enable(descriptor: Pair<File, Context>?): Response { 69 enableTracingLock.readLock().withLock { 70 if (isEnabled) return Response(RESULT_CODE_ALREADY_ENABLED) 71 } 72 73 enableTracingLock.writeLock().withLock { 74 return enableImpl(descriptor) 75 } 76 } 77 78 /** Calling thread must obtain a write lock on [enableTracingLock] before calling this method */ 79 @RequiresApi(Build.VERSION_CODES.R) // TODO(234351579): Support API < 30 80 private fun enableImpl(descriptor: Pair<File, Context>?): Response { 81 if (!enableTracingLock.isWriteLockedByCurrentThread) throw RuntimeException() 82 83 if (isEnabled) return Response(RESULT_CODE_ALREADY_ENABLED) 84 85 // Load library 86 try { 87 when (descriptor) { 88 null -> PerfettoNative.loadLib() 89 else -> 90 descriptor.let { (file, context) -> 91 PerfettoNative.loadLib(file, SafeLibLoader(context)) 92 } 93 } 94 } catch (t: Throwable) { 95 return when (t) { 96 is IncorrectChecksumException -> 97 Response(RESULT_CODE_ERROR_BINARY_VERIFICATION_ERROR, t) 98 is UnsatisfiedLinkError -> Response(RESULT_CODE_ERROR_BINARY_MISSING, t) 99 is Exception -> Response(RESULT_CODE_ERROR_OTHER, t) 100 else -> throw t 101 } 102 } 103 104 // Verify binary/java version match 105 val nativeVersion = PerfettoNative.nativeVersion() 106 val javaVersion = PerfettoNative.Metadata.version 107 if (nativeVersion != javaVersion) { 108 return Response( 109 RESULT_CODE_ERROR_BINARY_VERSION_MISMATCH, 110 "Binary and Java version mismatch. Binary: $nativeVersion. Java: $javaVersion" 111 ) 112 } 113 114 // Register as a Perfetto SDK data-source 115 try { 116 PerfettoNative.nativeRegisterWithPerfetto() 117 } catch (e: Exception) { 118 return Response(RESULT_CODE_ERROR_OTHER, e) 119 } 120 121 isEnabled = true 122 return Response(RESULT_CODE_SUCCESS) 123 } 124 125 /** 126 * Writes a trace message to indicate that a given section of code has begun. This call must be 127 * followed by a corresponding call to [endSection] on the same thread. 128 * 129 * @param sectionName The name of the code section to appear in the trace. 130 */ 131 fun beginSection(sectionName: String) { 132 if (isEnabled) { 133 // Note: key is not currently used, so passing 0 for now 134 PerfettoNative.nativeTraceEventBegin(key = 0, traceInfo = sectionName) 135 } 136 } 137 138 /** 139 * Writes a trace message to indicate that a given section of code has ended. This call must be 140 * preceded by a corresponding call to [beginSection]. Calling this method will mark the end of 141 * the most recently begun section of code, so care must be taken to ensure that [beginSection] 142 * / [endSection] pairs are properly nested and called from the same thread. 143 */ 144 fun endSection() { 145 if (isEnabled) PerfettoNative.nativeTraceEventEnd() 146 } 147 148 private fun errorMessage(t: Throwable): String = 149 t.run { javaClass.name + if (message != null) ": $message" else "" } 150 151 internal fun Response(resultCode: Int, message: String? = null) = 152 Response(resultCode, PerfettoNative.Metadata.version, message) 153 154 internal fun Response(resultCode: Int, exception: Throwable) = 155 Response(resultCode, errorMessage(exception)) 156 } 157