• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.FtraceConfig
27 import perfetto.protos.PerfettoConfig.ProcessStatsConfig
28 import perfetto.protos.PerfettoConfig.SurfaceFlingerLayersConfig
29 import perfetto.protos.PerfettoConfig.SurfaceFlingerTransactionsConfig
30 import perfetto.protos.PerfettoConfig.TraceConfig
31 import perfetto.protos.PerfettoConfig.WindowManagerConfig
32 
33 /* Captures traces from Perfetto. */
34 open class PerfettoTraceMonitor(val config: TraceConfig) : TraceMonitor() {
35     override val traceType = TraceType.PERFETTO
36     override val isEnabled
37         get() = perfettoPid != null
38 
39     private var perfettoPid: Int? = null
40     private var traceFile: File? = null
41     private val PERFETTO_TRACES_DIR = File("/data/misc/perfetto-traces")
42 
captureDumpnull43     fun captureDump(): File {
44         doStart()
45         return doStop()
46     }
47 
doStartnull48     override fun doStart() {
49         val fileName = File.createTempFile(traceType.fileName, "").name
50         traceFile = PERFETTO_TRACES_DIR.resolve(fileName)
51 
52         val command =
53             "perfetto --background-wait" + " --config -" + " --out ${traceFile?.absolutePath}"
54         val stdout = String(executeShellCommand(command, config.toByteArray()))
55         val pid = stdout.trim().toInt()
56 
57         perfettoPid = pid
58         allPerfettoPidsLock.lock()
59         try {
60             allPerfettoPids.add(pid)
61         } finally {
62             allPerfettoPidsLock.unlock()
63         }
64     }
65 
doStopnull66     override fun doStop(): File {
67         require(isEnabled) { "Attempted to stop disabled trace monitor" }
68         killPerfettoProcess(requireNotNull(perfettoPid))
69         waitPerfettoProcessExits(requireNotNull(perfettoPid))
70         perfettoPid = null
71         return requireNotNull(traceFile)
72     }
73 
74     class Builder {
75         private val DEFAULT_SF_LAYER_FLAGS =
76             listOf(
77                 SurfaceFlingerLayersConfig.TraceFlag.TRACE_FLAG_INPUT,
78                 SurfaceFlingerLayersConfig.TraceFlag.TRACE_FLAG_COMPOSITION,
79                 SurfaceFlingerLayersConfig.TraceFlag.TRACE_FLAG_VIRTUAL_DISPLAYS,
80             )
81 
82         private val dataSourceConfigs = mutableSetOf<DataSourceConfig>()
83         private var incrementalTimeoutMs: Int? = null
84         private var uniqueSessionName: String? = null
85 
<lambda>null86         fun enableImeTrace(): Builder = apply { enableCustomTrace(createImeDataSourceConfig()) }
87 
enableLayersTracenull88         fun enableLayersTrace(flags: List<SurfaceFlingerLayersConfig.TraceFlag>? = null): Builder =
89             apply {
90                 enableCustomTrace(
91                     createLayersTraceDataSourceConfig(flags ?: DEFAULT_SF_LAYER_FLAGS)
92                 )
93             }
94 
enableLayersDumpnull95         fun enableLayersDump(flags: List<SurfaceFlingerLayersConfig.TraceFlag>? = null): Builder =
96             apply {
97                 enableCustomTrace(createLayersDumpDataSourceConfig(flags ?: DEFAULT_SF_LAYER_FLAGS))
98             }
99 
<lambda>null100         fun enableTransactionsTrace(): Builder = apply {
101             enableCustomTrace(createTransactionsDataSourceConfig())
102         }
103 
<lambda>null104         fun enableTransitionsTrace(): Builder = apply {
105             enableCustomTrace(createTransitionsDataSourceConfig())
106         }
107 
108         data class ProtoLogGroupOverride(
109             val groupName: String,
110             val logFrom: LogLevel,
111             val collectStackTrace: Boolean,
112         )
113 
<lambda>null114         fun enableProtoLog(dataSourceName: String): Builder = apply {
115             enableProtoLog(logAll = true, dataSourceName = dataSourceName)
116         }
117 
118         @JvmOverloads
enableProtoLognull119         fun enableProtoLog(
120             logAll: Boolean = true,
121             groupOverrides: List<ProtoLogGroupOverride> = emptyList(),
122             dataSourceName: String = PROTOLOG_DATA_SOURCE,
123         ): Builder = apply {
124             enableCustomTrace(
125                 createProtoLogDataSourceConfig(logAll, null, groupOverrides, dataSourceName)
126             )
127         }
128 
129         @JvmOverloads
enableProtoLognull130         fun enableProtoLog(
131             defaultLogFrom: LogLevel,
132             groupOverrides: List<ProtoLogGroupOverride> = emptyList(),
133             dataSourceName: String = PROTOLOG_DATA_SOURCE,
134         ): Builder = apply {
135             enableCustomTrace(
136                 createProtoLogDataSourceConfig(
137                     false,
138                     defaultLogFrom,
139                     groupOverrides,
140                     dataSourceName,
141                 )
142             )
143         }
144 
enableViewCaptureTracenull145         fun enableViewCaptureTrace(): Builder = apply {
146             val config = DataSourceConfig.newBuilder().setName(VIEWCAPTURE_DATA_SOURCE).build()
147             enableCustomTrace(config)
148         }
149 
150         @JvmOverloads
enableWindowManagerTracenull151         fun enableWindowManagerTrace(
152             logFrequency: WindowManagerConfig.LogFrequency =
153                 WindowManagerConfig.LogFrequency.LOG_FREQUENCY_FRAME,
154             dataSourceName: String = WINDOWMANAGER_DATA_SOURCE,
155         ): Builder = apply {
156             val config =
157                 DataSourceConfig.newBuilder()
158                     .setName(dataSourceName)
159                     .setWindowmanagerConfig(
160                         WindowManagerConfig.newBuilder()
161                             .setLogLevel(WindowManagerConfig.LogLevel.LOG_LEVEL_VERBOSE)
162                             .setLogFrequency(logFrequency)
163                             .build()
164                     )
165                     .build()
166 
167             enableCustomTrace(config)
168         }
169 
170         @JvmOverloads
enableWindowManagerDumpnull171         fun enableWindowManagerDump(dataSourceName: String = WINDOWMANAGER_DATA_SOURCE): Builder =
172             apply {
173                 val config =
174                     DataSourceConfig.newBuilder()
175                         .setName(dataSourceName)
176                         .setWindowmanagerConfig(
177                             WindowManagerConfig.newBuilder()
178                                 .setLogLevel(WindowManagerConfig.LogLevel.LOG_LEVEL_VERBOSE)
179                                 .setLogFrequency(
180                                     WindowManagerConfig.LogFrequency.LOG_FREQUENCY_SINGLE_DUMP
181                                 )
182                                 .build()
183                         )
184                         .build()
185 
186                 enableCustomTrace(config)
187             }
188 
<lambda>null189         fun enableCustomTrace(dataSourceConfig: DataSourceConfig): Builder = apply {
190             dataSourceConfigs.add(dataSourceConfig)
191         }
192 
<lambda>null193         fun setIncrementalTimeout(timeoutMs: Int) = apply { incrementalTimeoutMs = timeoutMs }
194 
<lambda>null195         fun setUniqueSessionName(name: String): Builder = apply { uniqueSessionName = name }
196 
buildnull197         fun build(): PerfettoTraceMonitor {
198             val configBuilder =
199                 TraceConfig.newBuilder()
200                     .setDurationMs(0)
201                     .addBuffers(
202                         TraceConfig.BufferConfig.newBuilder()
203                             .setSizeKb(TRACE_BUFFER_SIZE_KB)
204                             .build()
205                     )
206 
207             if (uniqueSessionName != null) {
208                 configBuilder.setUniqueSessionName(uniqueSessionName)
209             }
210 
211             dataSourceConfigs.add(createProcessStatsDataSourceConfig())
212             dataSourceConfigs.add(createFtraceDataSourceConfig())
213 
214             for (dataSourceConfig in dataSourceConfigs) {
215                 configBuilder.addDataSources(createDataSourceWithConfig(dataSourceConfig))
216             }
217 
218             val incrementalTimeoutMs = incrementalTimeoutMs
219             if (incrementalTimeoutMs != null) {
220                 configBuilder.setIncrementalStateConfig(
221                     TraceConfig.IncrementalStateConfig.newBuilder()
222                         .setClearPeriodMs(incrementalTimeoutMs)
223                 )
224             }
225 
226             return PerfettoTraceMonitor(config = configBuilder.build())
227         }
228 
createImeDataSourceConfignull229         private fun createImeDataSourceConfig(): DataSourceConfig {
230             return DataSourceConfig.newBuilder().setName(IME_DATA_SOURCE).build()
231         }
232 
createLayersTraceDataSourceConfignull233         private fun createLayersTraceDataSourceConfig(
234             traceFlags: List<SurfaceFlingerLayersConfig.TraceFlag>
235         ): DataSourceConfig {
236             return DataSourceConfig.newBuilder()
237                 .setName(SF_LAYERS_DATA_SOURCE)
238                 .setSurfaceflingerLayersConfig(
239                     SurfaceFlingerLayersConfig.newBuilder()
240                         .setMode(SurfaceFlingerLayersConfig.Mode.MODE_ACTIVE)
241                         .apply { traceFlags.forEach { addTraceFlags(it) } }
242                         .build()
243                 )
244                 .build()
245         }
246 
createLayersDumpDataSourceConfignull247         private fun createLayersDumpDataSourceConfig(
248             traceFlags: List<SurfaceFlingerLayersConfig.TraceFlag>
249         ): DataSourceConfig {
250             return DataSourceConfig.newBuilder()
251                 .setName(SF_LAYERS_DATA_SOURCE)
252                 .setSurfaceflingerLayersConfig(
253                     SurfaceFlingerLayersConfig.newBuilder()
254                         .setMode(SurfaceFlingerLayersConfig.Mode.MODE_DUMP)
255                         .apply { traceFlags.forEach { addTraceFlags(it) } }
256                         .build()
257                 )
258                 .build()
259         }
260 
createTransactionsDataSourceConfignull261         private fun createTransactionsDataSourceConfig(): DataSourceConfig {
262             return DataSourceConfig.newBuilder()
263                 .setName(SF_TRANSACTIONS_DATA_SOURCE)
264                 .setSurfaceflingerTransactionsConfig(
265                     SurfaceFlingerTransactionsConfig.newBuilder()
266                         .setMode(SurfaceFlingerTransactionsConfig.Mode.MODE_ACTIVE)
267                         .build()
268                 )
269                 .build()
270         }
271 
createTransitionsDataSourceConfignull272         private fun createTransitionsDataSourceConfig(): DataSourceConfig {
273             return DataSourceConfig.newBuilder().setName(TRANSITIONS_DATA_SOURCE).build()
274         }
275 
createProtoLogDataSourceConfignull276         private fun createProtoLogDataSourceConfig(
277             logAll: Boolean,
278             logFrom: LogLevel?,
279             groupOverrides: List<ProtoLogGroupOverride>,
280             dataSourceName: String = PROTOLOG_DATA_SOURCE,
281         ): DataSourceConfig {
282             val protoLogConfigBuilder = PerfettoConfig.ProtoLogConfig.newBuilder()
283 
284             if (logAll) {
285                 protoLogConfigBuilder.setTracingMode(
286                     PerfettoConfig.ProtoLogConfig.TracingMode.ENABLE_ALL
287                 )
288             }
289 
290             if (logFrom != null) {
291                 protoLogConfigBuilder.setDefaultLogFromLevel(
292                     PerfettoConfig.ProtoLogLevel.forNumber(logFrom.id)
293                 )
294             }
295 
296             for (groupOverride in groupOverrides) {
297                 protoLogConfigBuilder.addGroupOverrides(
298                     PerfettoConfig.ProtoLogGroup.newBuilder()
299                         .setGroupName(groupOverride.groupName)
300                         .setLogFrom(
301                             PerfettoConfig.ProtoLogLevel.forNumber(groupOverride.logFrom.id)
302                         )
303                         .setCollectStacktrace(groupOverride.collectStackTrace)
304                 )
305             }
306 
307             return DataSourceConfig.newBuilder()
308                 .setName(dataSourceName)
309                 .setProtologConfig(protoLogConfigBuilder)
310                 .build()
311         }
312 
createProcessStatsDataSourceConfignull313         private fun createProcessStatsDataSourceConfig(): DataSourceConfig {
314             return DataSourceConfig.newBuilder()
315                 .setName(PROCESS_STATS_DATA_SOURCE)
316                 .setTargetBuffer(0)
317                 .setProcessStatsConfig(
318                     ProcessStatsConfig.newBuilder().setScanAllProcessesOnStart(true).build()
319                 )
320                 .build()
321         }
322 
createFtraceDataSourceConfignull323         private fun createFtraceDataSourceConfig(): DataSourceConfig {
324             val ftraceEvents = listOf("ftrace/print", "task/task_newtask", "task/task_rename")
325             val atraceCategories = listOf("ss", "wm")
326             return DataSourceConfig.newBuilder()
327                 .setName(FTRACE_DATA_SOURCE)
328                 .setTargetBuffer(0)
329                 .setFtraceConfig(
330                     FtraceConfig.newBuilder()
331                         .apply { ftraceEvents.forEach { addFtraceEvents(it) } }
332                         .apply { atraceCategories.forEach { addAtraceCategories(it) } }
333                         .build()
334                 )
335                 .build()
336         }
337 
createDataSourceWithConfignull338         private fun createDataSourceWithConfig(
339             dataSourceConfig: DataSourceConfig
340         ): TraceConfig.DataSource {
341             return TraceConfig.DataSource.newBuilder().setConfig(dataSourceConfig).build()
342         }
343     }
344 
345     companion object {
346         private const val TRACE_BUFFER_SIZE_KB = 1024 * 1024
347 
348         private const val IME_DATA_SOURCE = "android.inputmethod"
349         private const val SF_LAYERS_DATA_SOURCE = "android.surfaceflinger.layers"
350         private const val SF_TRANSACTIONS_DATA_SOURCE = "android.surfaceflinger.transactions"
351         private const val TRANSITIONS_DATA_SOURCE = "com.android.wm.shell.transition"
352         private const val PROTOLOG_DATA_SOURCE = "android.protolog"
353         private const val VIEWCAPTURE_DATA_SOURCE = "android.viewcapture"
354         private const val WINDOWMANAGER_DATA_SOURCE = "android.windowmanager"
355         private const val PROCESS_STATS_DATA_SOURCE = "linux.process_stats"
356         private const val FTRACE_DATA_SOURCE = "linux.ftrace"
357 
358         private val allPerfettoPids = mutableListOf<Int>()
359         private val allPerfettoPidsLock = ReentrantLock()
360 
361         @JvmStatic
newBuildernull362         fun newBuilder(): Builder {
363             return Builder()
364         }
365 
366         @JvmStatic
stopAllSessionsnull367         fun stopAllSessions() {
368             allPerfettoPidsLock.lock()
369             try {
370                 allPerfettoPids.forEach { killPerfettoProcess(it) }
371                 allPerfettoPids.forEach { waitPerfettoProcessExits(it) }
372                 allPerfettoPids.clear()
373             } finally {
374                 allPerfettoPidsLock.unlock()
375             }
376         }
377 
378         @JvmStatic
killPerfettoProcessnull379         fun killPerfettoProcess(pid: Int) {
380             if (isPerfettoProcessUp(pid)) {
381                 executeShellCommand("kill $pid")
382             }
383         }
384 
waitPerfettoProcessExitsnull385         private fun waitPerfettoProcessExits(pid: Int) {
386             while (true) {
387                 if (!isPerfettoProcessUp(pid)) {
388                     break
389                 }
390                 Thread.sleep(50)
391             }
392         }
393 
isPerfettoProcessUpnull394         private fun isPerfettoProcessUp(pid: Int): Boolean {
395             val out = String(executeShellCommand("ps -p $pid -o NAME"))
396             return out.contains("perfetto")
397         }
398     }
399 }
400