blob: 2541352b6daa6c91ac702c485016198ccf08d767 [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
Ben Lina8c34e12018-07-20 16:35:39 -070018import static android.net.wifi.WifiManager.WIFI_FEATURE_LINK_LAYER_STATS;
19
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070020import android.annotation.Nullable;
21import android.bluetooth.BluetoothActivityEnergyInfo;
22import android.bluetooth.BluetoothAdapter;
23import android.content.Context;
24import android.net.wifi.IWifiManager;
25import android.net.wifi.WifiActivityEnergyInfo;
26import android.os.BatteryStats;
27import android.os.Parcelable;
28import android.os.RemoteException;
29import android.os.ServiceManager;
30import android.os.SynchronousResultReceiver;
31import android.os.SystemClock;
32import android.telephony.ModemActivityInfo;
33import android.telephony.TelephonyManager;
34import android.util.IntArray;
35import android.util.Slog;
Bookatz3c648862018-05-25 13:32:43 -070036import android.util.StatsLog;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070037import android.util.TimeUtils;
38
39import com.android.internal.annotations.GuardedBy;
40import com.android.internal.os.BatteryStatsImpl;
Sudheer Shankae544d162017-12-28 17:06:20 -080041import com.android.internal.util.function.pooled.PooledLambda;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070042
43import libcore.util.EmptyArray;
44
Adam Lesinskicf0b2b62017-08-29 19:30:31 -070045import java.util.concurrent.CompletableFuture;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070046import java.util.concurrent.ExecutorService;
47import java.util.concurrent.Executors;
48import java.util.concurrent.Future;
Sudheer Shankac57729a2018-02-09 15:44:42 -080049import java.util.concurrent.ScheduledExecutorService;
50import java.util.concurrent.ScheduledFuture;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070051import java.util.concurrent.ThreadFactory;
Sudheer Shankac57729a2018-02-09 15:44:42 -080052import java.util.concurrent.TimeUnit;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070053import java.util.concurrent.TimeoutException;
54
55/**
56 * A Worker that fetches data from external sources (WiFi controller, bluetooth chipset) on a
57 * dedicated thread and updates BatteryStatsImpl with that information.
58 *
59 * As much work as possible is done without holding the BatteryStatsImpl lock, and only the
60 * readily available data is pushed into BatteryStatsImpl with the lock held.
61 */
62class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
63 private static final String TAG = "BatteryExternalStatsWorker";
64 private static final boolean DEBUG = false;
65
66 /**
67 * How long to wait on an individual subsystem to return its stats.
68 */
69 private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000;
70
71 // There is some accuracy error in wifi reports so allow some slop in the results.
72 private static final long MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS = 750;
73
Sudheer Shankac57729a2018-02-09 15:44:42 -080074 private final ScheduledExecutorService mExecutorService =
75 Executors.newSingleThreadScheduledExecutor(
76 (ThreadFactory) r -> {
77 Thread t = new Thread(r, "batterystats-worker");
78 t.setPriority(Thread.NORM_PRIORITY);
79 return t;
80 });
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070081
82 private final Context mContext;
83 private final BatteryStatsImpl mStats;
84
85 @GuardedBy("this")
86 private int mUpdateFlags = 0;
87
88 @GuardedBy("this")
89 private Future<?> mCurrentFuture = null;
90
91 @GuardedBy("this")
92 private String mCurrentReason = null;
93
94 @GuardedBy("this")
Sudheer Shankac57729a2018-02-09 15:44:42 -080095 private boolean mOnBattery;
96
97 @GuardedBy("this")
98 private boolean mOnBatteryScreenOff;
99
100 @GuardedBy("this")
101 private boolean mUseLatestStates = true;
102
103 @GuardedBy("this")
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700104 private final IntArray mUidsToRemove = new IntArray();
105
Sudheer Shankac57729a2018-02-09 15:44:42 -0800106 @GuardedBy("this")
107 private Future<?> mWakelockChangesUpdate;
108
Sudheer Shanka0719c6a2018-04-24 11:12:11 -0700109 @GuardedBy("this")
110 private Future<?> mBatteryLevelSync;
111
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700112 private final Object mWorkerLock = new Object();
113
114 @GuardedBy("mWorkerLock")
115 private IWifiManager mWifiManager = null;
116
117 @GuardedBy("mWorkerLock")
118 private TelephonyManager mTelephony = null;
119
120 // WiFi keeps an accumulated total of stats, unlike Bluetooth.
121 // Keep the last WiFi stats so we can compute a delta.
122 @GuardedBy("mWorkerLock")
123 private WifiActivityEnergyInfo mLastInfo =
Siddharth Rayb50a6842017-12-14 15:15:28 -0800124 new WifiActivityEnergyInfo(0, 0, 0, new long[]{0}, 0, 0, 0, 0);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700125
Sudheer Shankae56013a2018-04-23 11:22:15 -0700126 /**
127 * Timestamp at which all external stats were last collected in
128 * {@link SystemClock#elapsedRealtime()} time base.
129 */
130 @GuardedBy("this")
131 private long mLastCollectionTimeStamp;
132
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700133 BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) {
134 mContext = context;
135 mStats = stats;
136 }
137
138 @Override
139 public synchronized Future<?> scheduleSync(String reason, int flags) {
140 return scheduleSyncLocked(reason, flags);
141 }
142
143 @Override
144 public synchronized Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
145 mUidsToRemove.add(uid);
146 return scheduleSyncLocked("remove-uid", UPDATE_CPU);
147 }
148
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800149 @Override
Sudheer Shanka5c19b892018-01-05 17:25:46 -0800150 public synchronized Future<?> scheduleCpuSyncDueToSettingChange() {
151 return scheduleSyncLocked("setting-change", UPDATE_CPU);
152 }
153
154 @Override
Sudheer Shankac20379e2018-02-15 00:06:21 -0800155 public Future<?> scheduleReadProcStateCpuTimes(
156 boolean onBattery, boolean onBatteryScreenOff, long delayMillis) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800157 synchronized (mStats) {
Sudheer Shanka5c19b892018-01-05 17:25:46 -0800158 if (!mStats.trackPerProcStateCpuTimes()) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800159 return null;
160 }
161 }
162 synchronized (BatteryExternalStatsWorker.this) {
163 if (!mExecutorService.isShutdown()) {
Sudheer Shankac20379e2018-02-15 00:06:21 -0800164 return mExecutorService.schedule(PooledLambda.obtainRunnable(
Sudheer Shankae544d162017-12-28 17:06:20 -0800165 BatteryStatsImpl::updateProcStateCpuTimes,
Sudheer Shankac20379e2018-02-15 00:06:21 -0800166 mStats, onBattery, onBatteryScreenOff).recycleOnUse(),
167 delayMillis, TimeUnit.MILLISECONDS);
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800168 }
169 }
170 return null;
171 }
172
173 @Override
Sudheer Shankae544d162017-12-28 17:06:20 -0800174 public Future<?> scheduleCopyFromAllUidsCpuTimes(
175 boolean onBattery, boolean onBatteryScreenOff) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800176 synchronized (mStats) {
Sudheer Shanka5c19b892018-01-05 17:25:46 -0800177 if (!mStats.trackPerProcStateCpuTimes()) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800178 return null;
179 }
180 }
181 synchronized (BatteryExternalStatsWorker.this) {
182 if (!mExecutorService.isShutdown()) {
Sudheer Shankae544d162017-12-28 17:06:20 -0800183 return mExecutorService.submit(PooledLambda.obtainRunnable(
184 BatteryStatsImpl::copyFromAllUidsCpuTimes,
185 mStats, onBattery, onBatteryScreenOff).recycleOnUse());
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800186 }
187 }
188 return null;
189 }
190
Sudheer Shankac57729a2018-02-09 15:44:42 -0800191 @Override
192 public Future<?> scheduleCpuSyncDueToScreenStateChange(
193 boolean onBattery, boolean onBatteryScreenOff) {
194 synchronized (BatteryExternalStatsWorker.this) {
195 if (mCurrentFuture == null || (mUpdateFlags & UPDATE_CPU) == 0) {
196 mOnBattery = onBattery;
197 mOnBatteryScreenOff = onBatteryScreenOff;
198 mUseLatestStates = false;
199 }
200 return scheduleSyncLocked("screen-state", UPDATE_CPU);
201 }
202 }
203
204 @Override
205 public Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis) {
Sudheer Shanka0719c6a2018-04-24 11:12:11 -0700206 synchronized (BatteryExternalStatsWorker.this) {
207 mWakelockChangesUpdate = scheduleDelayedSyncLocked(mWakelockChangesUpdate,
208 () -> {
209 scheduleSync("wakelock-change", UPDATE_CPU);
210 scheduleRunnable(() -> mStats.postBatteryNeedsCpuUpdateMsg());
211 },
212 delayMillis);
213 return mWakelockChangesUpdate;
Sudheer Shankac57729a2018-02-09 15:44:42 -0800214 }
Sudheer Shankac57729a2018-02-09 15:44:42 -0800215 }
216
217 @Override
218 public void cancelCpuSyncDueToWakelockChange() {
Sudheer Shanka0719c6a2018-04-24 11:12:11 -0700219 synchronized (BatteryExternalStatsWorker.this) {
220 if (mWakelockChangesUpdate != null) {
221 mWakelockChangesUpdate.cancel(false);
222 mWakelockChangesUpdate = null;
223 }
Sudheer Shankac57729a2018-02-09 15:44:42 -0800224 }
225 }
226
Sudheer Shanka0719c6a2018-04-24 11:12:11 -0700227 @Override
228 public Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis) {
229 synchronized (BatteryExternalStatsWorker.this) {
230 mBatteryLevelSync = scheduleDelayedSyncLocked(mBatteryLevelSync,
231 () -> scheduleSync("battery-level", UPDATE_ALL),
232 delayMillis);
233 return mBatteryLevelSync;
234 }
235 }
236
237 @GuardedBy("this")
238 private void cancelSyncDueToBatteryLevelChangeLocked() {
239 if (mBatteryLevelSync != null) {
240 mBatteryLevelSync.cancel(false);
241 mBatteryLevelSync = null;
242 }
243 }
244
245 /**
246 * Schedule a sync {@param syncRunnable} with a delay. If there's already a scheduled sync, a
247 * new sync won't be scheduled unless it is being scheduled to run immediately (delayMillis=0).
248 *
249 * @param lastScheduledSync the task which was earlier scheduled to run
250 * @param syncRunnable the task that needs to be scheduled to run
251 * @param delayMillis time after which {@param syncRunnable} needs to be scheduled
252 * @return scheduled {@link Future} which can be used to check if task is completed or to
253 * cancel it if needed
254 */
255 @GuardedBy("this")
256 private Future<?> scheduleDelayedSyncLocked(Future<?> lastScheduledSync, Runnable syncRunnable,
257 long delayMillis) {
258 if (mExecutorService.isShutdown()) {
259 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
260 }
261
262 if (lastScheduledSync != null) {
263 // If there's already a scheduled task, leave it as is if we're trying to
264 // re-schedule it again with a delay, otherwise cancel and re-schedule it.
265 if (delayMillis == 0) {
266 lastScheduledSync.cancel(false);
267 } else {
268 return lastScheduledSync;
269 }
270 }
271
272 return mExecutorService.schedule(syncRunnable, delayMillis, TimeUnit.MILLISECONDS);
273 }
274
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700275 public synchronized Future<?> scheduleWrite() {
Adam Lesinskicf0b2b62017-08-29 19:30:31 -0700276 if (mExecutorService.isShutdown()) {
277 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
278 }
279
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700280 scheduleSyncLocked("write", UPDATE_ALL);
281 // Since we use a single threaded executor, we can assume the next scheduled task's
282 // Future finishes after the sync.
283 return mExecutorService.submit(mWriteTask);
284 }
285
286 /**
287 * Schedules a task to run on the BatteryExternalStatsWorker thread. If scheduling more work
288 * within the task, never wait on the resulting Future. This will result in a deadlock.
289 */
290 public synchronized void scheduleRunnable(Runnable runnable) {
Adam Lesinskicf0b2b62017-08-29 19:30:31 -0700291 if (!mExecutorService.isShutdown()) {
292 mExecutorService.submit(runnable);
293 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700294 }
295
296 public void shutdown() {
297 mExecutorService.shutdownNow();
298 }
299
Andreas Gampea36dc622018-02-05 17:19:22 -0800300 @GuardedBy("this")
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700301 private Future<?> scheduleSyncLocked(String reason, int flags) {
Adam Lesinskicf0b2b62017-08-29 19:30:31 -0700302 if (mExecutorService.isShutdown()) {
303 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
304 }
305
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700306 if (mCurrentFuture == null) {
307 mUpdateFlags = flags;
308 mCurrentReason = reason;
309 mCurrentFuture = mExecutorService.submit(mSyncTask);
310 }
311 mUpdateFlags |= flags;
312 return mCurrentFuture;
313 }
314
Sudheer Shankae56013a2018-04-23 11:22:15 -0700315 long getLastCollectionTimeStamp() {
316 synchronized (this) {
317 return mLastCollectionTimeStamp;
318 }
319 }
320
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700321 private final Runnable mSyncTask = new Runnable() {
322 @Override
323 public void run() {
324 // Capture a snapshot of the state we are meant to process.
325 final int updateFlags;
326 final String reason;
327 final int[] uidsToRemove;
Sudheer Shankac57729a2018-02-09 15:44:42 -0800328 final boolean onBattery;
329 final boolean onBatteryScreenOff;
330 final boolean useLatestStates;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700331 synchronized (BatteryExternalStatsWorker.this) {
332 updateFlags = mUpdateFlags;
333 reason = mCurrentReason;
334 uidsToRemove = mUidsToRemove.size() > 0 ? mUidsToRemove.toArray() : EmptyArray.INT;
Sudheer Shankac57729a2018-02-09 15:44:42 -0800335 onBattery = mOnBattery;
336 onBatteryScreenOff = mOnBatteryScreenOff;
337 useLatestStates = mUseLatestStates;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700338 mUpdateFlags = 0;
339 mCurrentReason = null;
340 mUidsToRemove.clear();
341 mCurrentFuture = null;
Sudheer Shankac57729a2018-02-09 15:44:42 -0800342 mUseLatestStates = true;
Sudheer Shanka0719c6a2018-04-24 11:12:11 -0700343 if ((updateFlags & UPDATE_ALL) != 0) {
344 cancelSyncDueToBatteryLevelChangeLocked();
345 }
346 if ((updateFlags & UPDATE_CPU) != 0) {
347 cancelCpuSyncDueToWakelockChange();
348 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700349 }
350
Mike Ma561a8d92018-03-20 18:24:05 -0700351 try {
352 synchronized (mWorkerLock) {
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700353 if (DEBUG) {
Mike Ma561a8d92018-03-20 18:24:05 -0700354 Slog.d(TAG, "begin updateExternalStatsSync reason=" + reason);
355 }
356 try {
357 updateExternalStatsLocked(reason, updateFlags, onBattery,
358 onBatteryScreenOff, useLatestStates);
359 } finally {
360 if (DEBUG) {
361 Slog.d(TAG, "end updateExternalStatsSync");
362 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700363 }
364 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700365
Mike Ma561a8d92018-03-20 18:24:05 -0700366 if ((updateFlags & UPDATE_CPU) != 0) {
367 mStats.copyFromAllUidsCpuTimes();
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700368 }
Mike Ma561a8d92018-03-20 18:24:05 -0700369
370 // Clean up any UIDs if necessary.
371 synchronized (mStats) {
372 for (int uid : uidsToRemove) {
Bookatz3c648862018-05-25 13:32:43 -0700373 StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, -1, uid,
374 StatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED);
Mike Ma561a8d92018-03-20 18:24:05 -0700375 mStats.removeIsolatedUidLocked(uid);
376 }
377 mStats.clearPendingRemovedUids();
378 }
379 } catch (Exception e) {
380 Slog.wtf(TAG, "Error updating external stats: ", e);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700381 }
Sudheer Shankae56013a2018-04-23 11:22:15 -0700382
383 synchronized (BatteryExternalStatsWorker.this) {
384 mLastCollectionTimeStamp = SystemClock.elapsedRealtime();
385 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700386 }
387 };
388
389 private final Runnable mWriteTask = new Runnable() {
390 @Override
391 public void run() {
392 synchronized (mStats) {
393 mStats.writeAsyncLocked();
394 }
395 }
396 };
397
Andreas Gampea36dc622018-02-05 17:19:22 -0800398 @GuardedBy("mWorkerLock")
Sudheer Shankac57729a2018-02-09 15:44:42 -0800399 private void updateExternalStatsLocked(final String reason, int updateFlags,
400 boolean onBattery, boolean onBatteryScreenOff, boolean useLatestStates) {
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700401 // We will request data from external processes asynchronously, and wait on a timeout.
402 SynchronousResultReceiver wifiReceiver = null;
403 SynchronousResultReceiver bluetoothReceiver = null;
404 SynchronousResultReceiver modemReceiver = null;
405
406 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
407 // We were asked to fetch WiFi data.
408 if (mWifiManager == null) {
409 mWifiManager = IWifiManager.Stub.asInterface(ServiceManager.getService(
410 Context.WIFI_SERVICE));
411 }
412
413 if (mWifiManager != null) {
414 try {
Ben Lina8c34e12018-07-20 16:35:39 -0700415 // Only fetch WiFi power data if it is supported.
416 if ((mWifiManager.getSupportedFeatures() & WIFI_FEATURE_LINK_LAYER_STATS) != 0) {
417 wifiReceiver = new SynchronousResultReceiver("wifi");
418 mWifiManager.requestActivityInfo(wifiReceiver);
419 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700420 } catch (RemoteException e) {
421 // Oh well.
422 }
423 }
424 }
425
426 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) {
427 // We were asked to fetch Bluetooth data.
428 final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
429 if (adapter != null) {
430 bluetoothReceiver = new SynchronousResultReceiver("bluetooth");
431 adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
432 }
433 }
434
435 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) {
436 // We were asked to fetch Telephony data.
437 if (mTelephony == null) {
438 mTelephony = TelephonyManager.from(mContext);
439 }
440
441 if (mTelephony != null) {
442 modemReceiver = new SynchronousResultReceiver("telephony");
443 mTelephony.requestModemActivityInfo(modemReceiver);
444 }
445 }
446
447 final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
448 final BluetoothActivityEnergyInfo bluetoothInfo = awaitControllerInfo(bluetoothReceiver);
449 final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
450
451 synchronized (mStats) {
452 mStats.addHistoryEventLocked(
453 SystemClock.elapsedRealtime(),
454 SystemClock.uptimeMillis(),
455 BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
456 reason, 0);
457
458 if ((updateFlags & UPDATE_CPU) != 0) {
Sudheer Shankac57729a2018-02-09 15:44:42 -0800459 if (useLatestStates) {
460 onBattery = mStats.isOnBatteryLocked();
461 onBatteryScreenOff = mStats.isOnBatteryScreenOffLocked();
462 }
463 mStats.updateCpuTimeLocked(onBattery, onBatteryScreenOff);
464 }
465
466 if ((updateFlags & UPDATE_ALL) != 0) {
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700467 mStats.updateKernelWakelocksLocked();
468 mStats.updateKernelMemoryBandwidthLocked();
469 }
470
Bookatz50df7112017-08-04 14:53:26 -0700471 if ((updateFlags & UPDATE_RPM) != 0) {
472 mStats.updateRpmStatsLocked();
473 }
474
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700475 if (bluetoothInfo != null) {
476 if (bluetoothInfo.isValid()) {
477 mStats.updateBluetoothStateLocked(bluetoothInfo);
478 } else {
Mike Ma561a8d92018-03-20 18:24:05 -0700479 Slog.w(TAG, "bluetooth info is invalid: " + bluetoothInfo);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700480 }
481 }
482 }
483
484 // WiFi and Modem state are updated without the mStats lock held, because they
485 // do some network stats retrieval before internally grabbing the mStats lock.
486
487 if (wifiInfo != null) {
488 if (wifiInfo.isValid()) {
489 mStats.updateWifiState(extractDeltaLocked(wifiInfo));
490 } else {
Mike Ma561a8d92018-03-20 18:24:05 -0700491 Slog.w(TAG, "wifi info is invalid: " + wifiInfo);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700492 }
493 }
494
495 if (modemInfo != null) {
496 if (modemInfo.isValid()) {
497 mStats.updateMobileRadioState(modemInfo);
498 } else {
Mike Ma561a8d92018-03-20 18:24:05 -0700499 Slog.w(TAG, "modem info is invalid: " + modemInfo);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700500 }
501 }
502 }
503
504 /**
505 * Helper method to extract the Parcelable controller info from a
506 * SynchronousResultReceiver.
507 */
508 private static <T extends Parcelable> T awaitControllerInfo(
509 @Nullable SynchronousResultReceiver receiver) {
510 if (receiver == null) {
511 return null;
512 }
513
514 try {
515 final SynchronousResultReceiver.Result result =
516 receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
517 if (result.bundle != null) {
518 // This is the final destination for the Bundle.
519 result.bundle.setDefusable(true);
520
521 final T data = result.bundle.getParcelable(
522 BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY);
523 if (data != null) {
524 return data;
525 }
526 }
527 Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
528 } catch (TimeoutException e) {
529 Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
530 }
531 return null;
532 }
533
Andreas Gampea36dc622018-02-05 17:19:22 -0800534 @GuardedBy("mWorkerLock")
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700535 private WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) {
536 final long timePeriodMs = latest.mTimestamp - mLastInfo.mTimestamp;
Siddharth Rayb50a6842017-12-14 15:15:28 -0800537 final long lastScanMs = mLastInfo.mControllerScanTimeMs;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700538 final long lastIdleMs = mLastInfo.mControllerIdleTimeMs;
539 final long lastTxMs = mLastInfo.mControllerTxTimeMs;
540 final long lastRxMs = mLastInfo.mControllerRxTimeMs;
541 final long lastEnergy = mLastInfo.mControllerEnergyUsed;
542
543 // We will modify the last info object to be the delta, and store the new
544 // WifiActivityEnergyInfo object as our last one.
545 final WifiActivityEnergyInfo delta = mLastInfo;
546 delta.mTimestamp = latest.getTimeStamp();
547 delta.mStackState = latest.getStackState();
548
549 final long txTimeMs = latest.mControllerTxTimeMs - lastTxMs;
550 final long rxTimeMs = latest.mControllerRxTimeMs - lastRxMs;
551 final long idleTimeMs = latest.mControllerIdleTimeMs - lastIdleMs;
Siddharth Rayb50a6842017-12-14 15:15:28 -0800552 final long scanTimeMs = latest.mControllerScanTimeMs - lastScanMs;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700553
Siddharth Ray97f62bf2018-06-17 18:28:44 -0700554 if (txTimeMs < 0 || rxTimeMs < 0 || scanTimeMs < 0 || idleTimeMs < 0) {
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700555 // The stats were reset by the WiFi system (which is why our delta is negative).
Siddharth Ray97f62bf2018-06-17 18:28:44 -0700556 // Returns the unaltered stats. The total on time should not exceed the time
557 // duartion between reports.
558 final long totalOnTimeMs = latest.mControllerTxTimeMs + latest.mControllerRxTimeMs
559 + latest.mControllerIdleTimeMs;
560 if (totalOnTimeMs <= timePeriodMs + MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS) {
561 delta.mControllerEnergyUsed = latest.mControllerEnergyUsed;
562 delta.mControllerRxTimeMs = latest.mControllerRxTimeMs;
563 delta.mControllerTxTimeMs = latest.mControllerTxTimeMs;
564 delta.mControllerIdleTimeMs = latest.mControllerIdleTimeMs;
565 delta.mControllerScanTimeMs = latest.mControllerScanTimeMs;
566 } else {
567 delta.mControllerEnergyUsed = 0;
568 delta.mControllerRxTimeMs = 0;
569 delta.mControllerTxTimeMs = 0;
570 delta.mControllerIdleTimeMs = 0;
571 delta.mControllerScanTimeMs = 0;
572 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700573 Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + delta);
574 } else {
575 final long totalActiveTimeMs = txTimeMs + rxTimeMs;
576 long maxExpectedIdleTimeMs;
577 if (totalActiveTimeMs > timePeriodMs) {
578 // Cap the max idle time at zero since the active time consumed the whole time
579 maxExpectedIdleTimeMs = 0;
580 if (totalActiveTimeMs > timePeriodMs + MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS) {
581 StringBuilder sb = new StringBuilder();
582 sb.append("Total Active time ");
583 TimeUtils.formatDuration(totalActiveTimeMs, sb);
584 sb.append(" is longer than sample period ");
585 TimeUtils.formatDuration(timePeriodMs, sb);
586 sb.append(".\n");
587 sb.append("Previous WiFi snapshot: ").append("idle=");
588 TimeUtils.formatDuration(lastIdleMs, sb);
589 sb.append(" rx=");
590 TimeUtils.formatDuration(lastRxMs, sb);
591 sb.append(" tx=");
592 TimeUtils.formatDuration(lastTxMs, sb);
593 sb.append(" e=").append(lastEnergy);
594 sb.append("\n");
595 sb.append("Current WiFi snapshot: ").append("idle=");
596 TimeUtils.formatDuration(latest.mControllerIdleTimeMs, sb);
597 sb.append(" rx=");
598 TimeUtils.formatDuration(latest.mControllerRxTimeMs, sb);
599 sb.append(" tx=");
600 TimeUtils.formatDuration(latest.mControllerTxTimeMs, sb);
601 sb.append(" e=").append(latest.mControllerEnergyUsed);
602 Slog.wtf(TAG, sb.toString());
603 }
604 } else {
605 maxExpectedIdleTimeMs = timePeriodMs - totalActiveTimeMs;
606 }
607 // These times seem to be the most reliable.
608 delta.mControllerTxTimeMs = txTimeMs;
609 delta.mControllerRxTimeMs = rxTimeMs;
Siddharth Rayb50a6842017-12-14 15:15:28 -0800610 delta.mControllerScanTimeMs = scanTimeMs;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700611 // WiFi calculates the idle time as a difference from the on time and the various
612 // Rx + Tx times. There seems to be some missing time there because this sometimes
613 // becomes negative. Just cap it at 0 and ensure that it is less than the expected idle
614 // time from the difference in timestamps.
615 // b/21613534
616 delta.mControllerIdleTimeMs = Math.min(maxExpectedIdleTimeMs, Math.max(0, idleTimeMs));
617 delta.mControllerEnergyUsed = Math.max(0, latest.mControllerEnergyUsed - lastEnergy);
618 }
619
620 mLastInfo = latest;
621 return delta;
622 }
623}