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.content.pm.PackageManager; 19 import android.hardware.power.stats.EnergyConsumerType; 20 import android.net.NetworkStats; 21 import android.net.wifi.WifiManager; 22 import android.os.BatteryConsumer; 23 import android.os.Handler; 24 import android.os.PersistableBundle; 25 import android.os.connectivity.WifiActivityEnergyInfo; 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.TimeUnit; 36 import java.util.function.IntSupplier; 37 import java.util.function.Supplier; 38 39 public class WifiPowerStatsCollector extends PowerStatsCollector { 40 private static final String TAG = "WifiPowerStatsCollector"; 41 42 private static final long WIFI_ACTIVITY_REQUEST_TIMEOUT = 20000; 43 44 private static final long ENERGY_UNSPECIFIED = -1; 45 46 interface WifiStatsRetriever { 47 interface Callback { onWifiScanTime(int uid, long scanTimeMs, long batchScanTimeMs)48 void onWifiScanTime(int uid, long scanTimeMs, long batchScanTimeMs); 49 } 50 retrieveWifiScanTimes(Callback callback)51 void retrieveWifiScanTimes(Callback callback); getWifiActiveDuration()52 long getWifiActiveDuration(); 53 } 54 55 interface Injector { getHandler()56 Handler getHandler(); getClock()57 Clock getClock(); getUidResolver()58 PowerStatsUidResolver getUidResolver(); getPowerStatsCollectionThrottlePeriod(String powerComponentName)59 long getPowerStatsCollectionThrottlePeriod(String powerComponentName); getPackageManager()60 PackageManager getPackageManager(); getConsumedEnergyRetriever()61 ConsumedEnergyRetriever getConsumedEnergyRetriever(); getVoltageSupplier()62 IntSupplier getVoltageSupplier(); getWifiNetworkStatsSupplier()63 Supplier<NetworkStats> getWifiNetworkStatsSupplier(); getWifiManager()64 WifiManager getWifiManager(); getWifiStatsRetriever()65 WifiStatsRetriever getWifiStatsRetriever(); 66 } 67 68 private final Injector mInjector; 69 70 private WifiPowerStatsLayout mLayout; 71 private boolean mIsInitialized; 72 private boolean mPowerReportingSupported; 73 74 private PowerStats mPowerStats; 75 private long[] mDeviceStats; 76 private volatile WifiManager mWifiManager; 77 private volatile Supplier<NetworkStats> mNetworkStatsSupplier; 78 private volatile WifiStatsRetriever mWifiStatsRetriever; 79 private ConsumedEnergyRetriever mConsumedEnergyRetriever; 80 private IntSupplier mVoltageSupplier; 81 private int[] mEnergyConsumerIds = new int[0]; 82 private WifiActivityEnergyInfo mLastWifiActivityInfo = 83 new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0); 84 private NetworkStats mLastNetworkStats; 85 private long[] mLastConsumedEnergyUws; 86 private int mLastVoltageMv; 87 88 private static class WifiScanTimes { 89 public long basicScanTimeMs; 90 public long batchedScanTimeMs; 91 } 92 private final WifiScanTimes mScanTimes = new WifiScanTimes(); 93 private final SparseArray<WifiScanTimes> mLastScanTimes = new SparseArray<>(); 94 private long mLastWifiActiveDuration; 95 WifiPowerStatsCollector(Injector injector)96 WifiPowerStatsCollector(Injector injector) { 97 super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod( 98 BatteryConsumer.powerComponentIdToString( 99 BatteryConsumer.POWER_COMPONENT_WIFI)), 100 injector.getUidResolver(), injector.getClock()); 101 mInjector = injector; 102 } 103 104 @Override setEnabled(boolean enabled)105 public void setEnabled(boolean enabled) { 106 if (enabled) { 107 PackageManager packageManager = mInjector.getPackageManager(); 108 super.setEnabled(packageManager != null 109 && packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)); 110 } else { 111 super.setEnabled(false); 112 } 113 } 114 ensureInitialized()115 private boolean ensureInitialized() { 116 if (mIsInitialized) { 117 return true; 118 } 119 120 if (!isEnabled()) { 121 return false; 122 } 123 124 mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever(); 125 mVoltageSupplier = mInjector.getVoltageSupplier(); 126 mWifiManager = mInjector.getWifiManager(); 127 mNetworkStatsSupplier = mInjector.getWifiNetworkStatsSupplier(); 128 mWifiStatsRetriever = mInjector.getWifiStatsRetriever(); 129 mPowerReportingSupported = 130 mWifiManager != null && mWifiManager.isEnhancedPowerReportingSupported(); 131 132 mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI); 133 mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length]; 134 Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED); 135 136 mLayout = new WifiPowerStatsLayout(); 137 mLayout.addDeviceWifiActivity(mPowerReportingSupported); 138 mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length); 139 mLayout.addUidNetworkStats(); 140 mLayout.addDeviceSectionUsageDuration(); 141 mLayout.addDeviceSectionPowerEstimate(); 142 mLayout.addUidSectionPowerEstimate(); 143 144 PersistableBundle extras = new PersistableBundle(); 145 mLayout.toExtras(extras); 146 PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor( 147 BatteryConsumer.POWER_COMPONENT_WIFI, mLayout.getDeviceStatsArrayLength(), 148 null, 0, mLayout.getUidStatsArrayLength(), 149 extras); 150 mPowerStats = new PowerStats(powerStatsDescriptor); 151 mDeviceStats = mPowerStats.stats; 152 153 mIsInitialized = true; 154 return true; 155 } 156 157 @Override collectStats()158 protected PowerStats collectStats() { 159 if (!ensureInitialized()) { 160 return null; 161 } 162 163 if (mPowerReportingSupported) { 164 collectWifiActivityInfo(); 165 } else { 166 collectWifiActivityStats(); 167 } 168 collectNetworkStats(); 169 collectWifiScanTime(); 170 171 if (mEnergyConsumerIds.length != 0) { 172 collectEnergyConsumers(); 173 } 174 175 return mPowerStats; 176 } 177 collectWifiActivityInfo()178 private void collectWifiActivityInfo() { 179 CompletableFuture<WifiActivityEnergyInfo> immediateFuture = new CompletableFuture<>(); 180 mWifiManager.getWifiActivityEnergyInfoAsync(Runnable::run, 181 immediateFuture::complete); 182 183 WifiActivityEnergyInfo activityInfo; 184 try { 185 activityInfo = immediateFuture.get(WIFI_ACTIVITY_REQUEST_TIMEOUT, 186 TimeUnit.MILLISECONDS); 187 } catch (Exception e) { 188 Slog.e(TAG, "Cannot acquire WifiActivityEnergyInfo", e); 189 activityInfo = null; 190 } 191 192 if (activityInfo == null) { 193 return; 194 } 195 196 long rxDuration = activityInfo.getControllerRxDurationMillis() 197 - mLastWifiActivityInfo.getControllerRxDurationMillis(); 198 long txDuration = activityInfo.getControllerTxDurationMillis() 199 - mLastWifiActivityInfo.getControllerTxDurationMillis(); 200 long scanDuration = activityInfo.getControllerScanDurationMillis() 201 - mLastWifiActivityInfo.getControllerScanDurationMillis(); 202 long idleDuration = activityInfo.getControllerIdleDurationMillis() 203 - mLastWifiActivityInfo.getControllerIdleDurationMillis(); 204 205 mLayout.setDeviceRxTime(mDeviceStats, rxDuration); 206 mLayout.setDeviceTxTime(mDeviceStats, txDuration); 207 mLayout.setDeviceScanTime(mDeviceStats, scanDuration); 208 mLayout.setDeviceIdleTime(mDeviceStats, idleDuration); 209 210 mPowerStats.durationMs = rxDuration + txDuration + scanDuration + idleDuration; 211 212 mLastWifiActivityInfo = activityInfo; 213 } 214 collectWifiActivityStats()215 private void collectWifiActivityStats() { 216 long duration = mWifiStatsRetriever.getWifiActiveDuration(); 217 mLayout.setDeviceActiveTime(mDeviceStats, Math.max(0, duration - mLastWifiActiveDuration)); 218 mLastWifiActiveDuration = duration; 219 mPowerStats.durationMs = duration; 220 } 221 collectNetworkStats()222 private void collectNetworkStats() { 223 mPowerStats.uidStats.clear(); 224 225 NetworkStats networkStats = mNetworkStatsSupplier.get(); 226 if (networkStats == null) { 227 return; 228 } 229 230 List<BatteryStatsImpl.NetworkStatsDelta> delta = 231 BatteryStatsImpl.computeDelta(networkStats, mLastNetworkStats); 232 mLastNetworkStats = networkStats; 233 for (int i = delta.size() - 1; i >= 0; i--) { 234 BatteryStatsImpl.NetworkStatsDelta uidDelta = delta.get(i); 235 long rxBytes = uidDelta.getRxBytes(); 236 long txBytes = uidDelta.getTxBytes(); 237 long rxPackets = uidDelta.getRxPackets(); 238 long txPackets = uidDelta.getTxPackets(); 239 if (rxBytes == 0 && txBytes == 0 && rxPackets == 0 && txPackets == 0) { 240 continue; 241 } 242 243 int uid = mUidResolver.mapUid(uidDelta.getUid()); 244 long[] stats = mPowerStats.uidStats.get(uid); 245 if (stats == null) { 246 stats = new long[mLayout.getUidStatsArrayLength()]; 247 mPowerStats.uidStats.put(uid, stats); 248 mLayout.setUidRxBytes(stats, rxBytes); 249 mLayout.setUidTxBytes(stats, txBytes); 250 mLayout.setUidRxPackets(stats, rxPackets); 251 mLayout.setUidTxPackets(stats, txPackets); 252 } else { 253 mLayout.setUidRxBytes(stats, mLayout.getUidRxBytes(stats) + rxBytes); 254 mLayout.setUidTxBytes(stats, mLayout.getUidTxBytes(stats) + txBytes); 255 mLayout.setUidRxPackets(stats, mLayout.getUidRxPackets(stats) + rxPackets); 256 mLayout.setUidTxPackets(stats, mLayout.getUidTxPackets(stats) + txPackets); 257 } 258 } 259 } 260 collectWifiScanTime()261 private void collectWifiScanTime() { 262 mScanTimes.basicScanTimeMs = 0; 263 mScanTimes.batchedScanTimeMs = 0; 264 mWifiStatsRetriever.retrieveWifiScanTimes((uid, scanTimeMs, batchScanTimeMs) -> { 265 WifiScanTimes lastScanTimes = mLastScanTimes.get(uid); 266 if (lastScanTimes == null) { 267 lastScanTimes = new WifiScanTimes(); 268 mLastScanTimes.put(uid, lastScanTimes); 269 } 270 271 long scanTimeDelta = Math.max(0, scanTimeMs - lastScanTimes.basicScanTimeMs); 272 long batchScanTimeDelta = Math.max(0, 273 batchScanTimeMs - lastScanTimes.batchedScanTimeMs); 274 if (scanTimeDelta != 0 || batchScanTimeDelta != 0) { 275 mScanTimes.basicScanTimeMs += scanTimeDelta; 276 mScanTimes.batchedScanTimeMs += batchScanTimeDelta; 277 uid = mUidResolver.mapUid(uid); 278 long[] stats = mPowerStats.uidStats.get(uid); 279 if (stats == null) { 280 stats = new long[mLayout.getUidStatsArrayLength()]; 281 mPowerStats.uidStats.put(uid, stats); 282 mLayout.setUidScanTime(stats, scanTimeDelta); 283 mLayout.setUidBatchScanTime(stats, batchScanTimeDelta); 284 } else { 285 mLayout.setUidScanTime(stats, mLayout.getUidScanTime(stats) + scanTimeDelta); 286 mLayout.setUidBatchScanTime(stats, 287 mLayout.getUidBatchedScanTime(stats) + batchScanTimeDelta); 288 } 289 } 290 lastScanTimes.basicScanTimeMs = scanTimeMs; 291 lastScanTimes.batchedScanTimeMs = batchScanTimeMs; 292 }); 293 294 mLayout.setDeviceBasicScanTime(mDeviceStats, mScanTimes.basicScanTimeMs); 295 mLayout.setDeviceBatchedScanTime(mDeviceStats, mScanTimes.batchedScanTimeMs); 296 } 297 collectEnergyConsumers()298 private void collectEnergyConsumers() { 299 int voltageMv = mVoltageSupplier.getAsInt(); 300 if (voltageMv <= 0) { 301 Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv 302 + " mV) when querying energy consumers"); 303 return; 304 } 305 306 int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv; 307 mLastVoltageMv = voltageMv; 308 309 long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds); 310 if (energyUws == null) { 311 return; 312 } 313 314 for (int i = energyUws.length - 1; i >= 0; i--) { 315 long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED 316 ? energyUws[i] - mLastConsumedEnergyUws[i] : 0; 317 if (energyDelta < 0) { 318 // Likely, restart of powerstats HAL 319 energyDelta = 0; 320 } 321 mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage)); 322 mLastConsumedEnergyUws[i] = energyUws[i]; 323 } 324 } 325 326 @Override onUidRemoved(int uid)327 protected void onUidRemoved(int uid) { 328 super.onUidRemoved(uid); 329 mLastScanTimes.remove(uid); 330 } 331 } 332