1 /* 2 * Copyright (C) 2018 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.batterysaver; 17 18 import android.os.BatteryManagerInternal; 19 import android.os.SystemClock; 20 import android.util.ArrayMap; 21 import android.util.Slog; 22 import android.util.TimeUtils; 23 24 import com.android.internal.annotations.GuardedBy; 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.server.EventLogTags; 27 import com.android.server.LocalServices; 28 29 import java.io.PrintWriter; 30 import java.text.SimpleDateFormat; 31 import java.util.Date; 32 33 /** 34 * This class keeps track of battery drain rate. 35 * 36 * IMPORTANT: This class shares the power manager lock, which is very low in the lock hierarchy. 37 * Do not call out with the lock held. (Settings provider is okay.) 38 * 39 * TODO: The use of the terms "percent" and "level" in this class is not standard. Fix it. 40 * 41 * Test: 42 atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java 43 */ 44 public class BatterySavingStats { 45 46 private static final String TAG = "BatterySavingStats"; 47 48 private static final boolean DEBUG = BatterySaverPolicy.DEBUG; 49 50 private final Object mLock; 51 52 /** Whether battery saver is on or off. */ 53 interface BatterySaverState { 54 int OFF = 0; 55 int ON = 1; 56 int ADAPTIVE = 2; 57 58 int SHIFT = 0; 59 int BITS = 2; 60 int MASK = (1 << BITS) - 1; 61 fromIndex(int index)62 static int fromIndex(int index) { 63 return (index >> SHIFT) & MASK; 64 } 65 } 66 67 /** Whether the device is interactive (i.e. screen on) or not. */ 68 interface InteractiveState { 69 int NON_INTERACTIVE = 0; 70 int INTERACTIVE = 1; 71 72 int SHIFT = BatterySaverState.SHIFT + BatterySaverState.BITS; 73 int BITS = 1; 74 int MASK = (1 << BITS) - 1; 75 fromIndex(int index)76 static int fromIndex(int index) { 77 return (index >> SHIFT) & MASK; 78 } 79 } 80 81 /** Doze mode. */ 82 interface DozeState { 83 int NOT_DOZING = 0; 84 int LIGHT = 1; 85 int DEEP = 2; 86 87 int SHIFT = InteractiveState.SHIFT + InteractiveState.BITS; 88 int BITS = 2; 89 int MASK = (1 << BITS) - 1; 90 fromIndex(int index)91 static int fromIndex(int index) { 92 return (index >> SHIFT) & MASK; 93 } 94 } 95 96 /** 97 * Various stats in each state. 98 */ 99 static class Stat { 100 public long startTime; 101 public long endTime; 102 103 public int startBatteryLevel; 104 public int endBatteryLevel; 105 106 public int startBatteryPercent; 107 public int endBatteryPercent; 108 109 public long totalTimeMillis; 110 public int totalBatteryDrain; 111 public int totalBatteryDrainPercent; 112 totalMinutes()113 public long totalMinutes() { 114 return totalTimeMillis / 60_000; 115 } 116 drainPerHour()117 public double drainPerHour() { 118 if (totalTimeMillis == 0) { 119 return 0; 120 } 121 return (double) totalBatteryDrain / (totalTimeMillis / (60.0 * 60 * 1000)); 122 } 123 drainPercentPerHour()124 public double drainPercentPerHour() { 125 if (totalTimeMillis == 0) { 126 return 0; 127 } 128 return (double) totalBatteryDrainPercent / (totalTimeMillis / (60.0 * 60 * 1000)); 129 } 130 131 @VisibleForTesting toStringForTest()132 String toStringForTest() { 133 return "{" + totalMinutes() + "m," + totalBatteryDrain + "," 134 + String.format("%.2f", drainPerHour()) + "uA/H," 135 + String.format("%.2f", drainPercentPerHour()) + "%" 136 + "}"; 137 } 138 } 139 140 private BatteryManagerInternal mBatteryManagerInternal; 141 142 private static final int STATE_NOT_INITIALIZED = -1; 143 private static final int STATE_CHARGING = -2; 144 145 /** 146 * Current state, one of STATE_* or values returned by {@link #statesToIndex}. 147 */ 148 @GuardedBy("mLock") 149 private int mCurrentState = STATE_NOT_INITIALIZED; 150 151 /** 152 * Stats in each state. 153 */ 154 @VisibleForTesting 155 @GuardedBy("mLock") 156 final ArrayMap<Integer, Stat> mStats = new ArrayMap<>(); 157 158 @GuardedBy("mLock") 159 private int mBatterySaverEnabledCount = 0; 160 161 @GuardedBy("mLock") 162 private boolean mIsBatterySaverEnabled; 163 164 @GuardedBy("mLock") 165 private long mLastBatterySaverEnabledTime = 0; 166 167 @GuardedBy("mLock") 168 private long mLastBatterySaverDisabledTime = 0; 169 170 /** Visible for unit tests */ 171 @VisibleForTesting BatterySavingStats(Object lock)172 public BatterySavingStats(Object lock) { 173 mLock = lock; 174 mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); 175 } 176 getBatteryManagerInternal()177 private BatteryManagerInternal getBatteryManagerInternal() { 178 if (mBatteryManagerInternal == null) { 179 mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); 180 if (mBatteryManagerInternal == null) { 181 Slog.wtf(TAG, "BatteryManagerInternal not initialized"); 182 } 183 } 184 return mBatteryManagerInternal; 185 } 186 187 /** 188 * Takes a state triplet and generates a state index. 189 */ 190 @VisibleForTesting statesToIndex( int batterySaverState, int interactiveState, int dozeState)191 static int statesToIndex( 192 int batterySaverState, int interactiveState, int dozeState) { 193 int ret = batterySaverState & BatterySaverState.MASK; 194 ret |= (interactiveState & InteractiveState.MASK) << InteractiveState.SHIFT; 195 ret |= (dozeState & DozeState.MASK) << DozeState.SHIFT; 196 return ret; 197 } 198 199 /** 200 * Takes a state index and returns a string for logging. 201 */ 202 @VisibleForTesting stateToString(int state)203 static String stateToString(int state) { 204 switch (state) { 205 case STATE_NOT_INITIALIZED: 206 return "NotInitialized"; 207 case STATE_CHARGING: 208 return "Charging"; 209 } 210 return "BS=" + BatterySaverState.fromIndex(state) 211 + ",I=" + InteractiveState.fromIndex(state) 212 + ",D=" + DozeState.fromIndex(state); 213 } 214 215 /** 216 * @return {@link Stat} fo a given state. 217 */ 218 @VisibleForTesting getStat(int stateIndex)219 Stat getStat(int stateIndex) { 220 synchronized (mLock) { 221 Stat stat = mStats.get(stateIndex); 222 if (stat == null) { 223 stat = new Stat(); 224 mStats.put(stateIndex, stat); 225 } 226 return stat; 227 } 228 } 229 230 /** 231 * @return {@link Stat} fo a given state triplet. 232 */ getStat(int batterySaverState, int interactiveState, int dozeState)233 private Stat getStat(int batterySaverState, int interactiveState, int dozeState) { 234 return getStat(statesToIndex(batterySaverState, interactiveState, dozeState)); 235 } 236 237 @VisibleForTesting injectCurrentTime()238 long injectCurrentTime() { 239 return SystemClock.elapsedRealtime(); 240 } 241 242 @VisibleForTesting injectBatteryLevel()243 int injectBatteryLevel() { 244 final BatteryManagerInternal bmi = getBatteryManagerInternal(); 245 if (bmi == null) { 246 return 0; 247 } 248 return bmi.getBatteryChargeCounter(); 249 } 250 251 @VisibleForTesting injectBatteryPercent()252 int injectBatteryPercent() { 253 final BatteryManagerInternal bmi = getBatteryManagerInternal(); 254 if (bmi == null) { 255 return 0; 256 } 257 return bmi.getBatteryLevel(); 258 } 259 260 /** 261 * Called from the outside whenever any of the states changes, when the device is not plugged 262 * in. 263 */ transitionState(int batterySaverState, int interactiveState, int dozeState)264 public void transitionState(int batterySaverState, int interactiveState, int dozeState) { 265 synchronized (mLock) { 266 final int newState = statesToIndex( 267 batterySaverState, interactiveState, dozeState); 268 transitionStateLocked(newState); 269 } 270 } 271 272 /** 273 * Called from the outside when the device is plugged in. 274 */ startCharging()275 public void startCharging() { 276 synchronized (mLock) { 277 transitionStateLocked(STATE_CHARGING); 278 } 279 } 280 281 @GuardedBy("mLock") transitionStateLocked(int newState)282 private void transitionStateLocked(int newState) { 283 if (mCurrentState == newState) { 284 return; 285 } 286 final long now = injectCurrentTime(); 287 final int batteryLevel = injectBatteryLevel(); 288 final int batteryPercent = injectBatteryPercent(); 289 290 final boolean oldBatterySaverEnabled = 291 BatterySaverState.fromIndex(mCurrentState) != BatterySaverState.OFF; 292 final boolean newBatterySaverEnabled = 293 BatterySaverState.fromIndex(newState) != BatterySaverState.OFF; 294 if (oldBatterySaverEnabled != newBatterySaverEnabled) { 295 mIsBatterySaverEnabled = newBatterySaverEnabled; 296 if (newBatterySaverEnabled) { 297 mBatterySaverEnabledCount++; 298 mLastBatterySaverEnabledTime = injectCurrentTime(); 299 } else { 300 mLastBatterySaverDisabledTime = injectCurrentTime(); 301 } 302 } 303 304 endLastStateLocked(now, batteryLevel, batteryPercent); 305 startNewStateLocked(newState, now, batteryLevel, batteryPercent); 306 } 307 308 @GuardedBy("mLock") endLastStateLocked(long now, int batteryLevel, int batteryPercent)309 private void endLastStateLocked(long now, int batteryLevel, int batteryPercent) { 310 if (mCurrentState < 0) { 311 return; 312 } 313 final Stat stat = getStat(mCurrentState); 314 315 stat.endBatteryLevel = batteryLevel; 316 stat.endBatteryPercent = batteryPercent; 317 stat.endTime = now; 318 319 final long deltaTime = stat.endTime - stat.startTime; 320 final int deltaDrain = stat.startBatteryLevel - stat.endBatteryLevel; 321 final int deltaPercent = stat.startBatteryPercent - stat.endBatteryPercent; 322 323 stat.totalTimeMillis += deltaTime; 324 stat.totalBatteryDrain += deltaDrain; 325 stat.totalBatteryDrainPercent += deltaPercent; 326 327 if (DEBUG) { 328 Slog.d(TAG, "State summary: " + stateToString(mCurrentState) 329 + ": " + (deltaTime / 1_000) + "s " 330 + "Start level: " + stat.startBatteryLevel + "uA " 331 + "End level: " + stat.endBatteryLevel + "uA " 332 + "Start percent: " + stat.startBatteryPercent + "% " 333 + "End percent: " + stat.endBatteryPercent + "% " 334 + "Drain " + deltaDrain + "uA"); 335 } 336 EventLogTags.writeBatterySavingStats( 337 BatterySaverState.fromIndex(mCurrentState), 338 InteractiveState.fromIndex(mCurrentState), 339 DozeState.fromIndex(mCurrentState), 340 deltaTime, 341 deltaDrain, 342 deltaPercent, 343 stat.totalTimeMillis, 344 stat.totalBatteryDrain, 345 stat.totalBatteryDrainPercent); 346 347 } 348 349 @GuardedBy("mLock") startNewStateLocked(int newState, long now, int batteryLevel, int batteryPercent)350 private void startNewStateLocked(int newState, long now, int batteryLevel, int batteryPercent) { 351 if (DEBUG) { 352 Slog.d(TAG, "New state: " + stateToString(newState)); 353 } 354 mCurrentState = newState; 355 356 if (mCurrentState < 0) { 357 return; 358 } 359 360 final Stat stat = getStat(mCurrentState); 361 stat.startBatteryLevel = batteryLevel; 362 stat.startBatteryPercent = batteryPercent; 363 stat.startTime = now; 364 stat.endTime = 0; 365 } 366 dump(PrintWriter pw, String indent)367 public void dump(PrintWriter pw, String indent) { 368 synchronized (mLock) { 369 pw.print(indent); 370 pw.println("Battery saving stats:"); 371 372 indent = indent + " "; 373 374 final long now = System.currentTimeMillis(); 375 final long nowElapsed = injectCurrentTime(); 376 final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 377 378 pw.print(indent); 379 pw.print("Battery Saver is currently: "); 380 pw.println(mIsBatterySaverEnabled ? "ON" : "OFF"); 381 if (mLastBatterySaverEnabledTime > 0) { 382 pw.print(indent); 383 pw.print(" "); 384 pw.print("Last ON time: "); 385 pw.print(sdf.format(new Date(now - nowElapsed + mLastBatterySaverEnabledTime))); 386 pw.print(" "); 387 TimeUtils.formatDuration(mLastBatterySaverEnabledTime, nowElapsed, pw); 388 pw.println(); 389 } 390 391 if (mLastBatterySaverDisabledTime > 0) { 392 pw.print(indent); 393 pw.print(" "); 394 pw.print("Last OFF time: "); 395 pw.print(sdf.format(new Date(now - nowElapsed + mLastBatterySaverDisabledTime))); 396 pw.print(" "); 397 TimeUtils.formatDuration(mLastBatterySaverDisabledTime, nowElapsed, pw); 398 pw.println(); 399 } 400 401 pw.print(indent); 402 pw.print(" "); 403 pw.print("Times enabled: "); 404 pw.println(mBatterySaverEnabledCount); 405 406 pw.println(); 407 408 pw.print(indent); 409 pw.println("Drain stats:"); 410 411 pw.print(indent); 412 pw.println(" Battery saver OFF ON"); 413 dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr", 414 DozeState.NOT_DOZING, "NonDoze"); 415 dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, " Intr", 416 DozeState.NOT_DOZING, " "); 417 418 dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr", 419 DozeState.DEEP, "Deep "); 420 dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, " Intr", 421 DozeState.DEEP, " "); 422 423 dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr", 424 DozeState.LIGHT, "Light "); 425 dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, " Intr", 426 DozeState.LIGHT, " "); 427 } 428 } 429 dumpLineLocked(PrintWriter pw, String indent, int interactiveState, String interactiveLabel, int dozeState, String dozeLabel)430 private void dumpLineLocked(PrintWriter pw, String indent, 431 int interactiveState, String interactiveLabel, 432 int dozeState, String dozeLabel) { 433 pw.print(indent); 434 pw.print(dozeLabel); 435 pw.print(" "); 436 pw.print(interactiveLabel); 437 pw.print(": "); 438 439 final Stat offStat = getStat(BatterySaverState.OFF, interactiveState, dozeState); 440 final Stat onStat = getStat(BatterySaverState.ON, interactiveState, dozeState); 441 442 pw.println(String.format("%6dm %6dmAh(%3d%%) %8.1fmAh/h %6dm %6dmAh(%3d%%) %8.1fmAh/h", 443 offStat.totalMinutes(), 444 offStat.totalBatteryDrain / 1000, 445 offStat.totalBatteryDrainPercent, 446 offStat.drainPerHour() / 1000.0, 447 onStat.totalMinutes(), 448 onStat.totalBatteryDrain / 1000, 449 onStat.totalBatteryDrainPercent, 450 onStat.drainPerHour() / 1000.0)); 451 } 452 } 453