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 android.tools.traces.io.IoUtils
22 import com.android.internal.protolog.common.LogLevel
23 import java.io.File
24 import java.util.concurrent.locks.ReentrantLock
25 import perfetto.protos.PerfettoConfig
26 import perfetto.protos.PerfettoConfig.DataSourceConfig
27 import perfetto.protos.PerfettoConfig.SurfaceFlingerLayersConfig
28 import perfetto.protos.PerfettoConfig.SurfaceFlingerTransactionsConfig
29 import perfetto.protos.PerfettoConfig.TraceConfig
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 configFileInPerfettoDir: File? = null
39     private var traceFile: File? = null
40     private var traceFileInPerfettoDir: File? = null
41     private val PERFETTO_CONFIGS_DIR = File("/data/misc/perfetto-configs")
42     private val PERFETTO_TRACES_DIR = File("/data/misc/perfetto-traces")
43 
doStartnull44     override fun doStart() {
45         val configFile = File.createTempFile("flickerlib-config-", ".cfg")
46         configFileInPerfettoDir = PERFETTO_CONFIGS_DIR.resolve(requireNotNull(configFile).name)
47 
48         traceFile = File.createTempFile(traceType.fileName, "")
49         traceFileInPerfettoDir = PERFETTO_TRACES_DIR.resolve(requireNotNull(traceFile).name)
50 
51         configFile.writeBytes(config.toByteArray())
52 
53         // Experiment for sporadic failures like b/333220956.
54         // The perfetto command below sometimes fails to find the config file on disk,
55         // so let's try to wait till the file exists on disk.
56         IoUtils.waitFileExists(configFile, 2000)
57 
58         IoUtils.moveFile(configFile, requireNotNull(configFileInPerfettoDir))
59         IoUtils.waitFileExists(requireNotNull(configFileInPerfettoDir), 2000)
60 
61         val command =
62             "perfetto --background-wait" +
63                 " --config ${configFileInPerfettoDir?.absolutePath}" +
64                 " --out ${traceFileInPerfettoDir?.absolutePath}"
65         val stdout = String(executeShellCommand(command))
66         val pid = stdout.trim().toInt()
67         perfettoPid = pid
68         allPerfettoPidsLock.lock()
69         try {
70             allPerfettoPids.add(pid)
71         } finally {
72             allPerfettoPidsLock.unlock()
73         }
74     }
75 
doStopnull76     override fun doStop(): File {
77         require(isEnabled) { "Attempted to stop disabled trace monitor" }
78         killPerfettoProcess(requireNotNull(perfettoPid))
79         waitPerfettoProcessExits(requireNotNull(perfettoPid))
80         IoUtils.moveFile(requireNotNull(traceFileInPerfettoDir), requireNotNull(traceFile))
81         executeShellCommand("rm ${configFileInPerfettoDir?.absolutePath}")
82         perfettoPid = null
83         return requireNotNull(traceFile)
84     }
85 
86     class Builder {
87         private val DEFAULT_SF_LAYER_FLAGS =
88             listOf(
89                 SurfaceFlingerLayersConfig.TraceFlag.TRACE_FLAG_INPUT,
90                 SurfaceFlingerLayersConfig.TraceFlag.TRACE_FLAG_COMPOSITION,
91                 SurfaceFlingerLayersConfig.TraceFlag.TRACE_FLAG_VIRTUAL_DISPLAYS
92             )
93 
94         private val dataSourceConfigs = mutableSetOf<DataSourceConfig>()
95         private var incrementalTimeoutMs: Int? = null
96 
<lambda>null97         fun enableImeTrace(): Builder = apply { enableCustomTrace(createImeDataSourceConfig()) }
98 
enableLayersTracenull99         fun enableLayersTrace(flags: List<SurfaceFlingerLayersConfig.TraceFlag>? = null): Builder =
100             apply {
101                 enableCustomTrace(
102                     createLayersTraceDataSourceConfig(flags ?: DEFAULT_SF_LAYER_FLAGS)
103                 )
104             }
105 
enableLayersDumpnull106         fun enableLayersDump(flags: List<SurfaceFlingerLayersConfig.TraceFlag>? = null): Builder =
107             apply {
108                 enableCustomTrace(createLayersDumpDataSourceConfig(flags ?: DEFAULT_SF_LAYER_FLAGS))
109             }
110 
<lambda>null111         fun enableTransactionsTrace(): Builder = apply {
112             enableCustomTrace(createTransactionsDataSourceConfig())
113         }
114 
<lambda>null115         fun enableTransitionsTrace(): Builder = apply {
116             enableCustomTrace(createTransitionsDataSourceConfig())
117         }
118 
119         data class ProtoLogGroupOverride(
120             val groupName: String,
121             val logFrom: LogLevel,
122             val collectStackTrace: Boolean,
123         )
124 
125         @JvmOverloads
enableProtoLognull126         fun enableProtoLog(
127             logAll: Boolean = true,
128             groupOverrides: List<ProtoLogGroupOverride> = emptyList()
129         ): Builder = apply {
130             enableCustomTrace(createProtoLogDataSourceConfig(logAll, groupOverrides))
131         }
132 
enableViewCaptureTracenull133         fun enableViewCaptureTrace(): Builder = apply {
134             val config = DataSourceConfig.newBuilder().setName(VIEWCAPTURE_DATA_SOURCE).build()
135             enableCustomTrace(config)
136         }
137 
<lambda>null138         fun enableCustomTrace(dataSourceConfig: DataSourceConfig): Builder = apply {
139             dataSourceConfigs.add(dataSourceConfig)
140         }
141 
<lambda>null142         fun setIncrementalTimeout(timeoutMs: Int) = apply { incrementalTimeoutMs = timeoutMs }
143 
buildnull144         fun build(): PerfettoTraceMonitor {
145             val configBuilder =
146                 TraceConfig.newBuilder()
147                     .setDurationMs(0)
148                     .addBuffers(
149                         TraceConfig.BufferConfig.newBuilder()
150                             .setSizeKb(TRACE_BUFFER_SIZE_KB)
151                             .build()
152                     )
153 
154             for (dataSourceConfig in dataSourceConfigs) {
155                 configBuilder.addDataSources(createDataSourceWithConfig(dataSourceConfig))
156             }
157 
158             val incrementalTimeoutMs = incrementalTimeoutMs
159             if (incrementalTimeoutMs != null) {
160                 configBuilder.setIncrementalStateConfig(
161                     TraceConfig.IncrementalStateConfig.newBuilder()
162                         .setClearPeriodMs(incrementalTimeoutMs)
163                 )
164             }
165 
166             return PerfettoTraceMonitor(config = configBuilder.build())
167         }
168 
createImeDataSourceConfignull169         private fun createImeDataSourceConfig(): DataSourceConfig {
170             return DataSourceConfig.newBuilder().setName(IME_DATA_SOURCE).build()
171         }
172 
createLayersTraceDataSourceConfignull173         private fun createLayersTraceDataSourceConfig(
174             traceFlags: List<SurfaceFlingerLayersConfig.TraceFlag>
175         ): DataSourceConfig {
176             return DataSourceConfig.newBuilder()
177                 .setName(SF_LAYERS_DATA_SOURCE)
178                 .setSurfaceflingerLayersConfig(
179                     SurfaceFlingerLayersConfig.newBuilder()
180                         .setMode(SurfaceFlingerLayersConfig.Mode.MODE_ACTIVE)
181                         .apply { traceFlags.forEach { addTraceFlags(it) } }
182                         .build()
183                 )
184                 .build()
185         }
186 
createLayersDumpDataSourceConfignull187         private fun createLayersDumpDataSourceConfig(
188             traceFlags: List<SurfaceFlingerLayersConfig.TraceFlag>
189         ): DataSourceConfig {
190             return DataSourceConfig.newBuilder()
191                 .setName(SF_LAYERS_DATA_SOURCE)
192                 .setSurfaceflingerLayersConfig(
193                     SurfaceFlingerLayersConfig.newBuilder()
194                         .setMode(SurfaceFlingerLayersConfig.Mode.MODE_DUMP)
195                         .apply { traceFlags.forEach { addTraceFlags(it) } }
196                         .build()
197                 )
198                 .build()
199         }
200 
createTransactionsDataSourceConfignull201         private fun createTransactionsDataSourceConfig(): DataSourceConfig {
202             return DataSourceConfig.newBuilder()
203                 .setName(SF_TRANSACTIONS_DATA_SOURCE)
204                 .setSurfaceflingerTransactionsConfig(
205                     SurfaceFlingerTransactionsConfig.newBuilder()
206                         .setMode(SurfaceFlingerTransactionsConfig.Mode.MODE_ACTIVE)
207                         .build()
208                 )
209                 .build()
210         }
211 
createTransitionsDataSourceConfignull212         private fun createTransitionsDataSourceConfig(): DataSourceConfig {
213             return DataSourceConfig.newBuilder().setName(TRANSITIONS_DATA_SOURCE).build()
214         }
215 
createProtoLogDataSourceConfignull216         private fun createProtoLogDataSourceConfig(
217             logAll: Boolean,
218             groupOverrides: List<ProtoLogGroupOverride>
219         ): DataSourceConfig {
220             val protoLogConfigBuilder = PerfettoConfig.ProtoLogConfig.newBuilder()
221 
222             if (logAll) {
223                 protoLogConfigBuilder.setTracingMode(
224                     PerfettoConfig.ProtoLogConfig.TracingMode.ENABLE_ALL
225                 )
226             }
227 
228             for (groupOverride in groupOverrides) {
229                 protoLogConfigBuilder.addGroupOverrides(
230                     PerfettoConfig.ProtoLogGroup.newBuilder()
231                         .setGroupName(groupOverride.groupName)
232                         .setLogFrom(
233                             PerfettoConfig.ProtoLogLevel.forNumber(
234                                 groupOverride.logFrom.ordinal + 1
235                             )
236                         )
237                         .setCollectStacktrace(groupOverride.collectStackTrace)
238                 )
239             }
240 
241             return DataSourceConfig.newBuilder()
242                 .setName(PROTOLOG_DATA_SOURCE)
243                 .setProtologConfig(protoLogConfigBuilder)
244                 .build()
245         }
246 
createDataSourceWithConfignull247         private fun createDataSourceWithConfig(
248             dataSourceConfig: DataSourceConfig
249         ): TraceConfig.DataSource {
250             return TraceConfig.DataSource.newBuilder().setConfig(dataSourceConfig).build()
251         }
252     }
253 
254     companion object {
255         private const val TRACE_BUFFER_SIZE_KB = 1024 * 1024
256 
257         private const val IME_DATA_SOURCE = "android.inputmethod"
258         private const val SF_LAYERS_DATA_SOURCE = "android.surfaceflinger.layers"
259         private const val SF_TRANSACTIONS_DATA_SOURCE = "android.surfaceflinger.transactions"
260         private const val TRANSITIONS_DATA_SOURCE = "com.android.wm.shell.transition"
261         private const val PROTOLOG_DATA_SOURCE = "android.protolog"
262         private const val VIEWCAPTURE_DATA_SOURCE = "android.viewcapture"
263 
264         private val allPerfettoPids = mutableListOf<Int>()
265         private val allPerfettoPidsLock = ReentrantLock()
266 
267         @JvmStatic
newBuildernull268         fun newBuilder(): Builder {
269             return Builder()
270         }
271 
stopAllSessionsnull272         fun stopAllSessions() {
273             allPerfettoPidsLock.lock()
274             try {
275                 allPerfettoPids.forEach { killPerfettoProcess(it) }
276                 allPerfettoPids.forEach { waitPerfettoProcessExits(it) }
277             } finally {
278                 allPerfettoPidsLock.unlock()
279             }
280         }
281 
killPerfettoProcessnull282         fun killPerfettoProcess(pid: Int) {
283             if (isPerfettoProcessUp(pid)) {
284                 executeShellCommand("kill $pid")
285             }
286         }
287 
waitPerfettoProcessExitsnull288         private fun waitPerfettoProcessExits(pid: Int) {
289             while (true) {
290                 if (!isPerfettoProcessUp(pid)) {
291                     break
292                 }
293                 Thread.sleep(50)
294             }
295         }
296 
isPerfettoProcessUpnull297         private fun isPerfettoProcessUp(pid: Int): Boolean {
298             val out = String(executeShellCommand("ps -p $pid -o CMD"))
299             return out.contains("perfetto")
300         }
301     }
302 }
303