1 /* 2 * Copyright (C) 2024 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 package com.android.server.power.stats; 17 18 import android.bluetooth.BluetoothActivityEnergyInfo; 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.UidTraffic; 21 import android.content.pm.PackageManager; 22 import android.hardware.power.stats.EnergyConsumerType; 23 import android.os.BatteryConsumer; 24 import android.os.Handler; 25 import android.os.PersistableBundle; 26 import android.util.Slog; 27 import android.util.SparseArray; 28 29 import com.android.internal.os.Clock; 30 import com.android.internal.os.PowerStats; 31 32 import java.util.Arrays; 33 import java.util.List; 34 import java.util.concurrent.CompletableFuture; 35 import java.util.concurrent.Executor; 36 import java.util.concurrent.TimeUnit; 37 import java.util.function.IntSupplier; 38 39 public class BluetoothPowerStatsCollector extends PowerStatsCollector { 40 private static final String TAG = "BluetoothPowerStatsCollector"; 41 42 private static final long BLUETOOTH_ACTIVITY_REQUEST_TIMEOUT = 20000; 43 44 private static final long ENERGY_UNSPECIFIED = -1; 45 46 interface BluetoothStatsRetriever { 47 interface Callback { onBluetoothScanTime(int uid, long scanTimeMs)48 void onBluetoothScanTime(int uid, long scanTimeMs); 49 } 50 retrieveBluetoothScanTimes(Callback callback)51 void retrieveBluetoothScanTimes(Callback callback); 52 requestControllerActivityEnergyInfo(Executor executor, BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback callback)53 boolean requestControllerActivityEnergyInfo(Executor executor, 54 BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback callback); 55 } 56 57 interface Injector { getHandler()58 Handler getHandler(); getClock()59 Clock getClock(); getUidResolver()60 PowerStatsUidResolver getUidResolver(); getPowerStatsCollectionThrottlePeriod(String powerComponentName)61 long getPowerStatsCollectionThrottlePeriod(String powerComponentName); getPackageManager()62 PackageManager getPackageManager(); getConsumedEnergyRetriever()63 ConsumedEnergyRetriever getConsumedEnergyRetriever(); getVoltageSupplier()64 IntSupplier getVoltageSupplier(); getBluetoothStatsRetriever()65 BluetoothStatsRetriever getBluetoothStatsRetriever(); 66 } 67 68 private final Injector mInjector; 69 70 private BluetoothPowerStatsLayout mLayout; 71 private boolean mIsInitialized; 72 private PowerStats mPowerStats; 73 private long[] mDeviceStats; 74 private BluetoothStatsRetriever mBluetoothStatsRetriever; 75 private ConsumedEnergyRetriever mConsumedEnergyRetriever; 76 private IntSupplier mVoltageSupplier; 77 private int[] mEnergyConsumerIds = new int[0]; 78 private long[] mLastConsumedEnergyUws; 79 private int mLastVoltageMv; 80 81 private long mLastRxTime; 82 private long mLastTxTime; 83 private long mLastIdleTime; 84 85 private static class UidStats { 86 public long rxCount; 87 public long lastRxCount; 88 public long txCount; 89 public long lastTxCount; 90 public long scanTime; 91 public long lastScanTime; 92 } 93 94 private final SparseArray<UidStats> mUidStats = new SparseArray<>(); 95 BluetoothPowerStatsCollector(Injector injector)96 BluetoothPowerStatsCollector(Injector injector) { 97 super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod( 98 BatteryConsumer.powerComponentIdToString( 99 BatteryConsumer.POWER_COMPONENT_BLUETOOTH)), 100 injector.getUidResolver(), 101 injector.getClock()); 102 mInjector = injector; 103 } 104 105 @Override setEnabled(boolean enabled)106 public void setEnabled(boolean enabled) { 107 if (enabled) { 108 PackageManager packageManager = mInjector.getPackageManager(); 109 super.setEnabled(packageManager != null 110 && packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)); 111 } else { 112 super.setEnabled(false); 113 } 114 } 115 ensureInitialized()116 private boolean ensureInitialized() { 117 if (mIsInitialized) { 118 return true; 119 } 120 121 if (!isEnabled()) { 122 return false; 123 } 124 125 mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever(); 126 mVoltageSupplier = mInjector.getVoltageSupplier(); 127 mBluetoothStatsRetriever = mInjector.getBluetoothStatsRetriever(); 128 mEnergyConsumerIds = 129 mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.BLUETOOTH); 130 mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length]; 131 Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED); 132 133 mLayout = new BluetoothPowerStatsLayout(); 134 mLayout.addDeviceBluetoothControllerActivity(); 135 mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length); 136 mLayout.addDeviceSectionUsageDuration(); 137 mLayout.addDeviceSectionPowerEstimate(); 138 mLayout.addUidTrafficStats(); 139 mLayout.addUidSectionPowerEstimate(); 140 141 PersistableBundle extras = new PersistableBundle(); 142 mLayout.toExtras(extras); 143 PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor( 144 BatteryConsumer.POWER_COMPONENT_BLUETOOTH, mLayout.getDeviceStatsArrayLength(), 145 null, 0, mLayout.getUidStatsArrayLength(), 146 extras); 147 mPowerStats = new PowerStats(powerStatsDescriptor); 148 mDeviceStats = mPowerStats.stats; 149 150 mIsInitialized = true; 151 return true; 152 } 153 154 @Override collectStats()155 protected PowerStats collectStats() { 156 if (!ensureInitialized()) { 157 return null; 158 } 159 160 mPowerStats.uidStats.clear(); 161 162 collectBluetoothActivityInfo(); 163 collectBluetoothScanStats(); 164 165 if (mEnergyConsumerIds.length != 0) { 166 collectEnergyConsumers(); 167 } 168 169 return mPowerStats; 170 } 171 collectBluetoothActivityInfo()172 private void collectBluetoothActivityInfo() { 173 CompletableFuture<BluetoothActivityEnergyInfo> immediateFuture = new CompletableFuture<>(); 174 boolean success = mBluetoothStatsRetriever.requestControllerActivityEnergyInfo( 175 Runnable::run, 176 new BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback() { 177 @Override 178 public void onBluetoothActivityEnergyInfoAvailable( 179 BluetoothActivityEnergyInfo info) { 180 immediateFuture.complete(info); 181 } 182 183 @Override 184 public void onBluetoothActivityEnergyInfoError(int error) { 185 immediateFuture.completeExceptionally( 186 new RuntimeException("error: " + error)); 187 } 188 }); 189 190 if (!success) { 191 return; 192 } 193 194 BluetoothActivityEnergyInfo activityInfo; 195 try { 196 activityInfo = immediateFuture.get(BLUETOOTH_ACTIVITY_REQUEST_TIMEOUT, 197 TimeUnit.MILLISECONDS); 198 } catch (Exception e) { 199 Slog.e(TAG, "Cannot acquire BluetoothActivityEnergyInfo", e); 200 activityInfo = null; 201 } 202 203 if (activityInfo == null) { 204 return; 205 } 206 207 long rxTime = activityInfo.getControllerRxTimeMillis(); 208 long rxTimeDelta = Math.max(0, rxTime - mLastRxTime); 209 mLayout.setDeviceRxTime(mDeviceStats, rxTimeDelta); 210 mLastRxTime = rxTime; 211 212 long txTime = activityInfo.getControllerTxTimeMillis(); 213 long txTimeDelta = Math.max(0, txTime - mLastTxTime); 214 mLayout.setDeviceTxTime(mDeviceStats, txTimeDelta); 215 mLastTxTime = txTime; 216 217 long idleTime = activityInfo.getControllerIdleTimeMillis(); 218 long idleTimeDelta = Math.max(0, idleTime - mLastIdleTime); 219 mLayout.setDeviceIdleTime(mDeviceStats, idleTimeDelta); 220 mLastIdleTime = idleTime; 221 222 mPowerStats.durationMs = rxTimeDelta + txTimeDelta + idleTimeDelta; 223 224 List<UidTraffic> uidTraffic = activityInfo.getUidTraffic(); 225 for (int i = uidTraffic.size() - 1; i >= 0; i--) { 226 UidTraffic ut = uidTraffic.get(i); 227 int uid = mUidResolver.mapUid(ut.getUid()); 228 UidStats counts = mUidStats.get(uid); 229 if (counts == null) { 230 counts = new UidStats(); 231 mUidStats.put(uid, counts); 232 } 233 counts.rxCount += ut.getRxBytes(); 234 counts.txCount += ut.getTxBytes(); 235 } 236 237 for (int i = mUidStats.size() - 1; i >= 0; i--) { 238 UidStats counts = mUidStats.valueAt(i); 239 long rxDelta = Math.max(0, counts.rxCount - counts.lastRxCount); 240 counts.lastRxCount = counts.rxCount; 241 counts.rxCount = 0; 242 243 long txDelta = Math.max(0, counts.txCount - counts.lastTxCount); 244 counts.lastTxCount = counts.txCount; 245 counts.txCount = 0; 246 247 if (rxDelta != 0 || txDelta != 0) { 248 int uid = mUidStats.keyAt(i); 249 long[] stats = mPowerStats.uidStats.get(uid); 250 if (stats == null) { 251 stats = new long[mLayout.getUidStatsArrayLength()]; 252 mPowerStats.uidStats.put(uid, stats); 253 } 254 255 mLayout.setUidRxBytes(stats, rxDelta); 256 mLayout.setUidTxBytes(stats, txDelta); 257 } 258 } 259 } 260 collectBluetoothScanStats()261 private void collectBluetoothScanStats() { 262 mBluetoothStatsRetriever.retrieveBluetoothScanTimes((uid, scanTimeMs) -> { 263 uid = mUidResolver.mapUid(uid); 264 UidStats uidStats = mUidStats.get(uid); 265 if (uidStats == null) { 266 uidStats = new UidStats(); 267 mUidStats.put(uid, uidStats); 268 } 269 uidStats.scanTime += scanTimeMs; 270 }); 271 272 long totalScanTime = 0; 273 for (int i = mUidStats.size() - 1; i >= 0; i--) { 274 UidStats counts = mUidStats.valueAt(i); 275 if (counts.scanTime == 0) { 276 continue; 277 } 278 279 long delta = Math.max(0, counts.scanTime - counts.lastScanTime); 280 counts.lastScanTime = counts.scanTime; 281 counts.scanTime = 0; 282 283 if (delta != 0) { 284 int uid = mUidStats.keyAt(i); 285 long[] stats = mPowerStats.uidStats.get(uid); 286 if (stats == null) { 287 stats = new long[mLayout.getUidStatsArrayLength()]; 288 mPowerStats.uidStats.put(uid, stats); 289 } 290 291 mLayout.setUidScanTime(stats, delta); 292 totalScanTime += delta; 293 } 294 } 295 296 mLayout.setDeviceScanTime(mDeviceStats, totalScanTime); 297 } 298 collectEnergyConsumers()299 private void collectEnergyConsumers() { 300 int voltageMv = mVoltageSupplier.getAsInt(); 301 if (voltageMv <= 0) { 302 Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv 303 + " mV) when querying energy consumers"); 304 return; 305 } 306 307 int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv; 308 mLastVoltageMv = voltageMv; 309 310 long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds); 311 if (energyUws == null) { 312 return; 313 } 314 315 for (int i = energyUws.length - 1; i >= 0; i--) { 316 long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED 317 ? energyUws[i] - mLastConsumedEnergyUws[i] : 0; 318 if (energyDelta < 0) { 319 // Likely, restart of powerstats HAL 320 energyDelta = 0; 321 } 322 mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage)); 323 mLastConsumedEnergyUws[i] = energyUws[i]; 324 } 325 } 326 327 @Override onUidRemoved(int uid)328 protected void onUidRemoved(int uid) { 329 super.onUidRemoved(uid); 330 mUidStats.remove(uid); 331 } 332 } 333