1 /*
2 * Copyright 2021 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 androidx.benchmark.perfetto
18
19 import android.os.Build
20 import androidx.annotation.RequiresApi
21 import androidx.annotation.RestrictTo
22 import androidx.benchmark.Arguments
23 import androidx.benchmark.Shell
24 import androidx.benchmark.VirtualFile
25 import perfetto.protos.AndroidPowerConfig
26 import perfetto.protos.DataSourceConfig
27 import perfetto.protos.FtraceConfig
28 import perfetto.protos.HeapprofdConfig
29 import perfetto.protos.MeminfoCounters
30 import perfetto.protos.PerfEventConfig
31 import perfetto.protos.PerfEvents
32 import perfetto.protos.ProcessStatsConfig
33 import perfetto.protos.SysStatsConfig
34 import perfetto.protos.TraceConfig
35 import perfetto.protos.TraceConfig.BufferConfig
36 import perfetto.protos.TraceConfig.BufferConfig.FillPolicy
37 import perfetto.protos.TrackEventConfig
38
39 /**
40 * Configuration for Perfetto trace recording.
41 *
42 * For more info, see https://perfetto.dev/docs/concepts/config
43 */
44 @ExperimentalPerfettoCaptureApi
45 sealed class PerfettoConfig(internal val isTextProto: Boolean) {
writeTonull46 @RequiresApi(23) internal abstract fun writeTo(virtualFile: VirtualFile)
47
48 /**
49 * Binary representation of a Perfetto config proto.
50 *
51 * This can be generated by a proto library, together with the definition here:
52 */
53 class Binary(val bytes: ByteArray) : PerfettoConfig(isTextProto = false) {
54 @RequiresApi(23)
55 override fun writeTo(virtualFile: VirtualFile) {
56 virtualFile.writeBytes(bytes)
57 }
58 }
59
60 /**
61 * TextProto representation of a Perfetto config.
62 *
63 * This can be generated with https://ui.perfetto.dev/#!/record/
64 *
65 * Note: this format is not recommended for long term use - the [Binary] proto representation is
66 * more likely to remain stable over time, across Perfetto/Android OS versions. For more
67 * information, see
68 * [the Perfetto documentation](https://perfetto.dev/docs/concepts/config#pbtx-vs-binary-format).
69 */
70 class Text(val text: String) : PerfettoConfig(isTextProto = true) {
71 @RequiresApi(23)
writeTonull72 override fun writeTo(virtualFile: VirtualFile) {
73 virtualFile.writeText(text)
74 }
75 }
76
77 /** Benchmark defined config for perfetto trace capture, used by benchmark/macrobenchmark. */
78 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
79 class Benchmark(
80 private val appTagPackages: List<String>,
81 private val useStackSamplingConfig: Boolean,
82 ) : PerfettoConfig(isTextProto = false) {
83 @RequiresApi(23)
writeTonull84 override fun writeTo(virtualFile: VirtualFile) {
85 val stackSamplingConfig =
86 if (useStackSamplingConfig) {
87 Arguments.profiler?.config(appTagPackages)
88 } else {
89 null
90 }
91 virtualFile.writeBytes(
92 perfettoConfig(
93 atraceApps =
94 if (Build.VERSION.SDK_INT <= 28 || appTagPackages.isEmpty()) {
95 appTagPackages
96 } else {
97 listOf("*")
98 },
99 stackSamplingConfig = stackSamplingConfig
100 )
101 .validateAndEncode()
102 )
103 }
104 }
105
106 /**
107 * Only used by group-internal tests
108 *
109 * Most minimal config possible
110 */
111 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
112 class MinimalTest(private val appTagPackages: List<String>) :
113 PerfettoConfig(isTextProto = false) {
114 @RequiresApi(23)
writeTonull115 override fun writeTo(virtualFile: VirtualFile) {
116 virtualFile.writeBytes(
117 configOf(listOf(minimalAtraceDataSource(atraceApps = appTagPackages)))
118 .validateAndEncode()
119 )
120 }
121 }
122 }
123
minimalAtraceDataSourcenull124 private fun minimalAtraceDataSource(atraceApps: List<String>) =
125 TraceConfig.DataSource(
126 config =
127 DataSourceConfig(
128 name = "linux.ftrace",
129 target_buffer = 0,
130 ftrace_config =
131 FtraceConfig(
132 ftrace_events = emptyList(),
133 atrace_categories = emptyList(),
134 atrace_apps = atraceApps,
135 compact_sched = null
136 )
137 )
138 )
139
140 private fun ftraceDataSource(atraceApps: List<String>) =
141 TraceConfig.DataSource(
142 config =
143 DataSourceConfig(
144 name = "linux.ftrace",
145 target_buffer = 0,
146 ftrace_config =
147 FtraceConfig(
148 ftrace_events =
149 listOf(
150 // We need to do process tracking to ensure kernel ftrace events
151 // targeted at short-lived
152 // threads are associated correctly
153 "task/task_newtask",
154 "task/task_rename",
155 "sched/sched_process_exit",
156 "sched/sched_process_free",
157
158 // Memory events
159 "mm_event/mm_event_record",
160 "kmem/rss_stat",
161 "kmem/ion_heap_shrink",
162 "kmem/ion_heap_grow",
163 "ion/ion_stat",
164 "oom/oom_score_adj_update",
165
166 // Disk I/O
167 "disk",
168 "ufs/ufshcd_clk_gating",
169
170 // Old (kernel) LMK
171 "lowmemorykiller/lowmemory_kill",
172 ),
173 atrace_categories =
174 listOf(
175 AtraceTag.ActivityManager,
176 AtraceTag.Aidl,
177 AtraceTag.Audio,
178 AtraceTag.BinderDriver,
179 AtraceTag.Camera,
180 AtraceTag.Dalvik,
181 AtraceTag.Frequency,
182 AtraceTag.Graphics,
183 AtraceTag.HardwareModules,
184 AtraceTag.Idle,
185 AtraceTag.Input,
186 AtraceTag.MemReclaim,
187 AtraceTag.Power,
188 AtraceTag.Resources,
189 AtraceTag.Scheduling,
190 AtraceTag.Synchronization,
191 AtraceTag.View,
192 AtraceTag.WindowManager
193 // "webview" not included to workaround b/190743595
194 // "memory" not included as some Q devices requiring
195 // ftrace_event
196 // configuration directly to collect this data. See b/171085599
197 )
198 .filter {
199 // filter to only supported tags on unrooted build
200 // TODO: use root-only tags as needed
201 it.supported(api = Build.VERSION.SDK_INT, rooted = false)
202 }
<lambda>null203 .map { it.tag },
204 atrace_apps = atraceApps,
205 compact_sched = FtraceConfig.CompactSchedConfig(enabled = true)
206 )
207 )
208 )
209
processStatsDataSourcenull210 private fun processStatsDataSource(
211 stackSamplingConfig: StackSamplingConfig?
212 ): TraceConfig.DataSource {
213 return TraceConfig.DataSource(
214 config =
215 DataSourceConfig(
216 name = "linux.process_stats",
217 target_buffer = 1,
218 process_stats_config =
219 ProcessStatsConfig(
220 proc_stats_poll_ms = stackSamplingConfig?.frequency?.toInt() ?: 10000,
221 // This flag appears to be unreliable on API 29 unbundled perfetto, so to
222 // avoid very
223 // frequent proc stats polling to name processes correctly, we currently use
224 // unbundled
225 // perfetto on API 29, even though the bundled version exists. (b/218668335)
226 scan_all_processes_on_start = true
227 )
228 )
229 )
230 }
231
232 private val PACKAGE_LIST_DATASOURCE =
233 TraceConfig.DataSource(
234 config =
235 DataSourceConfig(
236 name = "android.packages_list",
237 target_buffer = 1,
238 )
239 )
240
241 private val LINUX_SYS_STATS_DATASOURCE =
242 TraceConfig.DataSource(
243 config =
244 DataSourceConfig(
245 name = "linux.sys_stats",
246 target_buffer = 1,
247 sys_stats_config =
248 SysStatsConfig(
249 meminfo_period_ms = 1000,
250 meminfo_counters =
251 listOf(
252 MeminfoCounters.MEMINFO_MEM_TOTAL,
253 MeminfoCounters.MEMINFO_MEM_FREE,
254 MeminfoCounters.MEMINFO_MEM_AVAILABLE,
255 MeminfoCounters.MEMINFO_BUFFERS,
256 MeminfoCounters.MEMINFO_CACHED,
257 MeminfoCounters.MEMINFO_SWAP_CACHED,
258 MeminfoCounters.MEMINFO_ACTIVE,
259 MeminfoCounters.MEMINFO_INACTIVE,
260 MeminfoCounters.MEMINFO_ACTIVE_ANON,
261 MeminfoCounters.MEMINFO_INACTIVE_ANON,
262 MeminfoCounters.MEMINFO_ACTIVE_FILE,
263 MeminfoCounters.MEMINFO_INACTIVE_FILE,
264 MeminfoCounters.MEMINFO_UNEVICTABLE,
265 MeminfoCounters.MEMINFO_SWAP_TOTAL,
266 MeminfoCounters.MEMINFO_SWAP_FREE,
267 MeminfoCounters.MEMINFO_DIRTY,
268 MeminfoCounters.MEMINFO_WRITEBACK,
269 MeminfoCounters.MEMINFO_ANON_PAGES,
270 MeminfoCounters.MEMINFO_MAPPED,
271 MeminfoCounters.MEMINFO_SHMEM,
272 )
273 )
274 )
275 )
276
277 private val ANDROID_POWER_DATASOURCE =
278 TraceConfig.DataSource(
279 config =
280 DataSourceConfig(
281 name = "android.power",
282 android_power_config =
283 AndroidPowerConfig(
284 battery_poll_ms = 250,
285 battery_counters =
286 listOf(
287 AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CAPACITY_PERCENT,
288 AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CHARGE,
289 AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CURRENT
290 ),
291 collect_power_rails = true
292 )
293 )
294 )
295
296 /** A Perfetto data source to enable stack sampling. */
stackSamplingSourcenull297 private fun stackSamplingSource(
298 config: StackSamplingConfig,
299 ): List<TraceConfig.DataSource> {
300 val sources = mutableListOf<TraceConfig.DataSource>()
301 sources +=
302 TraceConfig.DataSource(
303 config =
304 DataSourceConfig(
305 name = "linux.perf",
306 target_buffer = 1,
307 perf_event_config =
308 PerfEventConfig(
309 timebase =
310 PerfEvents.Timebase(
311 counter = PerfEvents.Counter.SW_CPU_CLOCK,
312 frequency = config.frequency,
313 timestamp_clock = PerfEvents.PerfClock.PERF_CLOCK_MONOTONIC
314 ),
315 callstack_sampling =
316 PerfEventConfig.CallstackSampling(
317 scope =
318 PerfEventConfig.Scope(target_cmdline = config.packageNames)
319 ),
320 kernel_frames = false
321 )
322 )
323 )
324 sources +=
325 TraceConfig.DataSource(
326 config =
327 DataSourceConfig(
328 name = "linux.perf",
329 target_buffer = 1,
330 perf_event_config =
331 PerfEventConfig(
332 timebase =
333 PerfEvents.Timebase(
334 tracepoint = PerfEvents.Tracepoint(name = "sched_switch"),
335 period = 1
336 ),
337 callstack_sampling =
338 PerfEventConfig.CallstackSampling(
339 scope =
340 PerfEventConfig.Scope(target_cmdline = config.packageNames)
341 ),
342 kernel_frames = false
343 )
344 )
345 )
346 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
347 // https://perfetto.dev/docs/reference/trace-config-proto#HeapprofdConfig
348 sources +=
349 TraceConfig.DataSource(
350 config =
351 DataSourceConfig(
352 name = "android.heapprofd",
353 heapprofd_config =
354 HeapprofdConfig(
355 shmem_size_bytes = 8388608,
356 sampling_interval_bytes = 2048,
357 block_client = true,
358 process_cmdline = config.packageNames,
359 heaps =
360 listOf(
361 "com.android.art" // Java Heaps
362 ),
363 continuous_dump_config =
364 HeapprofdConfig.ContinuousDumpConfig(
365 dump_phase_ms = 0,
366 dump_interval_ms = 500 // ms
367 )
368 )
369 )
370 )
371 }
372 return sources
373 }
374
375 // reduce timeout to reduce trace capture overhead when devices have data source issues
376 // See b/323601788 and b/307649002.
377 internal const val PERFETTO_DATA_SOURCE_STOP_TIMEOUT_MS = 2500
378
configOfnull379 private fun configOf(dataSources: List<TraceConfig.DataSource>) =
380 TraceConfig(
381 buffers =
382 listOf(
383 BufferConfig(size_kb = 32768, FillPolicy.RING_BUFFER),
384 BufferConfig(size_kb = 4096, FillPolicy.RING_BUFFER)
385 ),
386 data_sources = dataSources,
387 // periodically dump to file, so we don't overrun our ring buffer
388 // buffers are expected to be big enough for 5 seconds, so conservatively set 2.5 dump
389 write_into_file = true,
390 file_write_period_ms = 2500,
391
392 // multiple of file_write_period_ms, enables trace processor to work in batches
393 flush_period_ms = 5000,
394 data_source_stop_timeout_ms = PERFETTO_DATA_SOURCE_STOP_TIMEOUT_MS,
395 )
396
397 /**
398 * Config for perfetto.
399 *
400 * Eventually, this should be more configurable.
401 */
402 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
403 internal fun perfettoConfig(
404 atraceApps: List<String>,
405 stackSamplingConfig: StackSamplingConfig?
406 ): TraceConfig {
407 val dataSources =
408 mutableListOf(
409 ftraceDataSource(atraceApps),
410 processStatsDataSource(stackSamplingConfig),
411 PACKAGE_LIST_DATASOURCE,
412 LINUX_SYS_STATS_DATASOURCE,
413 ANDROID_POWER_DATASOURCE,
414 TraceConfig.DataSource(DataSourceConfig("android.gpu.memory")),
415 TraceConfig.DataSource(DataSourceConfig("android.surfaceflinger.frame")),
416 TraceConfig.DataSource(DataSourceConfig("android.surfaceflinger.frametimeline")),
417 TraceConfig.DataSource(
418 DataSourceConfig(
419 "track_event",
420 track_event_config =
421 TrackEventConfig(
422 // currently `tracing:tracing-perfetto` records all events as part of
423 // the "rendering" category. In the future we should consider only
424 // setting this if sdk tracing is requested.
425 enabled_categories = listOf("rendering"),
426 disabled_categories = listOf("*")
427 )
428 )
429 )
430 )
431 if (stackSamplingConfig != null) {
432 dataSources += stackSamplingSource(config = stackSamplingConfig)
433 }
434 return configOf(dataSources)
435 }
436
validateAndEncodenull437 internal fun TraceConfig.validateAndEncode(): ByteArray {
438 val ftraceConfig = data_sources.firstNotNullOf { it.config?.ftrace_config }
439
440 // check tags against known-supported tags based on SDK_INT / root status
441 val supportedTags =
442 AtraceTag.supported(api = Build.VERSION.SDK_INT, rooted = Shell.isSessionRooted())
443 .map { it.tag }
444 .toSet()
445
446 val unsupportedTags = (ftraceConfig.atrace_categories - supportedTags)
447 check(unsupportedTags.isEmpty()) {
448 "Error - attempted to use unsupported atrace tags: $unsupportedTags"
449 }
450
451 if (Build.VERSION.SDK_INT < 28) {
452 check(!ftraceConfig.atrace_apps.contains("*")) {
453 "Support for wildcard (*) app matching in atrace added in API 28"
454 }
455 }
456
457 if (Build.VERSION.SDK_INT < 24) {
458 val packageList = ftraceConfig.atrace_apps.joinToString(",")
459 check(packageList.length <= 91) {
460 "Unable to trace package list (\"$packageList\").length = " +
461 "${packageList.length} > 91 chars, which is the limit before API 24"
462 }
463 }
464 return encode()
465 }
466