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