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