1 /* 2 * Copyright (C) 2020 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 com.android.server.power.stats; 18 19 import android.content.Context; 20 import android.hardware.SensorManager; 21 import android.os.BatteryConsumer; 22 import android.os.BatteryStats; 23 import android.os.BatteryUsageStats; 24 import android.os.BatteryUsageStatsQuery; 25 import android.os.Process; 26 import android.os.UidBatteryConsumer; 27 import android.util.Log; 28 import android.util.Slog; 29 import android.util.SparseArray; 30 import android.util.SparseBooleanArray; 31 32 import com.android.internal.os.Clock; 33 import com.android.internal.os.CpuScalingPolicies; 34 import com.android.internal.os.PowerProfile; 35 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.List; 39 40 /** 41 * Uses accumulated battery stats data and PowerCalculators to produce power 42 * usage data attributed to subsystems and UIDs. 43 */ 44 public class BatteryUsageStatsProvider { 45 private static final String TAG = "BatteryUsageStatsProv"; 46 private final Context mContext; 47 private final SparseBooleanArray mPowerStatsExporterEnabled = new SparseBooleanArray(); 48 private final PowerStatsExporter mPowerStatsExporter; 49 private final PowerStatsStore mPowerStatsStore; 50 private final PowerProfile mPowerProfile; 51 private final CpuScalingPolicies mCpuScalingPolicies; 52 private final Clock mClock; 53 private final Object mLock = new Object(); 54 private List<PowerCalculator> mPowerCalculators; 55 BatteryUsageStatsProvider(Context context, PowerStatsExporter powerStatsExporter, PowerProfile powerProfile, CpuScalingPolicies cpuScalingPolicies, PowerStatsStore powerStatsStore, Clock clock)56 public BatteryUsageStatsProvider(Context context, 57 PowerStatsExporter powerStatsExporter, 58 PowerProfile powerProfile, CpuScalingPolicies cpuScalingPolicies, 59 PowerStatsStore powerStatsStore, Clock clock) { 60 mContext = context; 61 mPowerStatsExporter = powerStatsExporter; 62 mPowerStatsStore = powerStatsStore; 63 mPowerProfile = powerProfile; 64 mCpuScalingPolicies = cpuScalingPolicies; 65 mClock = clock; 66 } 67 getPowerCalculators()68 private List<PowerCalculator> getPowerCalculators() { 69 synchronized (mLock) { 70 if (mPowerCalculators == null) { 71 mPowerCalculators = new ArrayList<>(); 72 73 // Power calculators are applied in the order of registration 74 mPowerCalculators.add(new BatteryChargeCalculator()); 75 if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)) { 76 mPowerCalculators.add( 77 new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile)); 78 } 79 mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile)); 80 mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile)); 81 if (!BatteryStats.checkWifiOnly(mContext)) { 82 if (!mPowerStatsExporterEnabled.get( 83 BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) { 84 mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile)); 85 } 86 if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_PHONE)) { 87 mPowerCalculators.add(new PhonePowerCalculator(mPowerProfile)); 88 } 89 } 90 if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_WIFI)) { 91 mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile)); 92 } 93 if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) { 94 mPowerCalculators.add(new BluetoothPowerCalculator(mPowerProfile)); 95 } 96 mPowerCalculators.add(new SensorPowerCalculator( 97 mContext.getSystemService(SensorManager.class))); 98 if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)) { 99 mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile)); 100 } 101 if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_CAMERA)) { 102 mPowerCalculators.add(new CameraPowerCalculator(mPowerProfile)); 103 } 104 if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) { 105 mPowerCalculators.add(new FlashlightPowerCalculator(mPowerProfile)); 106 } 107 if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_AUDIO)) { 108 mPowerCalculators.add(new AudioPowerCalculator(mPowerProfile)); 109 } 110 if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_VIDEO)) { 111 mPowerCalculators.add(new VideoPowerCalculator(mPowerProfile)); 112 } 113 mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile)); 114 mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile)); 115 mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile)); 116 mPowerCalculators.add(new CustomEnergyConsumerPowerCalculator(mPowerProfile)); 117 mPowerCalculators.add(new UserPowerCalculator()); 118 119 if (!com.android.server.power.optimization.Flags.disableSystemServicePowerAttr()) { 120 // It is important that SystemServicePowerCalculator be applied last, 121 // because it re-attributes some of the power estimated by the other 122 // calculators. 123 mPowerCalculators.add( 124 new SystemServicePowerCalculator(mCpuScalingPolicies, mPowerProfile)); 125 } 126 } 127 } 128 return mPowerCalculators; 129 } 130 131 /** 132 * Returns true if the last update was too long ago for the tolerances specified 133 * by the supplied queries. 134 */ shouldUpdateStats(List<BatteryUsageStatsQuery> queries, long elapsedRealtime, long lastUpdateTimeStampMs)135 public static boolean shouldUpdateStats(List<BatteryUsageStatsQuery> queries, 136 long elapsedRealtime, long lastUpdateTimeStampMs) { 137 long allowableStatsAge = Long.MAX_VALUE; 138 for (int i = queries.size() - 1; i >= 0; i--) { 139 BatteryUsageStatsQuery query = queries.get(i); 140 allowableStatsAge = Math.min(allowableStatsAge, query.getMaxStatsAge()); 141 } 142 143 return elapsedRealtime - lastUpdateTimeStampMs > allowableStatsAge; 144 } 145 146 /** 147 * Returns snapshots of battery attribution data, one per supplied query. 148 */ getBatteryUsageStats(BatteryStatsImpl stats, List<BatteryUsageStatsQuery> queries)149 public List<BatteryUsageStats> getBatteryUsageStats(BatteryStatsImpl stats, 150 List<BatteryUsageStatsQuery> queries) { 151 ArrayList<BatteryUsageStats> results = new ArrayList<>(queries.size()); 152 synchronized (stats) { 153 stats.prepareForDumpLocked(); 154 } 155 final long currentTimeMillis = mClock.currentTimeMillis(); 156 for (int i = 0; i < queries.size(); i++) { 157 results.add(getBatteryUsageStats(stats, queries.get(i), currentTimeMillis)); 158 } 159 160 return results; 161 } 162 163 /** 164 * Returns a snapshot of battery attribution data. 165 */ getBatteryUsageStats(BatteryStatsImpl stats, BatteryUsageStatsQuery query)166 public BatteryUsageStats getBatteryUsageStats(BatteryStatsImpl stats, 167 BatteryUsageStatsQuery query) { 168 return getBatteryUsageStats(stats, query, mClock.currentTimeMillis()); 169 } 170 getBatteryUsageStats(BatteryStatsImpl stats, BatteryUsageStatsQuery query, long currentTimeMs)171 private BatteryUsageStats getBatteryUsageStats(BatteryStatsImpl stats, 172 BatteryUsageStatsQuery query, long currentTimeMs) { 173 if (query.getToTimestamp() == 0) { 174 return getCurrentBatteryUsageStats(stats, query, currentTimeMs); 175 } else { 176 return getAggregatedBatteryUsageStats(stats, query); 177 } 178 } 179 getCurrentBatteryUsageStats(BatteryStatsImpl stats, BatteryUsageStatsQuery query, long currentTimeMs)180 private BatteryUsageStats getCurrentBatteryUsageStats(BatteryStatsImpl stats, 181 BatteryUsageStatsQuery query, long currentTimeMs) { 182 final long realtimeUs = mClock.elapsedRealtime() * 1000; 183 final long uptimeUs = mClock.uptimeMillis() * 1000; 184 185 final boolean includePowerModels = (query.getFlags() 186 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0; 187 final boolean includeProcessStateData = ((query.getFlags() 188 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0) 189 && stats.isProcessStateDataAvailable(); 190 final boolean includeVirtualUids = ((query.getFlags() 191 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS) != 0); 192 final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold(); 193 194 final BatteryUsageStats.Builder batteryUsageStatsBuilder; 195 long monotonicStartTime, monotonicEndTime; 196 synchronized (stats) { 197 monotonicStartTime = stats.getMonotonicStartTime(); 198 monotonicEndTime = stats.getMonotonicEndTime(); 199 200 batteryUsageStatsBuilder = new BatteryUsageStats.Builder( 201 stats.getCustomEnergyConsumerNames(), includePowerModels, 202 includeProcessStateData, minConsumedPowerThreshold); 203 204 // TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration 205 // of batteryUsageStats sessions to wall-clock adjustments 206 batteryUsageStatsBuilder.setStatsStartTimestamp(stats.getStartClockTime()); 207 batteryUsageStatsBuilder.setStatsEndTimestamp(currentTimeMs); 208 SparseArray<? extends BatteryStats.Uid> uidStats = stats.getUidStats(); 209 for (int i = uidStats.size() - 1; i >= 0; i--) { 210 final BatteryStats.Uid uid = uidStats.valueAt(i); 211 if (!includeVirtualUids && uid.getUid() == Process.SDK_SANDBOX_VIRTUAL_UID) { 212 continue; 213 } 214 215 batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid) 216 .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_BACKGROUND, 217 getProcessBackgroundTimeMs(uid, realtimeUs)) 218 .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND, 219 getProcessForegroundTimeMs(uid, realtimeUs)) 220 .setTimeInProcessStateMs( 221 UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 222 getProcessForegroundServiceTimeMs(uid, realtimeUs)); 223 } 224 225 final int[] powerComponents = query.getPowerComponents(); 226 final List<PowerCalculator> powerCalculators = getPowerCalculators(); 227 for (int i = 0, count = powerCalculators.size(); i < count; i++) { 228 PowerCalculator powerCalculator = powerCalculators.get(i); 229 if (powerComponents != null) { 230 boolean include = false; 231 for (int powerComponent : powerComponents) { 232 if (powerCalculator.isPowerComponentSupported(powerComponent)) { 233 include = true; 234 break; 235 } 236 } 237 if (!include) { 238 continue; 239 } 240 } 241 powerCalculator.calculate(batteryUsageStatsBuilder, stats, realtimeUs, uptimeUs, 242 query); 243 } 244 245 if ((query.getFlags() 246 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY) != 0) { 247 batteryUsageStatsBuilder.setBatteryHistory(stats.copyHistory()); 248 } 249 } 250 251 if (mPowerStatsExporterEnabled.indexOfValue(true) >= 0) { 252 mPowerStatsExporter.exportAggregatedPowerStats(batteryUsageStatsBuilder, 253 monotonicStartTime, monotonicEndTime); 254 } 255 256 BatteryUsageStats batteryUsageStats = batteryUsageStatsBuilder.build(); 257 if (includeProcessStateData) { 258 verify(batteryUsageStats); 259 } 260 return batteryUsageStats; 261 } 262 263 // STOPSHIP(b/229906525): remove verification before shipping 264 private static boolean sErrorReported; verify(BatteryUsageStats stats)265 private void verify(BatteryUsageStats stats) { 266 if (sErrorReported) { 267 return; 268 } 269 270 final double precision = 2.0; // Allow rounding errors up to 2 mAh 271 final int[] components = 272 {BatteryConsumer.POWER_COMPONENT_CPU, 273 BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, 274 BatteryConsumer.POWER_COMPONENT_WIFI, 275 BatteryConsumer.POWER_COMPONENT_BLUETOOTH}; 276 final int[] states = 277 {BatteryConsumer.PROCESS_STATE_FOREGROUND, 278 BatteryConsumer.PROCESS_STATE_BACKGROUND, 279 BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 280 BatteryConsumer.PROCESS_STATE_CACHED}; 281 for (UidBatteryConsumer ubc : stats.getUidBatteryConsumers()) { 282 for (int component : components) { 283 double consumedPower = ubc.getConsumedPower(ubc.getKey(component)); 284 double sumStates = 0; 285 for (int state : states) { 286 sumStates += ubc.getConsumedPower(ubc.getKey(component, state)); 287 } 288 if (sumStates > consumedPower + precision) { 289 String error = "Sum of states exceeds total. UID = " + ubc.getUid() + " " 290 + BatteryConsumer.powerComponentIdToString(component) 291 + " total = " + consumedPower + " states = " + sumStates; 292 if (!sErrorReported) { 293 Slog.wtf(TAG, error); 294 sErrorReported = true; 295 } else { 296 Slog.e(TAG, error); 297 } 298 return; 299 } 300 } 301 } 302 } 303 getProcessForegroundTimeMs(BatteryStats.Uid uid, long realtimeUs)304 private long getProcessForegroundTimeMs(BatteryStats.Uid uid, long realtimeUs) { 305 final long topStateDurationUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_TOP, 306 realtimeUs, BatteryStats.STATS_SINCE_CHARGED); 307 long foregroundActivityDurationUs = 0; 308 final BatteryStats.Timer foregroundActivityTimer = uid.getForegroundActivityTimer(); 309 if (foregroundActivityTimer != null) { 310 foregroundActivityDurationUs = foregroundActivityTimer.getTotalTimeLocked(realtimeUs, 311 BatteryStats.STATS_SINCE_CHARGED); 312 } 313 314 // Use the min value of STATE_TOP time and foreground activity time, since both of these 315 // times are imprecise 316 long totalForegroundDurationUs = Math.min(topStateDurationUs, foregroundActivityDurationUs); 317 318 totalForegroundDurationUs += uid.getProcessStateTime( 319 BatteryStats.Uid.PROCESS_STATE_FOREGROUND, realtimeUs, 320 BatteryStats.STATS_SINCE_CHARGED); 321 322 return totalForegroundDurationUs / 1000; 323 } 324 getProcessBackgroundTimeMs(BatteryStats.Uid uid, long realtimeUs)325 private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, long realtimeUs) { 326 return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 327 realtimeUs, BatteryStats.STATS_SINCE_CHARGED) 328 / 1000; 329 } 330 getProcessForegroundServiceTimeMs(BatteryStats.Uid uid, long realtimeUs)331 private long getProcessForegroundServiceTimeMs(BatteryStats.Uid uid, long realtimeUs) { 332 return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE, 333 realtimeUs, BatteryStats.STATS_SINCE_CHARGED) 334 / 1000; 335 } 336 getAggregatedBatteryUsageStats(BatteryStatsImpl stats, BatteryUsageStatsQuery query)337 private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryStatsImpl stats, 338 BatteryUsageStatsQuery query) { 339 final boolean includePowerModels = (query.getFlags() 340 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0; 341 final boolean includeProcessStateData = ((query.getFlags() 342 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0) 343 && stats.isProcessStateDataAvailable(); 344 final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold(); 345 346 final String[] customEnergyConsumerNames = stats.getCustomEnergyConsumerNames(); 347 final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( 348 customEnergyConsumerNames, includePowerModels, includeProcessStateData, 349 minConsumedPowerThreshold); 350 if (mPowerStatsStore == null) { 351 Log.e(TAG, "PowerStatsStore is unavailable"); 352 return builder.build(); 353 } 354 355 List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents(); 356 for (PowerStatsSpan.Metadata spanMetadata : toc) { 357 if (!spanMetadata.getSections().contains(BatteryUsageStatsSection.TYPE)) { 358 continue; 359 } 360 361 // BatteryUsageStatsQuery is expressed in terms of wall-clock time range for the 362 // session end time. 363 // 364 // The following algorithm is correct when there is only one time frame in the span. 365 // When the wall-clock time is adjusted in the middle of an stats span, 366 // constraining it by wall-clock time becomes ambiguous. In this case, the algorithm 367 // only covers some situations, but not others. When using the resulting data for 368 // analysis, we should always pay attention to the full set of included timeframes. 369 // TODO(b/298459065): switch to monotonic clock 370 long minTime = Long.MAX_VALUE; 371 long maxTime = 0; 372 for (PowerStatsSpan.TimeFrame timeFrame : spanMetadata.getTimeFrames()) { 373 long spanEndTime = timeFrame.startTime + timeFrame.duration; 374 minTime = Math.min(minTime, spanEndTime); 375 maxTime = Math.max(maxTime, spanEndTime); 376 } 377 378 // Per BatteryUsageStatsQuery API, the "from" timestamp is *exclusive*, 379 // while the "to" timestamp is *inclusive*. 380 boolean isInRange = 381 (query.getFromTimestamp() == 0 || minTime > query.getFromTimestamp()) 382 && (query.getToTimestamp() == 0 || maxTime <= query.getToTimestamp()); 383 if (!isInRange) { 384 continue; 385 } 386 387 PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan( 388 spanMetadata.getId(), BatteryUsageStatsSection.TYPE); 389 if (powerStatsSpan == null) { 390 continue; 391 } 392 393 for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) { 394 BatteryUsageStats snapshot = 395 ((BatteryUsageStatsSection) section).getBatteryUsageStats(); 396 if (!Arrays.equals(snapshot.getCustomPowerComponentNames(), 397 customEnergyConsumerNames)) { 398 Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different " 399 + "custom power components: " 400 + Arrays.toString(snapshot.getCustomPowerComponentNames())); 401 continue; 402 } 403 404 if (includeProcessStateData && !snapshot.isProcessStateDataIncluded()) { 405 Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which " 406 + " does not include process state data"); 407 continue; 408 } 409 410 builder.add(snapshot); 411 } 412 } 413 return builder.build(); 414 } 415 416 /** 417 * Specify whether PowerStats based attribution is supported for the specified component. 418 */ setPowerStatsExporterEnabled(int powerComponentId, boolean enabled)419 public void setPowerStatsExporterEnabled(int powerComponentId, boolean enabled) { 420 mPowerStatsExporterEnabled.put(powerComponentId, enabled); 421 } 422 } 423