1 /*
2  * Copyright 2022 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.macro
18 
19 import androidx.annotation.RestrictTo
20 import androidx.benchmark.Shell
21 
22 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
23 object PowerRail {
24 
25     private const val DUMPSYS_POWERSTATS = "dumpsys powerstats"
26 
27     /**
28      * Looking for something like this:
29      *
30      * ChannelId: 10, ChannelName: S9S_VDD_AOC, ChannelSubsystem: AOC PowerStatsService dumpsys:
31      * available Channels ChannelId: 0, ChannelName: S10M_VDD_TPU, ChannelSubsystem: TPU ChannelId:
32      * 1, ChannelName: VSYS_PWR_MODEM, ChannelSubsystem: Modem ChannelId: 2, ChannelName:
33      * VSYS_PWR_RFFE, ChannelSubsystem: Cellular ChannelId: 3, ChannelName: S2M_VDD_CPUCL2,
34      * ChannelSubsystem: CPU(BIG) ChannelId: 4, ChannelName: S3M_VDD_CPUCL1, ChannelSubsystem:
35      * CPU(MID)
36      */
37     private val CHANNEL_ID_REGEX = "ChannelId:(.*)".toRegex()
38 
39     /**
40      * Checks if rail metrics are generated on specified device.
41      *
42      * @Throws UnsupportedOperationException if `hasException == true` and no rail metrics are
43      *   found.
44      */
hasMetricsnull45     fun hasMetrics(throwOnMissingMetrics: Boolean = false): Boolean {
46         // Note - we don't capture stderr, since if dumpsys fails due to missing
47         // service, we'll correctly fail to find channels in stdout
48         val output = Shell.executeCommandCaptureStdoutOnly(DUMPSYS_POWERSTATS)
49         return hasMetrics(output, throwOnMissingMetrics)
50     }
51 
hasMetricsnull52     internal fun hasMetrics(output: String, throwOnMissingMetrics: Boolean = false): Boolean {
53         val line = output.splitToSequence("\r?\n".toRegex()).find { it.contains(CHANNEL_ID_REGEX) }
54         if (!line.isNullOrBlank()) {
55             return true
56         }
57         if (throwOnMissingMetrics) {
58             throw UnsupportedOperationException(
59                 """
60                 Rail metrics are not available on this device.
61                 To check a device for power/energy measurement support, the following command's
62                 output must contain rows underneath the "available Channels" section:
63 
64                 adb shell $DUMPSYS_POWERSTATS
65 
66                 To check at runtime for this, use PowerMetric.deviceSupportsPowerEnergy()
67 
68                 """
69                     .trimIndent()
70             )
71         }
72         return false
73     }
74 }
75