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