blob: d9a8818c39886f215ec9ac53ca634fa5b94e9cd2 [file] [log] [blame]
Adam Lesinskib3a1bad2017-05-26 11:50:40 -07001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16package com.android.server.am;
17
18import android.annotation.Nullable;
19import android.bluetooth.BluetoothActivityEnergyInfo;
20import android.bluetooth.BluetoothAdapter;
21import android.content.Context;
22import android.net.wifi.IWifiManager;
23import android.net.wifi.WifiActivityEnergyInfo;
24import android.os.BatteryStats;
25import android.os.Parcelable;
26import android.os.RemoteException;
27import android.os.ServiceManager;
28import android.os.SynchronousResultReceiver;
29import android.os.SystemClock;
30import android.telephony.ModemActivityInfo;
31import android.telephony.TelephonyManager;
32import android.util.IntArray;
33import android.util.Slog;
Bookatz3c648862018-05-25 13:32:43 -070034import android.util.StatsLog;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070035import android.util.TimeUtils;
36
37import com.android.internal.annotations.GuardedBy;
38import com.android.internal.os.BatteryStatsImpl;
Sudheer Shankae544d162017-12-28 17:06:20 -080039import com.android.internal.util.function.pooled.PooledLambda;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070040
41import libcore.util.EmptyArray;
42
Adam Lesinskicf0b2b62017-08-29 19:30:31 -070043import java.util.concurrent.CompletableFuture;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070044import java.util.concurrent.ExecutorService;
45import java.util.concurrent.Executors;
46import java.util.concurrent.Future;
Sudheer Shankac57729a2018-02-09 15:44:42 -080047import java.util.concurrent.ScheduledExecutorService;
48import java.util.concurrent.ScheduledFuture;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070049import java.util.concurrent.ThreadFactory;
Sudheer Shankac57729a2018-02-09 15:44:42 -080050import java.util.concurrent.TimeUnit;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070051import java.util.concurrent.TimeoutException;
52
53/**
54 * A Worker that fetches data from external sources (WiFi controller, bluetooth chipset) on a
55 * dedicated thread and updates BatteryStatsImpl with that information.
56 *
57 * As much work as possible is done without holding the BatteryStatsImpl lock, and only the
58 * readily available data is pushed into BatteryStatsImpl with the lock held.
59 */
60class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
61 private static final String TAG = "BatteryExternalStatsWorker";
62 private static final boolean DEBUG = false;
63
64 /**
65 * How long to wait on an individual subsystem to return its stats.
66 */
67 private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000;
68
69 // There is some accuracy error in wifi reports so allow some slop in the results.
70 private static final long MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS = 750;
71
Sudheer Shankac57729a2018-02-09 15:44:42 -080072 private final ScheduledExecutorService mExecutorService =
73 Executors.newSingleThreadScheduledExecutor(
74 (ThreadFactory) r -> {
75 Thread t = new Thread(r, "batterystats-worker");
76 t.setPriority(Thread.NORM_PRIORITY);
77 return t;
78 });
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070079
80 private final Context mContext;
81 private final BatteryStatsImpl mStats;
82
83 @GuardedBy("this")
84 private int mUpdateFlags = 0;
85
86 @GuardedBy("this")
87 private Future<?> mCurrentFuture = null;
88
89 @GuardedBy("this")
90 private String mCurrentReason = null;
91
92 @GuardedBy("this")
Sudheer Shankac57729a2018-02-09 15:44:42 -080093 private boolean mOnBattery;
94
95 @GuardedBy("this")
96 private boolean mOnBatteryScreenOff;
97
98 @GuardedBy("this")
99 private boolean mUseLatestStates = true;
100
101 @GuardedBy("this")
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700102 private final IntArray mUidsToRemove = new IntArray();
103
Sudheer Shankac57729a2018-02-09 15:44:42 -0800104 @GuardedBy("this")
105 private Future<?> mWakelockChangesUpdate;
106
Sudheer Shanka0719c6a2018-04-24 11:12:11 -0700107 @GuardedBy("this")
108 private Future<?> mBatteryLevelSync;
109
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700110 private final Object mWorkerLock = new Object();
111
112 @GuardedBy("mWorkerLock")
113 private IWifiManager mWifiManager = null;
114
115 @GuardedBy("mWorkerLock")
116 private TelephonyManager mTelephony = null;
117
118 // WiFi keeps an accumulated total of stats, unlike Bluetooth.
119 // Keep the last WiFi stats so we can compute a delta.
120 @GuardedBy("mWorkerLock")
121 private WifiActivityEnergyInfo mLastInfo =
Siddharth Rayb50a6842017-12-14 15:15:28 -0800122 new WifiActivityEnergyInfo(0, 0, 0, new long[]{0}, 0, 0, 0, 0);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700123
Sudheer Shankae56013a2018-04-23 11:22:15 -0700124 /**
125 * Timestamp at which all external stats were last collected in
126 * {@link SystemClock#elapsedRealtime()} time base.
127 */
128 @GuardedBy("this")
129 private long mLastCollectionTimeStamp;
130
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700131 BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) {
132 mContext = context;
133 mStats = stats;
134 }
135
136 @Override
137 public synchronized Future<?> scheduleSync(String reason, int flags) {
138 return scheduleSyncLocked(reason, flags);
139 }
140
141 @Override
142 public synchronized Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
143 mUidsToRemove.add(uid);
144 return scheduleSyncLocked("remove-uid", UPDATE_CPU);
145 }
146
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800147 @Override
Sudheer Shanka5c19b892018-01-05 17:25:46 -0800148 public synchronized Future<?> scheduleCpuSyncDueToSettingChange() {
149 return scheduleSyncLocked("setting-change", UPDATE_CPU);
150 }
151
152 @Override
Sudheer Shankac20379e2018-02-15 00:06:21 -0800153 public Future<?> scheduleReadProcStateCpuTimes(
154 boolean onBattery, boolean onBatteryScreenOff, long delayMillis) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800155 synchronized (mStats) {
Sudheer Shanka5c19b892018-01-05 17:25:46 -0800156 if (!mStats.trackPerProcStateCpuTimes()) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800157 return null;
158 }
159 }
160 synchronized (BatteryExternalStatsWorker.this) {
161 if (!mExecutorService.isShutdown()) {
Sudheer Shankac20379e2018-02-15 00:06:21 -0800162 return mExecutorService.schedule(PooledLambda.obtainRunnable(
Sudheer Shankae544d162017-12-28 17:06:20 -0800163 BatteryStatsImpl::updateProcStateCpuTimes,
Sudheer Shankac20379e2018-02-15 00:06:21 -0800164 mStats, onBattery, onBatteryScreenOff).recycleOnUse(),
165 delayMillis, TimeUnit.MILLISECONDS);
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800166 }
167 }
168 return null;
169 }
170
171 @Override
Sudheer Shankae544d162017-12-28 17:06:20 -0800172 public Future<?> scheduleCopyFromAllUidsCpuTimes(
173 boolean onBattery, boolean onBatteryScreenOff) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800174 synchronized (mStats) {
Sudheer Shanka5c19b892018-01-05 17:25:46 -0800175 if (!mStats.trackPerProcStateCpuTimes()) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800176 return null;
177 }
178 }
179 synchronized (BatteryExternalStatsWorker.this) {
180 if (!mExecutorService.isShutdown()) {
Sudheer Shankae544d162017-12-28 17:06:20 -0800181 return mExecutorService.submit(PooledLambda.obtainRunnable(
182 BatteryStatsImpl::copyFromAllUidsCpuTimes,
183 mStats, onBattery, onBatteryScreenOff).recycleOnUse());
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800184 }
185 }
186 return null;
187 }
188
Sudheer Shankac57729a2018-02-09 15:44:42 -0800189 @Override
190 public Future<?> scheduleCpuSyncDueToScreenStateChange(
191 boolean onBattery, boolean onBatteryScreenOff) {
192 synchronized (BatteryExternalStatsWorker.this) {
193 if (mCurrentFuture == null || (mUpdateFlags & UPDATE_CPU) == 0) {
194 mOnBattery = onBattery;
195 mOnBatteryScreenOff = onBatteryScreenOff;
196 mUseLatestStates = false;
197 }
198 return scheduleSyncLocked("screen-state", UPDATE_CPU);
199 }
200 }
201
202 @Override
203 public Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis) {
Sudheer Shanka0719c6a2018-04-24 11:12:11 -0700204 synchronized (BatteryExternalStatsWorker.this) {
205 mWakelockChangesUpdate = scheduleDelayedSyncLocked(mWakelockChangesUpdate,
206 () -> {
207 scheduleSync("wakelock-change", UPDATE_CPU);
208 scheduleRunnable(() -> mStats.postBatteryNeedsCpuUpdateMsg());
209 },
210 delayMillis);
211 return mWakelockChangesUpdate;
Sudheer Shankac57729a2018-02-09 15:44:42 -0800212 }
Sudheer Shankac57729a2018-02-09 15:44:42 -0800213 }
214
215 @Override
216 public void cancelCpuSyncDueToWakelockChange() {
Sudheer Shanka0719c6a2018-04-24 11:12:11 -0700217 synchronized (BatteryExternalStatsWorker.this) {
218 if (mWakelockChangesUpdate != null) {
219 mWakelockChangesUpdate.cancel(false);
220 mWakelockChangesUpdate = null;
221 }
Sudheer Shankac57729a2018-02-09 15:44:42 -0800222 }
223 }
224
Sudheer Shanka0719c6a2018-04-24 11:12:11 -0700225 @Override
226 public Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis) {
227 synchronized (BatteryExternalStatsWorker.this) {
228 mBatteryLevelSync = scheduleDelayedSyncLocked(mBatteryLevelSync,
229 () -> scheduleSync("battery-level", UPDATE_ALL),
230 delayMillis);
231 return mBatteryLevelSync;
232 }
233 }
234
235 @GuardedBy("this")
236 private void cancelSyncDueToBatteryLevelChangeLocked() {
237 if (mBatteryLevelSync != null) {
238 mBatteryLevelSync.cancel(false);
239 mBatteryLevelSync = null;
240 }
241 }
242
243 /**
244 * Schedule a sync {@param syncRunnable} with a delay. If there's already a scheduled sync, a
245 * new sync won't be scheduled unless it is being scheduled to run immediately (delayMillis=0).
246 *
247 * @param lastScheduledSync the task which was earlier scheduled to run
248 * @param syncRunnable the task that needs to be scheduled to run
249 * @param delayMillis time after which {@param syncRunnable} needs to be scheduled
250 * @return scheduled {@link Future} which can be used to check if task is completed or to
251 * cancel it if needed
252 */
253 @GuardedBy("this")
254 private Future<?> scheduleDelayedSyncLocked(Future<?> lastScheduledSync, Runnable syncRunnable,
255 long delayMillis) {
256 if (mExecutorService.isShutdown()) {
257 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
258 }
259
260 if (lastScheduledSync != null) {
261 // If there's already a scheduled task, leave it as is if we're trying to
262 // re-schedule it again with a delay, otherwise cancel and re-schedule it.
263 if (delayMillis == 0) {
264 lastScheduledSync.cancel(false);
265 } else {
266 return lastScheduledSync;
267 }
268 }
269
270 return mExecutorService.schedule(syncRunnable, delayMillis, TimeUnit.MILLISECONDS);
271 }
272
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700273 public synchronized Future<?> scheduleWrite() {
Adam Lesinskicf0b2b62017-08-29 19:30:31 -0700274 if (mExecutorService.isShutdown()) {
275 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
276 }
277
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700278 scheduleSyncLocked("write", UPDATE_ALL);
279 // Since we use a single threaded executor, we can assume the next scheduled task's
280 // Future finishes after the sync.
281 return mExecutorService.submit(mWriteTask);
282 }
283
284 /**
285 * Schedules a task to run on the BatteryExternalStatsWorker thread. If scheduling more work
286 * within the task, never wait on the resulting Future. This will result in a deadlock.
287 */
288 public synchronized void scheduleRunnable(Runnable runnable) {
Adam Lesinskicf0b2b62017-08-29 19:30:31 -0700289 if (!mExecutorService.isShutdown()) {
290 mExecutorService.submit(runnable);
291 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700292 }
293
294 public void shutdown() {
295 mExecutorService.shutdownNow();
296 }
297
Andreas Gampea36dc622018-02-05 17:19:22 -0800298 @GuardedBy("this")
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700299 private Future<?> scheduleSyncLocked(String reason, int flags) {
Adam Lesinskicf0b2b62017-08-29 19:30:31 -0700300 if (mExecutorService.isShutdown()) {
301 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
302 }
303
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700304 if (mCurrentFuture == null) {
305 mUpdateFlags = flags;
306 mCurrentReason = reason;
307 mCurrentFuture = mExecutorService.submit(mSyncTask);
308 }
309 mUpdateFlags |= flags;
310 return mCurrentFuture;
311 }
312
Sudheer Shankae56013a2018-04-23 11:22:15 -0700313 long getLastCollectionTimeStamp() {
314 synchronized (this) {
315 return mLastCollectionTimeStamp;
316 }
317 }
318
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700319 private final Runnable mSyncTask = new Runnable() {
320 @Override
321 public void run() {
322 // Capture a snapshot of the state we are meant to process.
323 final int updateFlags;
324 final String reason;
325 final int[] uidsToRemove;
Sudheer Shankac57729a2018-02-09 15:44:42 -0800326 final boolean onBattery;
327 final boolean onBatteryScreenOff;
328 final boolean useLatestStates;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700329 synchronized (BatteryExternalStatsWorker.this) {
330 updateFlags = mUpdateFlags;
331 reason = mCurrentReason;
332 uidsToRemove = mUidsToRemove.size() > 0 ? mUidsToRemove.toArray() : EmptyArray.INT;
Sudheer Shankac57729a2018-02-09 15:44:42 -0800333 onBattery = mOnBattery;
334 onBatteryScreenOff = mOnBatteryScreenOff;
335 useLatestStates = mUseLatestStates;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700336 mUpdateFlags = 0;
337 mCurrentReason = null;
338 mUidsToRemove.clear();
339 mCurrentFuture = null;
Sudheer Shankac57729a2018-02-09 15:44:42 -0800340 mUseLatestStates = true;
Sudheer Shanka0719c6a2018-04-24 11:12:11 -0700341 if ((updateFlags & UPDATE_ALL) != 0) {
342 cancelSyncDueToBatteryLevelChangeLocked();
343 }
344 if ((updateFlags & UPDATE_CPU) != 0) {
345 cancelCpuSyncDueToWakelockChange();
346 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700347 }
348
Mike Ma561a8d92018-03-20 18:24:05 -0700349 try {
350 synchronized (mWorkerLock) {
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700351 if (DEBUG) {
Mike Ma561a8d92018-03-20 18:24:05 -0700352 Slog.d(TAG, "begin updateExternalStatsSync reason=" + reason);
353 }
354 try {
355 updateExternalStatsLocked(reason, updateFlags, onBattery,
356 onBatteryScreenOff, useLatestStates);
357 } finally {
358 if (DEBUG) {
359 Slog.d(TAG, "end updateExternalStatsSync");
360 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700361 }
362 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700363
Mike Ma561a8d92018-03-20 18:24:05 -0700364 if ((updateFlags & UPDATE_CPU) != 0) {
365 mStats.copyFromAllUidsCpuTimes();
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700366 }
Mike Ma561a8d92018-03-20 18:24:05 -0700367
368 // Clean up any UIDs if necessary.
369 synchronized (mStats) {
370 for (int uid : uidsToRemove) {
Bookatz3c648862018-05-25 13:32:43 -0700371 StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, -1, uid,
372 StatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED);
Mike Ma561a8d92018-03-20 18:24:05 -0700373 mStats.removeIsolatedUidLocked(uid);
374 }
375 mStats.clearPendingRemovedUids();
376 }
377 } catch (Exception e) {
378 Slog.wtf(TAG, "Error updating external stats: ", e);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700379 }
Sudheer Shankae56013a2018-04-23 11:22:15 -0700380
381 synchronized (BatteryExternalStatsWorker.this) {
382 mLastCollectionTimeStamp = SystemClock.elapsedRealtime();
383 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700384 }
385 };
386
387 private final Runnable mWriteTask = new Runnable() {
388 @Override
389 public void run() {
390 synchronized (mStats) {
391 mStats.writeAsyncLocked();
392 }
393 }
394 };
395
Andreas Gampea36dc622018-02-05 17:19:22 -0800396 @GuardedBy("mWorkerLock")
Sudheer Shankac57729a2018-02-09 15:44:42 -0800397 private void updateExternalStatsLocked(final String reason, int updateFlags,
398 boolean onBattery, boolean onBatteryScreenOff, boolean useLatestStates) {
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700399 // We will request data from external processes asynchronously, and wait on a timeout.
400 SynchronousResultReceiver wifiReceiver = null;
401 SynchronousResultReceiver bluetoothReceiver = null;
402 SynchronousResultReceiver modemReceiver = null;
403
404 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
405 // We were asked to fetch WiFi data.
406 if (mWifiManager == null) {
407 mWifiManager = IWifiManager.Stub.asInterface(ServiceManager.getService(
408 Context.WIFI_SERVICE));
409 }
410
411 if (mWifiManager != null) {
412 try {
413 wifiReceiver = new SynchronousResultReceiver("wifi");
414 mWifiManager.requestActivityInfo(wifiReceiver);
415 } catch (RemoteException e) {
416 // Oh well.
417 }
418 }
419 }
420
421 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) {
422 // We were asked to fetch Bluetooth data.
423 final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
424 if (adapter != null) {
425 bluetoothReceiver = new SynchronousResultReceiver("bluetooth");
426 adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
427 }
428 }
429
430 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) {
431 // We were asked to fetch Telephony data.
432 if (mTelephony == null) {
433 mTelephony = TelephonyManager.from(mContext);
434 }
435
436 if (mTelephony != null) {
437 modemReceiver = new SynchronousResultReceiver("telephony");
438 mTelephony.requestModemActivityInfo(modemReceiver);
439 }
440 }
441
442 final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
443 final BluetoothActivityEnergyInfo bluetoothInfo = awaitControllerInfo(bluetoothReceiver);
444 final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
445
446 synchronized (mStats) {
447 mStats.addHistoryEventLocked(
448 SystemClock.elapsedRealtime(),
449 SystemClock.uptimeMillis(),
450 BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
451 reason, 0);
452
453 if ((updateFlags & UPDATE_CPU) != 0) {
Sudheer Shankac57729a2018-02-09 15:44:42 -0800454 if (useLatestStates) {
455 onBattery = mStats.isOnBatteryLocked();
456 onBatteryScreenOff = mStats.isOnBatteryScreenOffLocked();
457 }
458 mStats.updateCpuTimeLocked(onBattery, onBatteryScreenOff);
459 }
460
461 if ((updateFlags & UPDATE_ALL) != 0) {
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700462 mStats.updateKernelWakelocksLocked();
463 mStats.updateKernelMemoryBandwidthLocked();
464 }
465
Bookatz50df7112017-08-04 14:53:26 -0700466 if ((updateFlags & UPDATE_RPM) != 0) {
467 mStats.updateRpmStatsLocked();
468 }
469
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700470 if (bluetoothInfo != null) {
471 if (bluetoothInfo.isValid()) {
472 mStats.updateBluetoothStateLocked(bluetoothInfo);
473 } else {
Mike Ma561a8d92018-03-20 18:24:05 -0700474 Slog.w(TAG, "bluetooth info is invalid: " + bluetoothInfo);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700475 }
476 }
477 }
478
479 // WiFi and Modem state are updated without the mStats lock held, because they
480 // do some network stats retrieval before internally grabbing the mStats lock.
481
482 if (wifiInfo != null) {
483 if (wifiInfo.isValid()) {
484 mStats.updateWifiState(extractDeltaLocked(wifiInfo));
485 } else {
Mike Ma561a8d92018-03-20 18:24:05 -0700486 Slog.w(TAG, "wifi info is invalid: " + wifiInfo);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700487 }
488 }
489
490 if (modemInfo != null) {
491 if (modemInfo.isValid()) {
492 mStats.updateMobileRadioState(modemInfo);
493 } else {
Mike Ma561a8d92018-03-20 18:24:05 -0700494 Slog.w(TAG, "modem info is invalid: " + modemInfo);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700495 }
496 }
497 }
498
499 /**
500 * Helper method to extract the Parcelable controller info from a
501 * SynchronousResultReceiver.
502 */
503 private static <T extends Parcelable> T awaitControllerInfo(
504 @Nullable SynchronousResultReceiver receiver) {
505 if (receiver == null) {
506 return null;
507 }
508
509 try {
510 final SynchronousResultReceiver.Result result =
511 receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
512 if (result.bundle != null) {
513 // This is the final destination for the Bundle.
514 result.bundle.setDefusable(true);
515
516 final T data = result.bundle.getParcelable(
517 BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY);
518 if (data != null) {
519 return data;
520 }
521 }
522 Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
523 } catch (TimeoutException e) {
524 Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
525 }
526 return null;
527 }
528
Andreas Gampea36dc622018-02-05 17:19:22 -0800529 @GuardedBy("mWorkerLock")
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700530 private WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) {
531 final long timePeriodMs = latest.mTimestamp - mLastInfo.mTimestamp;
Siddharth Rayb50a6842017-12-14 15:15:28 -0800532 final long lastScanMs = mLastInfo.mControllerScanTimeMs;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700533 final long lastIdleMs = mLastInfo.mControllerIdleTimeMs;
534 final long lastTxMs = mLastInfo.mControllerTxTimeMs;
535 final long lastRxMs = mLastInfo.mControllerRxTimeMs;
536 final long lastEnergy = mLastInfo.mControllerEnergyUsed;
537
538 // We will modify the last info object to be the delta, and store the new
539 // WifiActivityEnergyInfo object as our last one.
540 final WifiActivityEnergyInfo delta = mLastInfo;
541 delta.mTimestamp = latest.getTimeStamp();
542 delta.mStackState = latest.getStackState();
543
544 final long txTimeMs = latest.mControllerTxTimeMs - lastTxMs;
545 final long rxTimeMs = latest.mControllerRxTimeMs - lastRxMs;
546 final long idleTimeMs = latest.mControllerIdleTimeMs - lastIdleMs;
Siddharth Rayb50a6842017-12-14 15:15:28 -0800547 final long scanTimeMs = latest.mControllerScanTimeMs - lastScanMs;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700548
Siddharth Ray97f62bf2018-06-17 18:28:44 -0700549 if (txTimeMs < 0 || rxTimeMs < 0 || scanTimeMs < 0 || idleTimeMs < 0) {
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700550 // The stats were reset by the WiFi system (which is why our delta is negative).
Siddharth Ray97f62bf2018-06-17 18:28:44 -0700551 // Returns the unaltered stats. The total on time should not exceed the time
552 // duartion between reports.
553 final long totalOnTimeMs = latest.mControllerTxTimeMs + latest.mControllerRxTimeMs
554 + latest.mControllerIdleTimeMs;
555 if (totalOnTimeMs <= timePeriodMs + MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS) {
556 delta.mControllerEnergyUsed = latest.mControllerEnergyUsed;
557 delta.mControllerRxTimeMs = latest.mControllerRxTimeMs;
558 delta.mControllerTxTimeMs = latest.mControllerTxTimeMs;
559 delta.mControllerIdleTimeMs = latest.mControllerIdleTimeMs;
560 delta.mControllerScanTimeMs = latest.mControllerScanTimeMs;
561 } else {
562 delta.mControllerEnergyUsed = 0;
563 delta.mControllerRxTimeMs = 0;
564 delta.mControllerTxTimeMs = 0;
565 delta.mControllerIdleTimeMs = 0;
566 delta.mControllerScanTimeMs = 0;
567 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700568 Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + delta);
569 } else {
570 final long totalActiveTimeMs = txTimeMs + rxTimeMs;
571 long maxExpectedIdleTimeMs;
572 if (totalActiveTimeMs > timePeriodMs) {
573 // Cap the max idle time at zero since the active time consumed the whole time
574 maxExpectedIdleTimeMs = 0;
575 if (totalActiveTimeMs > timePeriodMs + MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS) {
576 StringBuilder sb = new StringBuilder();
577 sb.append("Total Active time ");
578 TimeUtils.formatDuration(totalActiveTimeMs, sb);
579 sb.append(" is longer than sample period ");
580 TimeUtils.formatDuration(timePeriodMs, sb);
581 sb.append(".\n");
582 sb.append("Previous WiFi snapshot: ").append("idle=");
583 TimeUtils.formatDuration(lastIdleMs, sb);
584 sb.append(" rx=");
585 TimeUtils.formatDuration(lastRxMs, sb);
586 sb.append(" tx=");
587 TimeUtils.formatDuration(lastTxMs, sb);
588 sb.append(" e=").append(lastEnergy);
589 sb.append("\n");
590 sb.append("Current WiFi snapshot: ").append("idle=");
591 TimeUtils.formatDuration(latest.mControllerIdleTimeMs, sb);
592 sb.append(" rx=");
593 TimeUtils.formatDuration(latest.mControllerRxTimeMs, sb);
594 sb.append(" tx=");
595 TimeUtils.formatDuration(latest.mControllerTxTimeMs, sb);
596 sb.append(" e=").append(latest.mControllerEnergyUsed);
597 Slog.wtf(TAG, sb.toString());
598 }
599 } else {
600 maxExpectedIdleTimeMs = timePeriodMs - totalActiveTimeMs;
601 }
602 // These times seem to be the most reliable.
603 delta.mControllerTxTimeMs = txTimeMs;
604 delta.mControllerRxTimeMs = rxTimeMs;
Siddharth Rayb50a6842017-12-14 15:15:28 -0800605 delta.mControllerScanTimeMs = scanTimeMs;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700606 // WiFi calculates the idle time as a difference from the on time and the various
607 // Rx + Tx times. There seems to be some missing time there because this sometimes
608 // becomes negative. Just cap it at 0 and ensure that it is less than the expected idle
609 // time from the difference in timestamps.
610 // b/21613534
611 delta.mControllerIdleTimeMs = Math.min(maxExpectedIdleTimeMs, Math.max(0, idleTimeMs));
612 delta.mControllerEnergyUsed = Math.max(0, latest.mControllerEnergyUsed - lastEnergy);
613 }
614
615 mLastInfo = latest;
616 return delta;
617 }
618}