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