1 /*
<lambda>null2  * 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.perfetto
18 
19 import androidx.benchmark.macro.ExperimentalMetricApi
20 import androidx.benchmark.macro.PowerCategory
21 import androidx.benchmark.macro.PowerMetric
22 import androidx.benchmark.traceprocessor.Slice
23 import androidx.benchmark.traceprocessor.TraceProcessor
24 
25 // We want to use android_powrails.sql, but cannot as they do not split into sections with slice
26 
27 @OptIn(ExperimentalMetricApi::class)
28 internal object PowerQuery {
29     private fun getFullQuery(slice: Slice) =
30         """
31         SELECT
32             t.name,
33             max(c.value) - min(c.value) AS energyUws,
34             (max(c.value) - min(c.value))/((max(c.ts) - min(c.ts)) / 1000000) AS powerUs
35         FROM counter c
36         JOIN counter_track t ON c.track_id = t.id
37         WHERE t.name GLOB 'power.*'
38         AND c.ts >= ${slice.ts} AND c.ts <= ${slice.endTs}
39         GROUP BY t.name
40     """
41             .trimIndent()
42 
43     private val categoryToSubsystem =
44         mapOf(
45             PowerCategory.CPU to listOf("Cpu"),
46             PowerCategory.DISPLAY to listOf("Display"),
47             PowerCategory.GPU to listOf("Gpu"),
48             PowerCategory.GPS to listOf("Gps"),
49             PowerCategory.MEMORY to listOf("Ddr", "MemoryInterface"),
50             PowerCategory.MACHINE_LEARNING to listOf("Tpu"),
51             PowerCategory.NETWORK to listOf("Aoc", "Radio", "VsysPwrMmwave", "Wifi", "Modem"),
52             PowerCategory.UNCATEGORIZED to emptyList()
53         )
54 
55     /**
56      * The ComponentMeasurement object are built with attributes:
57      *
58      * @param name The name of the subsystem associated with the power usage in camel case.
59      * @param energyUws The energy used during the trace, measured in uWs.
60      * @param powerUw The energy used divided by the elapsed time, measured in uW.
61      */
62     data class ComponentMeasurement(
63         var name: String,
64         var energyUws: Double,
65         var powerUw: Double,
66     ) {
67         fun getValue(type: PowerMetric.Type): Double {
68             return if (type is PowerMetric.Type.Power) powerUw else energyUws
69         }
70     }
71 
72     /**
73      * The CategoryMeasurement object are built with attributes:
74      *
75      * @param energyUws The sum total energy used during the trace of all components in the
76      *   category, measured in uWs.
77      * @param powerUw The sum total energy used divided by the elapsed time of all components in the
78      *   category, measured in uW.
79      * @param components A list of all ComponentMeasurements under the same `PowerCategory`.
80      */
81     data class CategoryMeasurement(
82         var energyUws: Double,
83         var powerUw: Double,
84         var components: List<ComponentMeasurement>
85     ) {
86         fun getValue(type: PowerMetric.Type): Double {
87             return if (type is PowerMetric.Type.Power) powerUw else energyUws
88         }
89     }
90 
91     fun getPowerMetrics(
92         session: TraceProcessor.Session,
93         slice: Slice
94     ): Map<PowerCategory, CategoryMeasurement> {
95         // gather all recorded rails
96         val railMetrics: List<ComponentMeasurement> = getRailMetrics(session, slice)
97         railMetrics.ifEmpty {
98             return emptyMap()
99         }
100 
101         // sort ComponentMeasurements into CategoryMeasurements
102         return sortComponentsByCategories(railMetrics)
103     }
104 
105     private fun sortComponentsByCategories(
106         railMetrics: List<ComponentMeasurement>
107     ): Map<PowerCategory, CategoryMeasurement> {
108         // sort all ComponentMeasurements into CategoryMeasurements
109         return PowerCategory.values()
110             .associateWith { category ->
111                 // combine components under same category
112                 val rails: List<ComponentMeasurement> =
113                     railMetrics.filter { rail -> railInCategory(category, rail.name) }
114 
115                 // combine components into category
116                 rails.fold(
117                     CategoryMeasurement(energyUws = 0.0, powerUw = 0.0, components = rails)
118                 ) { total, next ->
119                     CategoryMeasurement(
120                         energyUws = total.energyUws + next.energyUws,
121                         powerUw = total.powerUw + next.powerUw,
122                         components = total.components
123                     )
124                 }
125             }
126             .filter { (_, measurement) -> measurement.components.isNotEmpty() }
127     }
128 
129     private fun getRailMetrics(
130         session: TraceProcessor.Session,
131         slice: Slice
132     ): List<ComponentMeasurement> {
133         val query = getFullQuery(slice)
134         return session
135             .query(query)
136             .map {
137                 ComponentMeasurement(
138                     name = (it["name"] as String).camelCase(),
139                     energyUws = it["energyUws"] as Double,
140                     powerUw = it["powerUs"] as Double,
141                 )
142             }
143             .toList()
144     }
145 
146     /**
147      * Checks if category contains rail, or is uncategorized.
148      *
149      * @param category A [PowerCategory] which maps to a list of subsystems.
150      * @param railName The name of a rail.
151      */
152     private fun railInCategory(category: PowerCategory, railName: String): Boolean {
153         if (category == PowerCategory.UNCATEGORIZED) {
154             return !filterRails(categoryToSubsystem.values.flatten(), railName)
155         }
156         return filterRails(categoryToSubsystem[category] ?: emptyList(), railName)
157     }
158 
159     /**
160      * Checks if rail name contains subsystem.
161      *
162      * @param subsystems A list of subsystems to check against rail name. If the rail is a part of
163      *   the subsystem, the subsystem will be a substring of the rail name.
164      * @param railName The name of a rail.
165      */
166     private fun filterRails(subsystems: List<String>, railName: String): Boolean {
167         for (subsystem in subsystems) {
168             if (railName.contains(subsystem)) {
169                 return true
170             }
171         }
172         return false
173     }
174 
175     init {
176         check(categoryToSubsystem.keys.size == PowerCategory.values().size) {
177             "Missing power categories in categoryToSubsystem"
178         }
179     }
180 }
181