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