PerfettoConfig.kt

/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.benchmark.perfetto

import android.os.Build
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.benchmark.Shell
import perfetto.protos.DataSourceConfig
import perfetto.protos.AndroidPowerConfig
import perfetto.protos.FtraceConfig
import perfetto.protos.MeminfoCounters
import perfetto.protos.ProcessStatsConfig
import perfetto.protos.SysStatsConfig
import perfetto.protos.TraceConfig
import perfetto.protos.TraceConfig.BufferConfig
import perfetto.protos.TraceConfig.BufferConfig.FillPolicy

private fun ftraceDataSource(
    atraceApps: List<String>
) = TraceConfig.DataSource(
    config = DataSourceConfig(
        name = "linux.ftrace",
        target_buffer = 0,
        ftrace_config = FtraceConfig(
            ftrace_events = listOf(
                // We need to do process tracking to ensure kernel ftrace events targeted at short-lived
                // threads are associated correctly
                "task/task_newtask",
                "task/task_rename",
                "sched/sched_process_exit",
                "sched/sched_process_free",

                // Memory events
                "mm_event/mm_event_record",
                "kmem/rss_stat",
                "kmem/ion_heap_shrink",
                "kmem/ion_heap_grow",
                "ion/ion_stat",
                "oom/oom_score_adj_update",

                // Old (kernel) LMK
                "lowmemorykiller/lowmemory_kill",
            ),
            atrace_categories = listOf(
                AtraceTag.ActivityManager,
                AtraceTag.Audio,
                AtraceTag.BinderDriver,
                AtraceTag.Camera,
                AtraceTag.Dalvik,
                AtraceTag.Frequency,
                AtraceTag.Graphics,
                AtraceTag.HardwareModules,
                AtraceTag.Idle,
                AtraceTag.Input,
                AtraceTag.MemReclaim,
                AtraceTag.Resources,
                AtraceTag.Scheduling,
                AtraceTag.Synchronization,
                AtraceTag.View,
                AtraceTag.WindowManager
                // "webview" not included to workaround b/190743595
                // "memory" not included as some Q devices requiring ftrace_event
                // configuration directly to collect this data. See b/171085599
            ).filter {
                // filter to only supported tags on unrooted build
                // TODO: use root-only tags as needed
                it.supported(api = Build.VERSION.SDK_INT, rooted = false)
            }.map {
                it.tag
            },
            atrace_apps = atraceApps,
            compact_sched = FtraceConfig.CompactSchedConfig(enabled = true)
        )
    )
)

private val PROCESS_STATS_DATASOURCE = TraceConfig.DataSource(
    config = DataSourceConfig(
        name = "linux.process_stats",
        target_buffer = 1,
        process_stats_config = ProcessStatsConfig(
            proc_stats_poll_ms = 10000,
            // This flag appears to be unreliable on API 29 unbundled perfetto, so to avoid very
            // frequent proc stats polling to name processes correctly, we currently use unbundled
            // perfetto on API 29, even though the bundled version exists. (b/218668335)
            scan_all_processes_on_start = true
        )
    )
)

private val PACKAGE_LIST_DATASOURCE = TraceConfig.DataSource(
    config = DataSourceConfig(
        name = "android.packages_list",
        target_buffer = 1,
    )
)

private val LINUX_SYS_STATS_DATASOURCE = TraceConfig.DataSource(
    config = DataSourceConfig(
        name = "linux.sys_stats",
        target_buffer = 1,
        sys_stats_config = SysStatsConfig(
            meminfo_period_ms = 1000,
            meminfo_counters = listOf(
                MeminfoCounters.MEMINFO_MEM_TOTAL,
                MeminfoCounters.MEMINFO_MEM_FREE,
                MeminfoCounters.MEMINFO_MEM_AVAILABLE,
                MeminfoCounters.MEMINFO_BUFFERS,
                MeminfoCounters.MEMINFO_CACHED,
                MeminfoCounters.MEMINFO_SWAP_CACHED,
                MeminfoCounters.MEMINFO_ACTIVE,
                MeminfoCounters.MEMINFO_INACTIVE,
                MeminfoCounters.MEMINFO_ACTIVE_ANON,
                MeminfoCounters.MEMINFO_INACTIVE_ANON,
                MeminfoCounters.MEMINFO_ACTIVE_FILE,
                MeminfoCounters.MEMINFO_INACTIVE_FILE,
                MeminfoCounters.MEMINFO_UNEVICTABLE,
                MeminfoCounters.MEMINFO_SWAP_TOTAL,
                MeminfoCounters.MEMINFO_SWAP_FREE,
                MeminfoCounters.MEMINFO_DIRTY,
                MeminfoCounters.MEMINFO_WRITEBACK,
                MeminfoCounters.MEMINFO_ANON_PAGES,
                MeminfoCounters.MEMINFO_MAPPED,
                MeminfoCounters.MEMINFO_SHMEM,
            )
        )
    )
)

private val ANDROID_POWER_DATASOURCE = TraceConfig.DataSource(
    config = DataSourceConfig(
        name = "android.power",
        android_power_config = AndroidPowerConfig(
            battery_poll_ms = 250,
            battery_counters = listOf(
                AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CAPACITY_PERCENT,
                AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CHARGE,
                AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CURRENT
            ),
            collect_power_rails = true
        )
    )
)

/**
 * Config for perfetto.
 *
 * Eventually, this should be more configurable.
 *
 * @suppress
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun perfettoConfig(
    atraceApps: List<String>
) = TraceConfig(
    buffers = listOf(
        BufferConfig(size_kb = 32768, FillPolicy.RING_BUFFER),
        BufferConfig(size_kb = 4096, FillPolicy.RING_BUFFER)
    ),
    data_sources = listOf(
        ftraceDataSource(atraceApps),
        PROCESS_STATS_DATASOURCE,
        PACKAGE_LIST_DATASOURCE,
        LINUX_SYS_STATS_DATASOURCE,
        ANDROID_POWER_DATASOURCE,
        TraceConfig.DataSource(DataSourceConfig("android.surfaceflinger.frametimeline")),
        TraceConfig.DataSource(DataSourceConfig("track_event")) // required by tracing-perfetto
    ),
    // periodically dump to file, so we don't overrun our ring buffer
    // buffers are expected to be big enough for 5 seconds, so conservatively set 2.5 dump
    write_into_file = true,
    file_write_period_ms = 2500,

    // multiple of file_write_period_ms, enables trace processor to work in batches
    flush_period_ms = 5000
)

@RequiresApi(21) // needed for shell access
internal fun TraceConfig.validateAndEncode(): ByteArray {
    val ftraceConfig = data_sources
        .mapNotNull { it.config?.ftrace_config }
        .first()

    // check tags against known-supported tags based on SDK_INT / root status
    val supportedTags = AtraceTag.supported(
        api = Build.VERSION.SDK_INT,
        rooted = Shell.isSessionRooted()
    ).map { it.tag }.toSet()

    val unsupportedTags = (ftraceConfig.atrace_categories - supportedTags)
    check(unsupportedTags.isEmpty()) {
        "Error - attempted to use unsupported atrace tags: $unsupportedTags"
    }

    if (Build.VERSION.SDK_INT < 28) {
        check(!ftraceConfig.atrace_apps.contains("*")) {
            "Support for wildcard (*) app matching in atrace added in API 28"
        }
    }

    if (Build.VERSION.SDK_INT < 24) {
        val packageList = ftraceConfig.atrace_apps.joinToString(",")
        check(packageList.length <= 91) {
            "Unable to trace package list (\"$packageList\").length = " +
                "${packageList.length} > 91 chars, which is the limit before API 24"
        }
    }
    return encode()
}