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