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