blob: b0b07ea767f6b26ed8761f2a5e333a5e0359ad2e [file] [log] [blame]
Makoto Onuki076218b2018-01-26 10:26:36 -08001/*
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 */
16package com.android.server.power.batterysaver;
17
18import android.os.BatteryManagerInternal;
19import android.os.SystemClock;
20import android.util.ArrayMap;
21import android.util.Slog;
22
23import com.android.internal.annotations.GuardedBy;
24import com.android.internal.annotations.VisibleForTesting;
Makoto Onuki04e9dc92018-01-29 13:09:45 -080025import com.android.internal.logging.MetricsLogger;
Makoto Onuki076218b2018-01-26 10:26:36 -080026import com.android.server.EventLogTags;
27import com.android.server.LocalServices;
28import com.android.server.power.BatterySaverPolicy;
29
30import java.io.PrintWriter;
31
32/**
33 * This class keeps track of battery drain rate.
34 *
Makoto Onuki472d8e32018-01-31 12:37:15 -080035 * TODO: The use of the terms "percent" and "level" in this class is not standard. Fix it.
36 *
Makoto Onuki076218b2018-01-26 10:26:36 -080037 * Test:
38 atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java
39 */
40public class BatterySavingStats {
41
42 private static final String TAG = "BatterySavingStats";
43
44 private static final boolean DEBUG = BatterySaverPolicy.DEBUG;
45
46 private final Object mLock = new Object();
47
48 /** Whether battery saver is on or off. */
49 interface BatterySaverState {
50 int OFF = 0;
51 int ON = 1;
52
53 int SHIFT = 0;
54 int BITS = 1;
55 int MASK = (1 << BITS) - 1;
56
57 static int fromIndex(int index) {
58 return (index >> SHIFT) & MASK;
59 }
60 }
61
62 /** Whether the device is interactive (i.e. screen on) or not. */
63 interface InteractiveState {
64 int NON_INTERACTIVE = 0;
65 int INTERACTIVE = 1;
66
67 int SHIFT = BatterySaverState.SHIFT + BatterySaverState.BITS;
68 int BITS = 1;
69 int MASK = (1 << BITS) - 1;
70
71 static int fromIndex(int index) {
72 return (index >> SHIFT) & MASK;
73 }
74 }
75
76 /** Doze mode. */
77 interface DozeState {
78 int NOT_DOZING = 0;
79 int LIGHT = 1;
80 int DEEP = 2;
81
82 int SHIFT = InteractiveState.SHIFT + InteractiveState.BITS;
83 int BITS = 2;
84 int MASK = (1 << BITS) - 1;
85
86 static int fromIndex(int index) {
87 return (index >> SHIFT) & MASK;
88 }
89 }
90
91 /**
92 * Various stats in each state.
93 */
94 static class Stat {
95 public long startTime;
96 public long endTime;
97
98 public int startBatteryLevel;
99 public int endBatteryLevel;
100
Makoto Onuki472d8e32018-01-31 12:37:15 -0800101 public int startBatteryPercent;
102 public int endBatteryPercent;
103
Makoto Onuki076218b2018-01-26 10:26:36 -0800104 public long totalTimeMillis;
105 public int totalBatteryDrain;
Makoto Onuki472d8e32018-01-31 12:37:15 -0800106 public int totalBatteryDrainPercent;
Makoto Onuki076218b2018-01-26 10:26:36 -0800107
108 public long totalMinutes() {
109 return totalTimeMillis / 60_000;
110 }
111
112 public double drainPerHour() {
113 if (totalTimeMillis == 0) {
114 return 0;
115 }
116 return (double) totalBatteryDrain / (totalTimeMillis / (60.0 * 60 * 1000));
117 }
118
Makoto Onuki472d8e32018-01-31 12:37:15 -0800119 public double drainPercentPerHour() {
120 if (totalTimeMillis == 0) {
121 return 0;
122 }
123 return (double) totalBatteryDrainPercent / (totalTimeMillis / (60.0 * 60 * 1000));
124 }
125
Makoto Onuki076218b2018-01-26 10:26:36 -0800126 @VisibleForTesting
127 String toStringForTest() {
128 return "{" + totalMinutes() + "m," + totalBatteryDrain + ","
Makoto Onuki472d8e32018-01-31 12:37:15 -0800129 + String.format("%.2f", drainPerHour()) + "uA/H,"
130 + String.format("%.2f", drainPercentPerHour()) + "%"
131 + "}";
Makoto Onuki076218b2018-01-26 10:26:36 -0800132 }
133 }
134
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800135 @VisibleForTesting
Makoto Onuki472d8e32018-01-31 12:37:15 -0800136 static final String COUNTER_POWER_PERCENT_PREFIX = "battery_saver_stats_percent_";
137
138 @VisibleForTesting
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800139 static final String COUNTER_POWER_MILLIAMPS_PREFIX = "battery_saver_stats_milliamps_";
140
141 @VisibleForTesting
142 static final String COUNTER_TIME_SECONDS_PREFIX = "battery_saver_stats_seconds_";
143
Makoto Onuki076218b2018-01-26 10:26:36 -0800144 private static BatterySavingStats sInstance;
145
146 private BatteryManagerInternal mBatteryManagerInternal;
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800147 private final MetricsLogger mMetricsLogger;
Makoto Onuki076218b2018-01-26 10:26:36 -0800148
149 private static final int STATE_NOT_INITIALIZED = -1;
150 private static final int STATE_CHARGING = -2;
151
152 /**
153 * Current state, one of STATE_* or values returned by {@link #statesToIndex}.
154 */
155 @GuardedBy("mLock")
156 private int mCurrentState = STATE_NOT_INITIALIZED;
157
158 /**
159 * Stats in each state.
160 */
161 @VisibleForTesting
162 @GuardedBy("mLock")
163 final ArrayMap<Integer, Stat> mStats = new ArrayMap<>();
164
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800165 private final MetricsLoggerHelper mMetricsLoggerHelper = new MetricsLoggerHelper();
166
Makoto Onuki076218b2018-01-26 10:26:36 -0800167 /**
168 * Don't call it directly -- use {@link #getInstance()}. Not private for testing.
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800169 * @param metricsLogger
Makoto Onuki076218b2018-01-26 10:26:36 -0800170 */
171 @VisibleForTesting
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800172 BatterySavingStats(MetricsLogger metricsLogger) {
Makoto Onuki076218b2018-01-26 10:26:36 -0800173 mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800174 mMetricsLogger = metricsLogger;
Makoto Onuki076218b2018-01-26 10:26:36 -0800175 }
176
177 public static synchronized BatterySavingStats getInstance() {
178 if (sInstance == null) {
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800179 sInstance = new BatterySavingStats(new MetricsLogger());
Makoto Onuki076218b2018-01-26 10:26:36 -0800180 }
181 return sInstance;
182 }
183
184 private BatteryManagerInternal getBatteryManagerInternal() {
185 if (mBatteryManagerInternal == null) {
186 mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
Makoto Onuki472d8e32018-01-31 12:37:15 -0800187 if (mBatteryManagerInternal == null) {
188 Slog.wtf(TAG, "BatteryManagerInternal not initialized");
189 }
Makoto Onuki076218b2018-01-26 10:26:36 -0800190 }
191 return mBatteryManagerInternal;
192 }
193
194 /**
195 * Takes a state triplet and generates a state index.
196 */
197 @VisibleForTesting
198 static int statesToIndex(
199 int batterySaverState, int interactiveState, int dozeState) {
200 int ret = batterySaverState & BatterySaverState.MASK;
201 ret |= (interactiveState & InteractiveState.MASK) << InteractiveState.SHIFT;
202 ret |= (dozeState & DozeState.MASK) << DozeState.SHIFT;
203 return ret;
204 }
205
206 /**
207 * Takes a state index and returns a string for logging.
208 */
209 @VisibleForTesting
210 static String stateToString(int state) {
211 switch (state) {
212 case STATE_NOT_INITIALIZED:
213 return "NotInitialized";
214 case STATE_CHARGING:
215 return "Charging";
216 }
217 return "BS=" + BatterySaverState.fromIndex(state)
218 + ",I=" + InteractiveState.fromIndex(state)
219 + ",D=" + DozeState.fromIndex(state);
220 }
221
222 /**
223 * @return {@link Stat} fo a given state.
224 */
225 @VisibleForTesting
226 Stat getStat(int stateIndex) {
227 synchronized (mLock) {
228 Stat stat = mStats.get(stateIndex);
229 if (stat == null) {
230 stat = new Stat();
231 mStats.put(stateIndex, stat);
232 }
233 return stat;
234 }
235 }
236
237 /**
238 * @return {@link Stat} fo a given state triplet.
239 */
240 private Stat getStat(int batterySaverState, int interactiveState, int dozeState) {
241 return getStat(statesToIndex(batterySaverState, interactiveState, dozeState));
242 }
243
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800244 @VisibleForTesting
Makoto Onuki076218b2018-01-26 10:26:36 -0800245 long injectCurrentTime() {
246 return SystemClock.elapsedRealtime();
247 }
248
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800249 @VisibleForTesting
Makoto Onuki076218b2018-01-26 10:26:36 -0800250 int injectBatteryLevel() {
251 final BatteryManagerInternal bmi = getBatteryManagerInternal();
252 if (bmi == null) {
Makoto Onuki076218b2018-01-26 10:26:36 -0800253 return 0;
254 }
255 return bmi.getBatteryChargeCounter();
256 }
257
Makoto Onuki472d8e32018-01-31 12:37:15 -0800258 @VisibleForTesting
259 int injectBatteryPercent() {
260 final BatteryManagerInternal bmi = getBatteryManagerInternal();
261 if (bmi == null) {
262 return 0;
263 }
264 return bmi.getBatteryLevel();
265 }
266
Makoto Onuki076218b2018-01-26 10:26:36 -0800267 /**
268 * Called from the outside whenever any of the states changes, when the device is not plugged
269 * in.
270 */
271 public void transitionState(int batterySaverState, int interactiveState, int dozeState) {
272 synchronized (mLock) {
Makoto Onuki076218b2018-01-26 10:26:36 -0800273 final int newState = statesToIndex(
274 batterySaverState, interactiveState, dozeState);
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800275 transitionStateLocked(newState);
Makoto Onuki076218b2018-01-26 10:26:36 -0800276 }
277 }
278
279 /**
280 * Called from the outside when the device is plugged in.
281 */
282 public void startCharging() {
283 synchronized (mLock) {
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800284 transitionStateLocked(STATE_CHARGING);
Makoto Onuki076218b2018-01-26 10:26:36 -0800285 }
286 }
287
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800288 private void transitionStateLocked(int newState) {
289 if (mCurrentState == newState) {
290 return;
291 }
292 final long now = injectCurrentTime();
293 final int batteryLevel = injectBatteryLevel();
Makoto Onuki472d8e32018-01-31 12:37:15 -0800294 final int batteryPercent = injectBatteryPercent();
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800295
Makoto Onuki472d8e32018-01-31 12:37:15 -0800296 endLastStateLocked(now, batteryLevel, batteryPercent);
297 startNewStateLocked(newState, now, batteryLevel, batteryPercent);
298 mMetricsLoggerHelper.transitionState(newState, now, batteryLevel, batteryPercent);
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800299 }
300
Makoto Onuki472d8e32018-01-31 12:37:15 -0800301 private void endLastStateLocked(long now, int batteryLevel, int batteryPercent) {
Makoto Onuki076218b2018-01-26 10:26:36 -0800302 if (mCurrentState < 0) {
303 return;
304 }
305 final Stat stat = getStat(mCurrentState);
306
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800307 stat.endBatteryLevel = batteryLevel;
Makoto Onuki472d8e32018-01-31 12:37:15 -0800308 stat.endBatteryPercent = batteryPercent;
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800309 stat.endTime = now;
Makoto Onuki076218b2018-01-26 10:26:36 -0800310
311 final long deltaTime = stat.endTime - stat.startTime;
312 final int deltaDrain = stat.startBatteryLevel - stat.endBatteryLevel;
Makoto Onuki472d8e32018-01-31 12:37:15 -0800313 final int deltaPercent = stat.startBatteryPercent - stat.endBatteryPercent;
Makoto Onuki076218b2018-01-26 10:26:36 -0800314
315 stat.totalTimeMillis += deltaTime;
316 stat.totalBatteryDrain += deltaDrain;
Makoto Onuki472d8e32018-01-31 12:37:15 -0800317 stat.totalBatteryDrainPercent += deltaPercent;
Makoto Onuki076218b2018-01-26 10:26:36 -0800318
319 if (DEBUG) {
320 Slog.d(TAG, "State summary: " + stateToString(mCurrentState)
321 + ": " + (deltaTime / 1_000) + "s "
322 + "Start level: " + stat.startBatteryLevel + "uA "
323 + "End level: " + stat.endBatteryLevel + "uA "
Makoto Onuki472d8e32018-01-31 12:37:15 -0800324 + "Start percent: " + stat.startBatteryPercent + "% "
325 + "End percent: " + stat.endBatteryPercent + "% "
326 + "Drain " + deltaDrain + "uA");
Makoto Onuki076218b2018-01-26 10:26:36 -0800327 }
328 EventLogTags.writeBatterySavingStats(
329 BatterySaverState.fromIndex(mCurrentState),
330 InteractiveState.fromIndex(mCurrentState),
331 DozeState.fromIndex(mCurrentState),
332 deltaTime,
333 deltaDrain,
Makoto Onuki472d8e32018-01-31 12:37:15 -0800334 deltaPercent,
Makoto Onuki076218b2018-01-26 10:26:36 -0800335 stat.totalTimeMillis,
Makoto Onuki472d8e32018-01-31 12:37:15 -0800336 stat.totalBatteryDrain,
337 stat.totalBatteryDrainPercent);
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800338
Makoto Onuki076218b2018-01-26 10:26:36 -0800339 }
340
Makoto Onuki472d8e32018-01-31 12:37:15 -0800341 private void startNewStateLocked(int newState, long now, int batteryLevel, int batteryPercent) {
Makoto Onuki076218b2018-01-26 10:26:36 -0800342 if (DEBUG) {
343 Slog.d(TAG, "New state: " + stateToString(newState));
344 }
345 mCurrentState = newState;
346
347 if (mCurrentState < 0) {
348 return;
349 }
350
351 final Stat stat = getStat(mCurrentState);
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800352 stat.startBatteryLevel = batteryLevel;
Makoto Onuki472d8e32018-01-31 12:37:15 -0800353 stat.startBatteryPercent = batteryPercent;
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800354 stat.startTime = now;
Makoto Onuki076218b2018-01-26 10:26:36 -0800355 stat.endTime = 0;
356 }
357
358 public void dump(PrintWriter pw, String indent) {
359 synchronized (mLock) {
360 pw.print(indent);
361 pw.println("Battery Saving Stats:");
362
363 indent = indent + " ";
364
365 pw.print(indent);
Makoto Onuki472d8e32018-01-31 12:37:15 -0800366 pw.println("Battery Saver: Off On");
Makoto Onuki076218b2018-01-26 10:26:36 -0800367 dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr",
368 DozeState.NOT_DOZING, "NonDoze");
369 dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, " Intr",
370 DozeState.NOT_DOZING, " ");
371
372 dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr",
373 DozeState.DEEP, "Deep ");
374 dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, " Intr",
375 DozeState.DEEP, " ");
376
377 dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr",
378 DozeState.LIGHT, "Light ");
379 dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, " Intr",
380 DozeState.LIGHT, " ");
381
382 pw.println();
383 }
384 }
385
386 private void dumpLineLocked(PrintWriter pw, String indent,
387 int interactiveState, String interactiveLabel,
388 int dozeState, String dozeLabel) {
389 pw.print(indent);
390 pw.print(dozeLabel);
391 pw.print(" ");
392 pw.print(interactiveLabel);
393 pw.print(": ");
394
395 final Stat offStat = getStat(BatterySaverState.OFF, interactiveState, dozeState);
396 final Stat onStat = getStat(BatterySaverState.ON, interactiveState, dozeState);
397
Makoto Onuki472d8e32018-01-31 12:37:15 -0800398 pw.println(String.format("%6dm %6dmA (%3d%%) %8.1fmA/h %6dm %6dmA (%3d%%) %8.1fmA/h",
Makoto Onuki076218b2018-01-26 10:26:36 -0800399 offStat.totalMinutes(),
400 offStat.totalBatteryDrain / 1000,
Makoto Onuki472d8e32018-01-31 12:37:15 -0800401 offStat.totalBatteryDrainPercent,
Makoto Onuki076218b2018-01-26 10:26:36 -0800402 offStat.drainPerHour() / 1000.0,
403 onStat.totalMinutes(),
404 onStat.totalBatteryDrain / 1000,
Makoto Onuki472d8e32018-01-31 12:37:15 -0800405 onStat.totalBatteryDrainPercent,
Makoto Onuki076218b2018-01-26 10:26:36 -0800406 onStat.drainPerHour() / 1000.0));
407 }
Makoto Onuki076218b2018-01-26 10:26:36 -0800408
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800409 @VisibleForTesting
410 class MetricsLoggerHelper {
411 private int mLastState = STATE_NOT_INITIALIZED;
412 private long mStartTime;
413 private int mStartBatteryLevel;
Makoto Onuki472d8e32018-01-31 12:37:15 -0800414 private int mStartPercent;
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800415
416 private static final int STATE_CHANGE_DETECT_MASK =
417 (BatterySaverState.MASK << BatterySaverState.SHIFT) |
418 (InteractiveState.MASK << InteractiveState.SHIFT);
419
Makoto Onuki472d8e32018-01-31 12:37:15 -0800420 public void transitionState(int newState, long now, int batteryLevel, int batteryPercent) {
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800421 final boolean stateChanging =
422 ((mLastState >= 0) ^ (newState >= 0)) ||
423 (((mLastState ^ newState) & STATE_CHANGE_DETECT_MASK) != 0);
424 if (stateChanging) {
425 if (mLastState >= 0) {
426 final long deltaTime = now - mStartTime;
427 final int deltaBattery = mStartBatteryLevel - batteryLevel;
Makoto Onuki472d8e32018-01-31 12:37:15 -0800428 final int deltaPercent = mStartPercent - batteryPercent;
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800429
Makoto Onuki472d8e32018-01-31 12:37:15 -0800430 report(mLastState, deltaTime, deltaBattery, deltaPercent);
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800431 }
432 mStartTime = now;
433 mStartBatteryLevel = batteryLevel;
Makoto Onuki472d8e32018-01-31 12:37:15 -0800434 mStartPercent = batteryPercent;
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800435 }
436 mLastState = newState;
437 }
438
439 String getCounterSuffix(int state) {
440 final boolean batterySaver =
441 BatterySaverState.fromIndex(state) != BatterySaverState.OFF;
442 final boolean interactive =
443 InteractiveState.fromIndex(state) != InteractiveState.NON_INTERACTIVE;
444 if (batterySaver) {
445 return interactive ? "11" : "10";
446 } else {
447 return interactive ? "01" : "00";
448 }
449 }
450
Makoto Onuki472d8e32018-01-31 12:37:15 -0800451 void report(int state, long deltaTimeMs, int deltaBatteryUa, int deltaPercent) {
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800452 final String suffix = getCounterSuffix(state);
453 mMetricsLogger.count(COUNTER_POWER_MILLIAMPS_PREFIX + suffix, deltaBatteryUa / 1000);
Makoto Onuki472d8e32018-01-31 12:37:15 -0800454 mMetricsLogger.count(COUNTER_POWER_PERCENT_PREFIX + suffix, deltaPercent);
Makoto Onuki04e9dc92018-01-29 13:09:45 -0800455 mMetricsLogger.count(COUNTER_TIME_SECONDS_PREFIX + suffix, (int) (deltaTimeMs / 1000));
456 }
457 }
458}