PowerRail.kt

/*
 * Copyright 2022 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.macro

import androidx.annotation.RestrictTo
import androidx.benchmark.Shell

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
object PowerRail {

    private const val DUMPSYS_POWERSTATS = "dumpsys powerstats"

    /**
     * Looking for something like this:
     *
     * ChannelId: 10, ChannelName: S9S_VDD_AOC, ChannelSubsystem: AOC
     * PowerStatsService dumpsys: available Channels
     * ChannelId: 0, ChannelName: S10M_VDD_TPU, ChannelSubsystem: TPU
     * ChannelId: 1, ChannelName: VSYS_PWR_MODEM, ChannelSubsystem: Modem
     * ChannelId: 2, ChannelName: VSYS_PWR_RFFE, ChannelSubsystem: Cellular
     * ChannelId: 3, ChannelName: S2M_VDD_CPUCL2, ChannelSubsystem: CPU(BIG)
     * ChannelId: 4, ChannelName: S3M_VDD_CPUCL1, ChannelSubsystem: CPU(MID)
     */
    private val CHANNEL_ID_REGEX = "ChannelId:(.*)".toRegex()

    /**
     * Checks if rail metrics are generated on specified device.
     *
     * @Throws UnsupportedOperationException if `hasException == true` and no rail metrics are found.
     */
    fun hasMetrics(throwOnMissingMetrics: Boolean = false): Boolean {
        val output = Shell.executeCommand(DUMPSYS_POWERSTATS)
        return hasMetrics(output, throwOnMissingMetrics)
    }

    internal fun hasMetrics(output: String, throwOnMissingMetrics: Boolean = false): Boolean {
        val line = output.splitToSequence("\r?\n".toRegex()).find {
            it.contains(CHANNEL_ID_REGEX)
        }
        if (!line.isNullOrBlank()) {
            return true
        }
        if (throwOnMissingMetrics) {
            throw UnsupportedOperationException(
                """
                Rail metrics are not available on this device.
                To check a device for power/energy measurement support, the following command's
                output must contain rows underneath the "available Channels" section:

                adb shell $DUMPSYS_POWERSTATS

                To check at runtime for this, use PowerRail.hasMetrics()

                """.trimIndent()
            )
        }
        return false
    }
}