Home | History | Annotate | Download | only in batterysaver
      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 
     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 
     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 
     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 
    117         public long totalMinutes() {
    118             return totalTimeMillis / 60_000;
    119         }
    120 
    121         public double drainPerHour() {
    122             if (totalTimeMillis == 0) {
    123                 return 0;
    124             }
    125             return (double) totalBatteryDrain / (totalTimeMillis / (60.0 * 60 * 1000));
    126         }
    127 
    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
    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
    183     public BatterySavingStats(Object lock, MetricsLogger metricsLogger) {
    184         mLock = lock;
    185         mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
    186         mMetricsLogger = metricsLogger;
    187     }
    188 
    189     public BatterySavingStats(Object lock) {
    190         this(lock, new MetricsLogger());
    191     }
    192 
    193     public void setSendTronLog(boolean send) {
    194         synchronized (mLock) {
    195             mSendTronLog = send;
    196         }
    197     }
    198 
    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
    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
    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
    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      */
    255     private Stat getStat(int batterySaverState, int interactiveState, int dozeState) {
    256         return getStat(statesToIndex(batterySaverState, interactiveState, dozeState));
    257     }
    258 
    259     @VisibleForTesting
    260     long injectCurrentTime() {
    261         return SystemClock.elapsedRealtime();
    262     }
    263 
    264     @VisibleForTesting
    265     int injectBatteryLevel() {
    266         final BatteryManagerInternal bmi = getBatteryManagerInternal();
    267         if (bmi == null) {
    268             return 0;
    269         }
    270         return bmi.getBatteryChargeCounter();
    271     }
    272 
    273     @VisibleForTesting
    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      */
    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      */
    297     public void startCharging() {
    298         synchronized (mLock) {
    299             transitionStateLocked(STATE_CHARGING);
    300         }
    301     }
    302 
    303     @GuardedBy("mLock")
    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")
    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")
    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 
    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 
    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 
    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 
    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