1 /*
2  * Copyright (C) 2023 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 
17 package android.tools.traces.monitors
18 
19 import android.tools.io.TraceType
20 import android.tools.traces.executeShellCommand
21 import com.android.internal.protolog.common.LogLevel
22 import java.io.File
23 import java.util.concurrent.locks.ReentrantLock
24 import perfetto.protos.PerfettoConfig
25 import perfetto.protos.PerfettoConfig.DataSourceConfig
26 import perfetto.protos.PerfettoConfig.SurfaceFlingerLayersConfig
27 import perfetto.protos.PerfettoConfig.SurfaceFlingerTransactionsConfig
28 import perfetto.protos.PerfettoConfig.TraceConfig
29 import perfetto.protos.PerfettoConfig.WindowManagerConfig
30 
31 /* Captures traces from Perfetto. */
32 open class PerfettoTraceMonitor(val config: TraceConfig) : TraceMonitor() {
33     override val traceType = TraceType.PERFETTO
34     override val isEnabled
35         get() = perfettoPid != null
36 
37     private var perfettoPid: Int? = null
38     private var traceFile: File? = null
39     private val PERFETTO_TRACES_DIR = File("/data/misc/perfetto-traces")
40 
captureDumpnull41     fun captureDump(): File {
42         doStart()
43         return doStop()
44     }
45 
doStartnull46     override fun doStart() {
47         val fileName = File.createTempFile(traceType.fileName, "").name
48         traceFile = PERFETTO_TRACES_DIR.resolve(fileName)
49 
50         val command =
51             "perfetto --background-wait" + " --config -" + " --out ${traceFile?.absolutePath}"
52         val stdout = String(executeShellCommand(command, config.toByteArray()))
53         val pid = stdout.trim().toInt()
54 
55         perfettoPid = pid
56         allPerfettoPidsLock.lock()
57         try {
58             allPerfettoPids.add(pid)
59         } finally {
60             allPerfettoPidsLock.unlock()
61         }
62     }
63 
doStopnull64     override fun doStop(): File {
65         require(isEnabled) { "Attempted to stop disabled trace monitor" }
66         killPerfettoProcess(requireNotNull(perfettoPid))
67         waitPerfettoProcessExits(requireNotNull(perfettoPid))
68         perfettoPid = null
69         return requireNotNull(traceFile)
70     }
71 
72     class Builder {
73         private val DEFAULT_SF_LAYER_FLAGS =
74             listOf(
75                 SurfaceFlingerLayersConfig.TraceFlag.TRACE_FLAG_INPUT,
76                 SurfaceFlingerLayersConfig.TraceFlag.TRACE_FLAG_COMPOSITION,
77                 SurfaceFlingerLayersConfig.TraceFlag.TRACE_FLAG_VIRTUAL_DISPLAYS,
78             )
79 
80         private val dataSourceConfigs = mutableSetOf<DataSourceConfig>()
81         private var incrementalTimeoutMs: Int? = null
82 
<lambda>null83         fun enableImeTrace(): Builder = apply { enableCustomTrace(createImeDataSourceConfig()) }
84 
enableLayersTracenull85         fun enableLayersTrace(flags: List<SurfaceFlingerLayersConfig.TraceFlag>? = null): Builder =
86             apply {
87                 enableCustomTrace(
88                     createLayersTraceDataSourceConfig(flags ?: DEFAULT_SF_LAYER_FLAGS)
89                 )
90             }
91 
enableLayersDumpnull92         fun enableLayersDump(flags: List<SurfaceFlingerLayersConfig.TraceFlag>? = null): Builder =
93             apply {
94                 enableCustomTrace(createLayersDumpDataSourceConfig(flags ?: DEFAULT_SF_LAYER_FLAGS))
95             }
96 
<lambda>null97         fun enableTransactionsTrace(): Builder = apply {
98             enableCustomTrace(createTransactionsDataSourceConfig())
99         }
100 
<lambda>null101         fun enableTransitionsTrace(): Builder = apply {
102             enableCustomTrace(createTransitionsDataSourceConfig())
103         }
104 
105         data class ProtoLogGroupOverride(
106             val groupName: String,
107             val logFrom: LogLevel,
108             val collectStackTrace: Boolean,
109         )
110 
<lambda>null111         fun enableProtoLog(dataSourceName: String): Builder = apply {
112             enableProtoLog(logAll = true, dataSourceName = dataSourceName)
113         }
114 
115         @JvmOverloads
enableProtoLognull116         fun enableProtoLog(
117             logAll: Boolean = true,
118             groupOverrides: List<ProtoLogGroupOverride> = emptyList(),
119             dataSourceName: String = PROTOLOG_DATA_SOURCE,
120         ): Builder = apply {
121             enableCustomTrace(
122                 createProtoLogDataSourceConfig(logAll, null, groupOverrides, dataSourceName)
123             )
124         }
125 
126         @JvmOverloads
enableProtoLognull127         fun enableProtoLog(
128             defaultLogFrom: LogLevel,
129             groupOverrides: List<ProtoLogGroupOverride> = emptyList(),
130             dataSourceName: String = PROTOLOG_DATA_SOURCE,
131         ): Builder = apply {
132             enableCustomTrace(
133                 createProtoLogDataSourceConfig(
134                     false,
135                     defaultLogFrom,
136                     groupOverrides,
137                     dataSourceName,
138                 )
139             )
140         }
141 
enableViewCaptureTracenull142         fun enableViewCaptureTrace(): Builder = apply {
143             val config = DataSourceConfig.newBuilder().setName(VIEWCAPTURE_DATA_SOURCE).build()
144             enableCustomTrace(config)
145         }
146 
147         @JvmOverloads
enableWindowManagerTracenull148         fun enableWindowManagerTrace(
149             logFrequency: WindowManagerConfig.LogFrequency =
150                 WindowManagerConfig.LogFrequency.LOG_FREQUENCY_FRAME,
151             dataSourceName: String = WINDOWMANAGER_DATA_SOURCE,
152         ): Builder = apply {
153             val config =
154                 DataSourceConfig.newBuilder()
155                     .setName(dataSourceName)
156                     .setWindowmanagerConfig(
157                         WindowManagerConfig.newBuilder()
158                             .setLogLevel(WindowManagerConfig.LogLevel.LOG_LEVEL_VERBOSE)
159                             .setLogFrequency(logFrequency)
160                             .build()
161                     )
162                     .build()
163 
164             enableCustomTrace(config)
165         }
166 
167         @JvmOverloads
enableWindowManagerDumpnull168         fun enableWindowManagerDump(dataSourceName: String = WINDOWMANAGER_DATA_SOURCE): Builder =
169             apply {
170                 val config =
171                     DataSourceConfig.newBuilder()
172                         .setName(dataSourceName)
173                         .setWindowmanagerConfig(
174                             WindowManagerConfig.newBuilder()
175                                 .setLogLevel(WindowManagerConfig.LogLevel.LOG_LEVEL_VERBOSE)
176                                 .setLogFrequency(
177                                     WindowManagerConfig.LogFrequency.LOG_FREQUENCY_SINGLE_DUMP
178                                 )
179                                 .build()
180                         )
181                         .build()
182 
183                 enableCustomTrace(config)
184             }
185 
<lambda>null186         fun enableCustomTrace(dataSourceConfig: DataSourceConfig): Builder = apply {
187             dataSourceConfigs.add(dataSourceConfig)
188         }
189 
<lambda>null190         fun setIncrementalTimeout(timeoutMs: Int) = apply { incrementalTimeoutMs = timeoutMs }
191 
buildnull192         fun build(): PerfettoTraceMonitor {
193             val configBuilder =
194                 TraceConfig.newBuilder()
195                     .setDurationMs(0)
196                     .addBuffers(
197                         TraceConfig.BufferConfig.newBuilder()
198                             .setSizeKb(TRACE_BUFFER_SIZE_KB)
199                             .build()
200                     )
201 
202             for (dataSourceConfig in dataSourceConfigs) {
203                 configBuilder.addDataSources(createDataSourceWithConfig(dataSourceConfig))
204             }
205 
206             val incrementalTimeoutMs = incrementalTimeoutMs
207             if (incrementalTimeoutMs != null) {
208                 configBuilder.setIncrementalStateConfig(
209                     TraceConfig.IncrementalStateConfig.newBuilder()
210                         .setClearPeriodMs(incrementalTimeoutMs)
211                 )
212             }
213 
214             return PerfettoTraceMonitor(config = configBuilder.build())
215         }
216 
createImeDataSourceConfignull217         private fun createImeDataSourceConfig(): DataSourceConfig {
218             return DataSourceConfig.newBuilder().setName(IME_DATA_SOURCE).build()
219         }
220 
createLayersTraceDataSourceConfignull221         private fun createLayersTraceDataSourceConfig(
222             traceFlags: List<SurfaceFlingerLayersConfig.TraceFlag>
223         ): DataSourceConfig {
224             return DataSourceConfig.newBuilder()
225                 .setName(SF_LAYERS_DATA_SOURCE)
226                 .setSurfaceflingerLayersConfig(
227                     SurfaceFlingerLayersConfig.newBuilder()
228                         .setMode(SurfaceFlingerLayersConfig.Mode.MODE_ACTIVE)
229                         .apply { traceFlags.forEach { addTraceFlags(it) } }
230                         .build()
231                 )
232                 .build()
233         }
234 
createLayersDumpDataSourceConfignull235         private fun createLayersDumpDataSourceConfig(
236             traceFlags: List<SurfaceFlingerLayersConfig.TraceFlag>
237         ): DataSourceConfig {
238             return DataSourceConfig.newBuilder()
239                 .setName(SF_LAYERS_DATA_SOURCE)
240                 .setSurfaceflingerLayersConfig(
241                     SurfaceFlingerLayersConfig.newBuilder()
242                         .setMode(SurfaceFlingerLayersConfig.Mode.MODE_DUMP)
243                         .apply { traceFlags.forEach { addTraceFlags(it) } }
244                         .build()
245                 )
246                 .build()
247         }
248 
createTransactionsDataSourceConfignull249         private fun createTransactionsDataSourceConfig(): DataSourceConfig {
250             return DataSourceConfig.newBuilder()
251                 .setName(SF_TRANSACTIONS_DATA_SOURCE)
252                 .setSurfaceflingerTransactionsConfig(
253                     SurfaceFlingerTransactionsConfig.newBuilder()
254                         .setMode(SurfaceFlingerTransactionsConfig.Mode.MODE_ACTIVE)
255                         .build()
256                 )
257                 .build()
258         }
259 
createTransitionsDataSourceConfignull260         private fun createTransitionsDataSourceConfig(): DataSourceConfig {
261             return DataSourceConfig.newBuilder().setName(TRANSITIONS_DATA_SOURCE).build()
262         }
263 
createProtoLogDataSourceConfignull264         private fun createProtoLogDataSourceConfig(
265             logAll: Boolean,
266             logFrom: LogLevel?,
267             groupOverrides: List<ProtoLogGroupOverride>,
268             dataSourceName: String = PROTOLOG_DATA_SOURCE,
269         ): DataSourceConfig {
270             val protoLogConfigBuilder = PerfettoConfig.ProtoLogConfig.newBuilder()
271 
272             if (logAll) {
273                 protoLogConfigBuilder.setTracingMode(
274                     PerfettoConfig.ProtoLogConfig.TracingMode.ENABLE_ALL
275                 )
276             }
277 
278             if (logFrom != null) {
279                 protoLogConfigBuilder.setDefaultLogFromLevel(
280                     PerfettoConfig.ProtoLogLevel.forNumber(logFrom.id)
281                 )
282             }
283 
284             for (groupOverride in groupOverrides) {
285                 protoLogConfigBuilder.addGroupOverrides(
286                     PerfettoConfig.ProtoLogGroup.newBuilder()
287                         .setGroupName(groupOverride.groupName)
288                         .setLogFrom(
289                             PerfettoConfig.ProtoLogLevel.forNumber(groupOverride.logFrom.id)
290                         )
291                         .setCollectStacktrace(groupOverride.collectStackTrace)
292                 )
293             }
294 
295             return DataSourceConfig.newBuilder()
296                 .setName(dataSourceName)
297                 .setProtologConfig(protoLogConfigBuilder)
298                 .build()
299         }
300 
createDataSourceWithConfignull301         private fun createDataSourceWithConfig(
302             dataSourceConfig: DataSourceConfig
303         ): TraceConfig.DataSource {
304             return TraceConfig.DataSource.newBuilder().setConfig(dataSourceConfig).build()
305         }
306     }
307 
308     companion object {
309         private const val TRACE_BUFFER_SIZE_KB = 1024 * 1024
310 
311         private const val IME_DATA_SOURCE = "android.inputmethod"
312         private const val SF_LAYERS_DATA_SOURCE = "android.surfaceflinger.layers"
313         private const val SF_TRANSACTIONS_DATA_SOURCE = "android.surfaceflinger.transactions"
314         private const val TRANSITIONS_DATA_SOURCE = "com.android.wm.shell.transition"
315         private const val PROTOLOG_DATA_SOURCE = "android.protolog"
316         private const val VIEWCAPTURE_DATA_SOURCE = "android.viewcapture"
317         private const val WINDOWMANAGER_DATA_SOURCE = "android.windowmanager"
318 
319         private val allPerfettoPids = mutableListOf<Int>()
320         private val allPerfettoPidsLock = ReentrantLock()
321 
322         @JvmStatic
newBuildernull323         fun newBuilder(): Builder {
324             return Builder()
325         }
326 
327         @JvmStatic
stopAllSessionsnull328         fun stopAllSessions() {
329             allPerfettoPidsLock.lock()
330             try {
331                 allPerfettoPids.forEach { killPerfettoProcess(it) }
332                 allPerfettoPids.forEach { waitPerfettoProcessExits(it) }
333                 allPerfettoPids.clear()
334             } finally {
335                 allPerfettoPidsLock.unlock()
336             }
337         }
338 
339         @JvmStatic
killPerfettoProcessnull340         fun killPerfettoProcess(pid: Int) {
341             if (isPerfettoProcessUp(pid)) {
342                 executeShellCommand("kill $pid")
343             }
344         }
345 
waitPerfettoProcessExitsnull346         private fun waitPerfettoProcessExits(pid: Int) {
347             while (true) {
348                 if (!isPerfettoProcessUp(pid)) {
349                     break
350                 }
351                 Thread.sleep(50)
352             }
353         }
354 
isPerfettoProcessUpnull355         private fun isPerfettoProcessUp(pid: Int): Boolean {
356             val out = String(executeShellCommand("ps -p $pid -o CMD"))
357             return out.contains("perfetto")
358         }
359     }
360 }
361