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