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.IndentingPrintWriter; 21 import android.util.Slog; 22 import android.util.SparseArray; 23 import android.util.TimeUtils; 24 25 import com.android.internal.annotations.GuardedBy; 26 import com.android.internal.annotations.VisibleForTesting; 27 import com.android.server.EventLogTags; 28 import com.android.server.LocalServices; 29 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 /** Whether the device is plugged in or not. */ 97 interface PlugState { 98 int UNPLUGGED = 0; 99 int PLUGGED = 1; 100 101 int SHIFT = DozeState.SHIFT + DozeState.BITS; 102 int BITS = 1; 103 int MASK = (1 << BITS) - 1; 104 fromIndex(int index)105 static int fromIndex(int index) { 106 return (index >> SHIFT) & MASK; 107 } 108 } 109 110 /** 111 * Various stats in each state. 112 */ 113 static class Stat { 114 public long startTime; 115 public long endTime; 116 117 public int startBatteryLevel; 118 public int endBatteryLevel; 119 120 public int startBatteryPercent; 121 public int endBatteryPercent; 122 123 public long totalTimeMillis; 124 public int totalBatteryDrain; 125 public int totalBatteryDrainPercent; 126 totalMinutes()127 public long totalMinutes() { 128 return totalTimeMillis / 60_000; 129 } 130 drainPerHour()131 public double drainPerHour() { 132 if (totalTimeMillis == 0) { 133 return 0; 134 } 135 return (double) totalBatteryDrain / (totalTimeMillis / (60.0 * 60 * 1000)); 136 } 137 drainPercentPerHour()138 public double drainPercentPerHour() { 139 if (totalTimeMillis == 0) { 140 return 0; 141 } 142 return (double) totalBatteryDrainPercent / (totalTimeMillis / (60.0 * 60 * 1000)); 143 } 144 145 @VisibleForTesting toStringForTest()146 String toStringForTest() { 147 return "{" + totalMinutes() + "m," + totalBatteryDrain + "," 148 + String.format("%.2f", drainPerHour()) + "uA/H," 149 + String.format("%.2f", drainPercentPerHour()) + "%" 150 + "}"; 151 } 152 } 153 154 private BatteryManagerInternal mBatteryManagerInternal; 155 156 private static final int STATE_NOT_INITIALIZED = -1; 157 158 /** 159 * Current state, one of STATE_* or values returned by {@link #statesToIndex}. 160 */ 161 @GuardedBy("mLock") 162 private int mCurrentState = STATE_NOT_INITIALIZED; 163 164 /** 165 * Stats in each state. 166 */ 167 @VisibleForTesting 168 @GuardedBy("mLock") 169 final SparseArray<Stat> mStats = new SparseArray<>(); 170 171 @GuardedBy("mLock") 172 private int mBatterySaverEnabledCount = 0; 173 174 @GuardedBy("mLock") 175 private long mLastBatterySaverEnabledTime = 0; 176 177 @GuardedBy("mLock") 178 private long mLastBatterySaverDisabledTime = 0; 179 180 @GuardedBy("mLock") 181 private int mAdaptiveBatterySaverEnabledCount = 0; 182 183 @GuardedBy("mLock") 184 private long mLastAdaptiveBatterySaverEnabledTime = 0; 185 186 @GuardedBy("mLock") 187 private long mLastAdaptiveBatterySaverDisabledTime = 0; 188 189 /** Visible for unit tests */ 190 @VisibleForTesting BatterySavingStats(Object lock)191 public BatterySavingStats(Object lock) { 192 mLock = lock; 193 mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); 194 } 195 getBatteryManagerInternal()196 private BatteryManagerInternal getBatteryManagerInternal() { 197 if (mBatteryManagerInternal == null) { 198 mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); 199 if (mBatteryManagerInternal == null) { 200 Slog.wtf(TAG, "BatteryManagerInternal not initialized"); 201 } 202 } 203 return mBatteryManagerInternal; 204 } 205 206 /** 207 * Takes a state triplet and generates a state index. 208 */ 209 @VisibleForTesting statesToIndex( int batterySaverState, int interactiveState, int dozeState, int plugState)210 static int statesToIndex( 211 int batterySaverState, int interactiveState, int dozeState, int plugState) { 212 int ret = batterySaverState & BatterySaverState.MASK; 213 ret |= (interactiveState & InteractiveState.MASK) << InteractiveState.SHIFT; 214 ret |= (dozeState & DozeState.MASK) << DozeState.SHIFT; 215 ret |= (plugState & PlugState.MASK) << PlugState.SHIFT; 216 return ret; 217 } 218 219 /** 220 * Takes a state index and returns a string for logging. 221 */ 222 @VisibleForTesting stateToString(int state)223 static String stateToString(int state) { 224 switch (state) { 225 case STATE_NOT_INITIALIZED: 226 return "NotInitialized"; 227 } 228 return "BS=" + BatterySaverState.fromIndex(state) 229 + ",I=" + InteractiveState.fromIndex(state) 230 + ",D=" + DozeState.fromIndex(state) 231 + ",P=" + PlugState.fromIndex(state); 232 } 233 234 /** 235 * @return {@link Stat} fo a given state. 236 */ 237 @VisibleForTesting getStat(int stateIndex)238 Stat getStat(int stateIndex) { 239 synchronized (mLock) { 240 Stat stat = mStats.get(stateIndex); 241 if (stat == null) { 242 stat = new Stat(); 243 mStats.put(stateIndex, stat); 244 } 245 return stat; 246 } 247 } 248 249 /** 250 * @return {@link Stat} fo a given state triplet. 251 */ getStat(int batterySaverState, int interactiveState, int dozeState, int plugState)252 private Stat getStat(int batterySaverState, int interactiveState, int dozeState, 253 int plugState) { 254 return getStat(statesToIndex(batterySaverState, interactiveState, dozeState, plugState)); 255 } 256 257 @VisibleForTesting injectCurrentTime()258 long injectCurrentTime() { 259 return SystemClock.elapsedRealtime(); 260 } 261 262 @VisibleForTesting injectBatteryLevel()263 int injectBatteryLevel() { 264 final BatteryManagerInternal bmi = getBatteryManagerInternal(); 265 if (bmi == null) { 266 return 0; 267 } 268 return bmi.getBatteryChargeCounter(); 269 } 270 271 @VisibleForTesting injectBatteryPercent()272 int injectBatteryPercent() { 273 final BatteryManagerInternal bmi = getBatteryManagerInternal(); 274 if (bmi == null) { 275 return 0; 276 } 277 return bmi.getBatteryLevel(); 278 } 279 280 /** 281 * Called from the outside whenever any of the states change. 282 */ transitionState(int batterySaverState, int interactiveState, int dozeState, int plugState)283 void transitionState(int batterySaverState, int interactiveState, int dozeState, 284 int plugState) { 285 synchronized (mLock) { 286 final int newState = statesToIndex( 287 batterySaverState, interactiveState, dozeState, plugState); 288 transitionStateLocked(newState); 289 } 290 } 291 292 @GuardedBy("mLock") transitionStateLocked(int newState)293 private void transitionStateLocked(int newState) { 294 if (mCurrentState == newState) { 295 return; 296 } 297 final long now = injectCurrentTime(); 298 final int batteryLevel = injectBatteryLevel(); 299 final int batteryPercent = injectBatteryPercent(); 300 301 final int oldBatterySaverState = mCurrentState < 0 302 ? BatterySaverState.OFF : BatterySaverState.fromIndex(mCurrentState); 303 final int newBatterySaverState = newState < 0 304 ? BatterySaverState.OFF : BatterySaverState.fromIndex(newState); 305 if (oldBatterySaverState != newBatterySaverState) { 306 switch (newBatterySaverState) { 307 case BatterySaverState.ON: 308 mBatterySaverEnabledCount++; 309 mLastBatterySaverEnabledTime = now; 310 if (oldBatterySaverState == BatterySaverState.ADAPTIVE) { 311 mLastAdaptiveBatterySaverDisabledTime = now; 312 } 313 break; 314 case BatterySaverState.OFF: 315 if (oldBatterySaverState == BatterySaverState.ON) { 316 mLastBatterySaverDisabledTime = now; 317 } else { 318 mLastAdaptiveBatterySaverDisabledTime = now; 319 } 320 break; 321 case BatterySaverState.ADAPTIVE: 322 mAdaptiveBatterySaverEnabledCount++; 323 mLastAdaptiveBatterySaverEnabledTime = now; 324 if (oldBatterySaverState == BatterySaverState.ON) { 325 mLastBatterySaverDisabledTime = now; 326 } 327 break; 328 } 329 } 330 331 endLastStateLocked(now, batteryLevel, batteryPercent); 332 startNewStateLocked(newState, now, batteryLevel, batteryPercent); 333 } 334 335 @GuardedBy("mLock") 336 private void endLastStateLocked(long now, int batteryLevel, int batteryPercent) { 337 if (mCurrentState < 0) { 338 return; 339 } 340 final Stat stat = getStat(mCurrentState); 341 342 stat.endBatteryLevel = batteryLevel; 343 stat.endBatteryPercent = batteryPercent; 344 stat.endTime = now; 345 346 final long deltaTime = stat.endTime - stat.startTime; 347 final int deltaDrain = stat.startBatteryLevel - stat.endBatteryLevel; 348 final int deltaPercent = stat.startBatteryPercent - stat.endBatteryPercent; 349 350 stat.totalTimeMillis += deltaTime; 351 stat.totalBatteryDrain += deltaDrain; 352 stat.totalBatteryDrainPercent += deltaPercent; 353 354 if (DEBUG) { 355 Slog.d(TAG, "State summary: " + stateToString(mCurrentState) 356 + ": " + (deltaTime / 1_000) + "s " 357 + "Start level: " + stat.startBatteryLevel + "uA " 358 + "End level: " + stat.endBatteryLevel + "uA " 359 + "Start percent: " + stat.startBatteryPercent + "% " 360 + "End percent: " + stat.endBatteryPercent + "% " 361 + "Drain " + deltaDrain + "uA"); 362 } 363 EventLogTags.writeBatterySavingStats( 364 BatterySaverState.fromIndex(mCurrentState), 365 InteractiveState.fromIndex(mCurrentState), 366 DozeState.fromIndex(mCurrentState), 367 deltaTime, 368 deltaDrain, 369 deltaPercent, 370 stat.totalTimeMillis, 371 stat.totalBatteryDrain, 372 stat.totalBatteryDrainPercent); 373 374 } 375 376 @GuardedBy("mLock") 377 private void startNewStateLocked(int newState, long now, int batteryLevel, int batteryPercent) { 378 if (DEBUG) { 379 Slog.d(TAG, "New state: " + stateToString(newState)); 380 } 381 mCurrentState = newState; 382 383 if (mCurrentState < 0) { 384 return; 385 } 386 387 final Stat stat = getStat(mCurrentState); 388 stat.startBatteryLevel = batteryLevel; 389 stat.startBatteryPercent = batteryPercent; 390 stat.startTime = now; 391 stat.endTime = 0; 392 } 393 394 public void dump(IndentingPrintWriter pw) { 395 pw.println("Battery saving stats:"); 396 pw.increaseIndent(); 397 398 synchronized (mLock) { 399 final long now = System.currentTimeMillis(); 400 final long nowElapsed = injectCurrentTime(); 401 final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 402 403 pw.print("Battery Saver is currently: "); 404 switch (BatterySaverState.fromIndex(mCurrentState)) { 405 case BatterySaverState.OFF: 406 pw.println("OFF"); 407 break; 408 case BatterySaverState.ON: 409 pw.println("ON"); 410 break; 411 case BatterySaverState.ADAPTIVE: 412 pw.println("ADAPTIVE"); 413 break; 414 } 415 416 pw.increaseIndent(); 417 if (mLastBatterySaverEnabledTime > 0) { 418 pw.print("Last ON time: "); 419 pw.print(sdf.format(new Date(now - nowElapsed + mLastBatterySaverEnabledTime))); 420 pw.print(" "); 421 TimeUtils.formatDuration(mLastBatterySaverEnabledTime, nowElapsed, pw); 422 pw.println(); 423 } 424 425 if (mLastBatterySaverDisabledTime > 0) { 426 pw.print("Last OFF time: "); 427 pw.print(sdf.format(new Date(now - nowElapsed + mLastBatterySaverDisabledTime))); 428 pw.print(" "); 429 TimeUtils.formatDuration(mLastBatterySaverDisabledTime, nowElapsed, pw); 430 pw.println(); 431 } 432 433 pw.print("Times full enabled: "); 434 pw.println(mBatterySaverEnabledCount); 435 436 if (mLastAdaptiveBatterySaverEnabledTime > 0) { 437 pw.print("Last ADAPTIVE ON time: "); 438 pw.print(sdf.format( 439 new Date(now - nowElapsed + mLastAdaptiveBatterySaverEnabledTime))); 440 pw.print(" "); 441 TimeUtils.formatDuration(mLastAdaptiveBatterySaverEnabledTime, nowElapsed, pw); 442 pw.println(); 443 } 444 if (mLastAdaptiveBatterySaverDisabledTime > 0) { 445 pw.print("Last ADAPTIVE OFF time: "); 446 pw.print(sdf.format( 447 new Date(now - nowElapsed + mLastAdaptiveBatterySaverDisabledTime))); 448 pw.print(" "); 449 TimeUtils.formatDuration(mLastAdaptiveBatterySaverDisabledTime, nowElapsed, pw); 450 pw.println(); 451 } 452 pw.print("Times adaptive enabled: "); 453 pw.println(mAdaptiveBatterySaverEnabledCount); 454 455 pw.decreaseIndent(); 456 pw.println(); 457 458 pw.println("Drain stats:"); 459 460 pw.println(" Battery saver OFF ON"); 461 dumpLineLocked(pw, InteractiveState.NON_INTERACTIVE, "NonIntr", 462 DozeState.NOT_DOZING, "NonDoze"); 463 dumpLineLocked(pw, InteractiveState.INTERACTIVE, " Intr", 464 DozeState.NOT_DOZING, " "); 465 466 dumpLineLocked(pw, InteractiveState.NON_INTERACTIVE, "NonIntr", 467 DozeState.DEEP, "Deep "); 468 dumpLineLocked(pw, InteractiveState.INTERACTIVE, " Intr", 469 DozeState.DEEP, " "); 470 471 dumpLineLocked(pw, InteractiveState.NON_INTERACTIVE, "NonIntr", 472 DozeState.LIGHT, "Light "); 473 dumpLineLocked(pw, InteractiveState.INTERACTIVE, " Intr", 474 DozeState.LIGHT, " "); 475 } pw.decreaseIndent()476 pw.decreaseIndent(); 477 } 478 dumpLineLocked(IndentingPrintWriter pw, int interactiveState, String interactiveLabel, int dozeState, String dozeLabel)479 private void dumpLineLocked(IndentingPrintWriter pw, 480 int interactiveState, String interactiveLabel, 481 int dozeState, String dozeLabel) { 482 pw.print(dozeLabel); 483 pw.print(" "); 484 pw.print(interactiveLabel); 485 pw.print(": "); 486 487 final Stat offStat = getStat(BatterySaverState.OFF, interactiveState, dozeState, 488 PlugState.UNPLUGGED); 489 final Stat onStat = getStat(BatterySaverState.ON, interactiveState, dozeState, 490 PlugState.UNPLUGGED); 491 492 pw.println(String.format("%6dm %6dmAh(%3d%%) %8.1fmAh/h %6dm %6dmAh(%3d%%) %8.1fmAh/h", 493 offStat.totalMinutes(), 494 offStat.totalBatteryDrain / 1000, 495 offStat.totalBatteryDrainPercent, 496 offStat.drainPerHour() / 1000.0, 497 onStat.totalMinutes(), 498 onStat.totalBatteryDrain / 1000, 499 onStat.totalBatteryDrainPercent, 500 onStat.drainPerHour() / 1000.0)); 501 } 502 } 503