blob: 1fcaeef72dba4935816c256724b581526e85179a [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;
34import android.util.TimeUtils;
35
36import com.android.internal.annotations.GuardedBy;
37import com.android.internal.os.BatteryStatsImpl;
Sudheer Shankae544d162017-12-28 17:06:20 -080038import com.android.internal.util.function.pooled.PooledLambda;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070039
40import libcore.util.EmptyArray;
41
Adam Lesinskicf0b2b62017-08-29 19:30:31 -070042import java.util.concurrent.CompletableFuture;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070043import java.util.concurrent.ExecutorService;
44import java.util.concurrent.Executors;
45import java.util.concurrent.Future;
46import java.util.concurrent.ThreadFactory;
47import java.util.concurrent.TimeoutException;
48
49/**
50 * A Worker that fetches data from external sources (WiFi controller, bluetooth chipset) on a
51 * dedicated thread and updates BatteryStatsImpl with that information.
52 *
53 * As much work as possible is done without holding the BatteryStatsImpl lock, and only the
54 * readily available data is pushed into BatteryStatsImpl with the lock held.
55 */
56class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
57 private static final String TAG = "BatteryExternalStatsWorker";
58 private static final boolean DEBUG = false;
59
60 /**
61 * How long to wait on an individual subsystem to return its stats.
62 */
63 private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000;
64
65 // There is some accuracy error in wifi reports so allow some slop in the results.
66 private static final long MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS = 750;
67
68 private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor(
69 (ThreadFactory) r -> {
70 Thread t = new Thread(r, "batterystats-worker");
71 t.setPriority(Thread.NORM_PRIORITY);
72 return t;
73 });
74
75 private final Context mContext;
76 private final BatteryStatsImpl mStats;
77
78 @GuardedBy("this")
79 private int mUpdateFlags = 0;
80
81 @GuardedBy("this")
82 private Future<?> mCurrentFuture = null;
83
84 @GuardedBy("this")
85 private String mCurrentReason = null;
86
87 @GuardedBy("this")
88 private final IntArray mUidsToRemove = new IntArray();
89
90 private final Object mWorkerLock = new Object();
91
92 @GuardedBy("mWorkerLock")
93 private IWifiManager mWifiManager = null;
94
95 @GuardedBy("mWorkerLock")
96 private TelephonyManager mTelephony = null;
97
98 // WiFi keeps an accumulated total of stats, unlike Bluetooth.
99 // Keep the last WiFi stats so we can compute a delta.
100 @GuardedBy("mWorkerLock")
101 private WifiActivityEnergyInfo mLastInfo =
102 new WifiActivityEnergyInfo(0, 0, 0, new long[]{0}, 0, 0, 0);
103
104 BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) {
105 mContext = context;
106 mStats = stats;
107 }
108
109 @Override
110 public synchronized Future<?> scheduleSync(String reason, int flags) {
111 return scheduleSyncLocked(reason, flags);
112 }
113
114 @Override
115 public synchronized Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
116 mUidsToRemove.add(uid);
117 return scheduleSyncLocked("remove-uid", UPDATE_CPU);
118 }
119
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800120 @Override
Sudheer Shanka5c19b892018-01-05 17:25:46 -0800121 public synchronized Future<?> scheduleCpuSyncDueToSettingChange() {
122 return scheduleSyncLocked("setting-change", UPDATE_CPU);
123 }
124
125 @Override
Sudheer Shankae544d162017-12-28 17:06:20 -0800126 public Future<?> scheduleReadProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800127 synchronized (mStats) {
Sudheer Shanka5c19b892018-01-05 17:25:46 -0800128 if (!mStats.trackPerProcStateCpuTimes()) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800129 return null;
130 }
131 }
132 synchronized (BatteryExternalStatsWorker.this) {
133 if (!mExecutorService.isShutdown()) {
Sudheer Shankae544d162017-12-28 17:06:20 -0800134 return mExecutorService.submit(PooledLambda.obtainRunnable(
135 BatteryStatsImpl::updateProcStateCpuTimes,
136 mStats, onBattery, onBatteryScreenOff).recycleOnUse());
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800137 }
138 }
139 return null;
140 }
141
142 @Override
Sudheer Shankae544d162017-12-28 17:06:20 -0800143 public Future<?> scheduleCopyFromAllUidsCpuTimes(
144 boolean onBattery, boolean onBatteryScreenOff) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800145 synchronized (mStats) {
Sudheer Shanka5c19b892018-01-05 17:25:46 -0800146 if (!mStats.trackPerProcStateCpuTimes()) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800147 return null;
148 }
149 }
150 synchronized (BatteryExternalStatsWorker.this) {
151 if (!mExecutorService.isShutdown()) {
Sudheer Shankae544d162017-12-28 17:06:20 -0800152 return mExecutorService.submit(PooledLambda.obtainRunnable(
153 BatteryStatsImpl::copyFromAllUidsCpuTimes,
154 mStats, onBattery, onBatteryScreenOff).recycleOnUse());
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800155 }
156 }
157 return null;
158 }
159
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700160 public synchronized Future<?> scheduleWrite() {
Adam Lesinskicf0b2b62017-08-29 19:30:31 -0700161 if (mExecutorService.isShutdown()) {
162 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
163 }
164
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700165 scheduleSyncLocked("write", UPDATE_ALL);
166 // Since we use a single threaded executor, we can assume the next scheduled task's
167 // Future finishes after the sync.
168 return mExecutorService.submit(mWriteTask);
169 }
170
171 /**
172 * Schedules a task to run on the BatteryExternalStatsWorker thread. If scheduling more work
173 * within the task, never wait on the resulting Future. This will result in a deadlock.
174 */
175 public synchronized void scheduleRunnable(Runnable runnable) {
Adam Lesinskicf0b2b62017-08-29 19:30:31 -0700176 if (!mExecutorService.isShutdown()) {
177 mExecutorService.submit(runnable);
178 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700179 }
180
181 public void shutdown() {
182 mExecutorService.shutdownNow();
183 }
184
185 private Future<?> scheduleSyncLocked(String reason, int flags) {
Adam Lesinskicf0b2b62017-08-29 19:30:31 -0700186 if (mExecutorService.isShutdown()) {
187 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
188 }
189
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700190 if (mCurrentFuture == null) {
191 mUpdateFlags = flags;
192 mCurrentReason = reason;
193 mCurrentFuture = mExecutorService.submit(mSyncTask);
194 }
195 mUpdateFlags |= flags;
196 return mCurrentFuture;
197 }
198
199 private final Runnable mSyncTask = new Runnable() {
200 @Override
201 public void run() {
202 // Capture a snapshot of the state we are meant to process.
203 final int updateFlags;
204 final String reason;
205 final int[] uidsToRemove;
206 synchronized (BatteryExternalStatsWorker.this) {
207 updateFlags = mUpdateFlags;
208 reason = mCurrentReason;
209 uidsToRemove = mUidsToRemove.size() > 0 ? mUidsToRemove.toArray() : EmptyArray.INT;
210 mUpdateFlags = 0;
211 mCurrentReason = null;
212 mUidsToRemove.clear();
213 mCurrentFuture = null;
214 }
215
216 synchronized (mWorkerLock) {
217 if (DEBUG) {
218 Slog.d(TAG, "begin updateExternalStatsSync reason=" + reason);
219 }
220 try {
221 updateExternalStatsLocked(reason, updateFlags);
222 } finally {
223 if (DEBUG) {
224 Slog.d(TAG, "end updateExternalStatsSync");
225 }
226 }
227 }
228
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800229 if ((updateFlags & UPDATE_CPU) != 0) {
230 mStats.copyFromAllUidsCpuTimes();
231 }
232
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700233 // Clean up any UIDs if necessary.
234 synchronized (mStats) {
235 for (int uid : uidsToRemove) {
236 mStats.removeIsolatedUidLocked(uid);
237 }
238 }
239 }
240 };
241
242 private final Runnable mWriteTask = new Runnable() {
243 @Override
244 public void run() {
245 synchronized (mStats) {
246 mStats.writeAsyncLocked();
247 }
248 }
249 };
250
251 private void updateExternalStatsLocked(final String reason, int updateFlags) {
252 // We will request data from external processes asynchronously, and wait on a timeout.
253 SynchronousResultReceiver wifiReceiver = null;
254 SynchronousResultReceiver bluetoothReceiver = null;
255 SynchronousResultReceiver modemReceiver = null;
256
257 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
258 // We were asked to fetch WiFi data.
259 if (mWifiManager == null) {
260 mWifiManager = IWifiManager.Stub.asInterface(ServiceManager.getService(
261 Context.WIFI_SERVICE));
262 }
263
264 if (mWifiManager != null) {
265 try {
266 wifiReceiver = new SynchronousResultReceiver("wifi");
267 mWifiManager.requestActivityInfo(wifiReceiver);
268 } catch (RemoteException e) {
269 // Oh well.
270 }
271 }
272 }
273
274 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) {
275 // We were asked to fetch Bluetooth data.
276 final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
277 if (adapter != null) {
278 bluetoothReceiver = new SynchronousResultReceiver("bluetooth");
279 adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
280 }
281 }
282
283 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) {
284 // We were asked to fetch Telephony data.
285 if (mTelephony == null) {
286 mTelephony = TelephonyManager.from(mContext);
287 }
288
289 if (mTelephony != null) {
290 modemReceiver = new SynchronousResultReceiver("telephony");
291 mTelephony.requestModemActivityInfo(modemReceiver);
292 }
293 }
294
295 final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
296 final BluetoothActivityEnergyInfo bluetoothInfo = awaitControllerInfo(bluetoothReceiver);
297 final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
298
299 synchronized (mStats) {
300 mStats.addHistoryEventLocked(
301 SystemClock.elapsedRealtime(),
302 SystemClock.uptimeMillis(),
303 BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
304 reason, 0);
305
306 if ((updateFlags & UPDATE_CPU) != 0) {
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700307 mStats.updateCpuTimeLocked();
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700308 mStats.updateKernelWakelocksLocked();
309 mStats.updateKernelMemoryBandwidthLocked();
310 }
311
Bookatz50df7112017-08-04 14:53:26 -0700312 if ((updateFlags & UPDATE_RPM) != 0) {
313 mStats.updateRpmStatsLocked();
314 }
315
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700316 if (bluetoothInfo != null) {
317 if (bluetoothInfo.isValid()) {
318 mStats.updateBluetoothStateLocked(bluetoothInfo);
319 } else {
320 Slog.e(TAG, "bluetooth info is invalid: " + bluetoothInfo);
321 }
322 }
323 }
324
325 // WiFi and Modem state are updated without the mStats lock held, because they
326 // do some network stats retrieval before internally grabbing the mStats lock.
327
328 if (wifiInfo != null) {
329 if (wifiInfo.isValid()) {
330 mStats.updateWifiState(extractDeltaLocked(wifiInfo));
331 } else {
332 Slog.e(TAG, "wifi info is invalid: " + wifiInfo);
333 }
334 }
335
336 if (modemInfo != null) {
337 if (modemInfo.isValid()) {
338 mStats.updateMobileRadioState(modemInfo);
339 } else {
340 Slog.e(TAG, "modem info is invalid: " + modemInfo);
341 }
342 }
343 }
344
345 /**
346 * Helper method to extract the Parcelable controller info from a
347 * SynchronousResultReceiver.
348 */
349 private static <T extends Parcelable> T awaitControllerInfo(
350 @Nullable SynchronousResultReceiver receiver) {
351 if (receiver == null) {
352 return null;
353 }
354
355 try {
356 final SynchronousResultReceiver.Result result =
357 receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
358 if (result.bundle != null) {
359 // This is the final destination for the Bundle.
360 result.bundle.setDefusable(true);
361
362 final T data = result.bundle.getParcelable(
363 BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY);
364 if (data != null) {
365 return data;
366 }
367 }
368 Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
369 } catch (TimeoutException e) {
370 Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
371 }
372 return null;
373 }
374
375 private WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) {
376 final long timePeriodMs = latest.mTimestamp - mLastInfo.mTimestamp;
377 final long lastIdleMs = mLastInfo.mControllerIdleTimeMs;
378 final long lastTxMs = mLastInfo.mControllerTxTimeMs;
379 final long lastRxMs = mLastInfo.mControllerRxTimeMs;
380 final long lastEnergy = mLastInfo.mControllerEnergyUsed;
381
382 // We will modify the last info object to be the delta, and store the new
383 // WifiActivityEnergyInfo object as our last one.
384 final WifiActivityEnergyInfo delta = mLastInfo;
385 delta.mTimestamp = latest.getTimeStamp();
386 delta.mStackState = latest.getStackState();
387
388 final long txTimeMs = latest.mControllerTxTimeMs - lastTxMs;
389 final long rxTimeMs = latest.mControllerRxTimeMs - lastRxMs;
390 final long idleTimeMs = latest.mControllerIdleTimeMs - lastIdleMs;
391
392 if (txTimeMs < 0 || rxTimeMs < 0) {
393 // The stats were reset by the WiFi system (which is why our delta is negative).
394 // Returns the unaltered stats.
395 delta.mControllerEnergyUsed = latest.mControllerEnergyUsed;
396 delta.mControllerRxTimeMs = latest.mControllerRxTimeMs;
397 delta.mControllerTxTimeMs = latest.mControllerTxTimeMs;
398 delta.mControllerIdleTimeMs = latest.mControllerIdleTimeMs;
399 Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + delta);
400 } else {
401 final long totalActiveTimeMs = txTimeMs + rxTimeMs;
402 long maxExpectedIdleTimeMs;
403 if (totalActiveTimeMs > timePeriodMs) {
404 // Cap the max idle time at zero since the active time consumed the whole time
405 maxExpectedIdleTimeMs = 0;
406 if (totalActiveTimeMs > timePeriodMs + MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS) {
407 StringBuilder sb = new StringBuilder();
408 sb.append("Total Active time ");
409 TimeUtils.formatDuration(totalActiveTimeMs, sb);
410 sb.append(" is longer than sample period ");
411 TimeUtils.formatDuration(timePeriodMs, sb);
412 sb.append(".\n");
413 sb.append("Previous WiFi snapshot: ").append("idle=");
414 TimeUtils.formatDuration(lastIdleMs, sb);
415 sb.append(" rx=");
416 TimeUtils.formatDuration(lastRxMs, sb);
417 sb.append(" tx=");
418 TimeUtils.formatDuration(lastTxMs, sb);
419 sb.append(" e=").append(lastEnergy);
420 sb.append("\n");
421 sb.append("Current WiFi snapshot: ").append("idle=");
422 TimeUtils.formatDuration(latest.mControllerIdleTimeMs, sb);
423 sb.append(" rx=");
424 TimeUtils.formatDuration(latest.mControllerRxTimeMs, sb);
425 sb.append(" tx=");
426 TimeUtils.formatDuration(latest.mControllerTxTimeMs, sb);
427 sb.append(" e=").append(latest.mControllerEnergyUsed);
428 Slog.wtf(TAG, sb.toString());
429 }
430 } else {
431 maxExpectedIdleTimeMs = timePeriodMs - totalActiveTimeMs;
432 }
433 // These times seem to be the most reliable.
434 delta.mControllerTxTimeMs = txTimeMs;
435 delta.mControllerRxTimeMs = rxTimeMs;
436 // WiFi calculates the idle time as a difference from the on time and the various
437 // Rx + Tx times. There seems to be some missing time there because this sometimes
438 // becomes negative. Just cap it at 0 and ensure that it is less than the expected idle
439 // time from the difference in timestamps.
440 // b/21613534
441 delta.mControllerIdleTimeMs = Math.min(maxExpectedIdleTimeMs, Math.max(0, idleTimeMs));
442 delta.mControllerEnergyUsed = Math.max(0, latest.mControllerEnergyUsed - lastEnergy);
443 }
444
445 mLastInfo = latest;
446 return delta;
447 }
448}