--- /dev/null
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.android.server.am;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.net.wifi.IWifiManager;
+import android.net.wifi.WifiActivityEnergyInfo;
+import android.os.BatteryStats;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SynchronousResultReceiver;
+import android.os.SystemClock;
+import android.telephony.ModemActivityInfo;
+import android.telephony.TelephonyManager;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BatteryStatsImpl;
+
+import libcore.util.EmptyArray;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A Worker that fetches data from external sources (WiFi controller, bluetooth chipset) on a
+ * dedicated thread and updates BatteryStatsImpl with that information.
+ *
+ * As much work as possible is done without holding the BatteryStatsImpl lock, and only the
+ * readily available data is pushed into BatteryStatsImpl with the lock held.
+ */
+class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
+ private static final String TAG = "BatteryExternalStatsWorker";
+ private static final boolean DEBUG = false;
+
+ /**
+ * How long to wait on an individual subsystem to return its stats.
+ */
+ private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000;
+
+ // There is some accuracy error in wifi reports so allow some slop in the results.
+ private static final long MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS = 750;
+
+ private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor(
+ (ThreadFactory) r -> {
+ Thread t = new Thread(r, "batterystats-worker");
+ t.setPriority(Thread.NORM_PRIORITY);
+ return t;
+ });
+
+ private final Context mContext;
+ private final BatteryStatsImpl mStats;
+
+ @GuardedBy("this")
+ private int mUpdateFlags = 0;
+
+ @GuardedBy("this")
+ private Future<?> mCurrentFuture = null;
+
+ @GuardedBy("this")
+ private String mCurrentReason = null;
+
+ @GuardedBy("this")
+ private final IntArray mUidsToRemove = new IntArray();
+
+ private final Object mWorkerLock = new Object();
+
+ @GuardedBy("mWorkerLock")
+ private IWifiManager mWifiManager = null;
+
+ @GuardedBy("mWorkerLock")
+ private TelephonyManager mTelephony = null;
+
+ // WiFi keeps an accumulated total of stats, unlike Bluetooth.
+ // Keep the last WiFi stats so we can compute a delta.
+ @GuardedBy("mWorkerLock")
+ private WifiActivityEnergyInfo mLastInfo =
+ new WifiActivityEnergyInfo(0, 0, 0, new long[]{0}, 0, 0, 0);
+
+ BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) {
+ mContext = context;
+ mStats = stats;
+ }
+
+ @Override
+ public synchronized Future<?> scheduleSync(String reason, int flags) {
+ return scheduleSyncLocked(reason, flags);
+ }
+
+ @Override
+ public synchronized Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
+ mUidsToRemove.add(uid);
+ return scheduleSyncLocked("remove-uid", UPDATE_CPU);
+ }
+
+ public synchronized Future<?> scheduleWrite() {
+ scheduleSyncLocked("write", UPDATE_ALL);
+ // Since we use a single threaded executor, we can assume the next scheduled task's
+ // Future finishes after the sync.
+ return mExecutorService.submit(mWriteTask);
+ }
+
+ /**
+ * Schedules a task to run on the BatteryExternalStatsWorker thread. If scheduling more work
+ * within the task, never wait on the resulting Future. This will result in a deadlock.
+ */
+ public synchronized void scheduleRunnable(Runnable runnable) {
+ mExecutorService.submit(runnable);
+ }
+
+ public void shutdown() {
+ mExecutorService.shutdownNow();
+ }
+
+ private Future<?> scheduleSyncLocked(String reason, int flags) {
+ if (mCurrentFuture == null) {
+ mUpdateFlags = flags;
+ mCurrentReason = reason;
+ mCurrentFuture = mExecutorService.submit(mSyncTask);
+ }
+ mUpdateFlags |= flags;
+ return mCurrentFuture;
+ }
+
+ private final Runnable mSyncTask = new Runnable() {
+ @Override
+ public void run() {
+ // Capture a snapshot of the state we are meant to process.
+ final int updateFlags;
+ final String reason;
+ final int[] uidsToRemove;
+ synchronized (BatteryExternalStatsWorker.this) {
+ updateFlags = mUpdateFlags;
+ reason = mCurrentReason;
+ uidsToRemove = mUidsToRemove.size() > 0 ? mUidsToRemove.toArray() : EmptyArray.INT;
+ mUpdateFlags = 0;
+ mCurrentReason = null;
+ mUidsToRemove.clear();
+ mCurrentFuture = null;
+ }
+
+ synchronized (mWorkerLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "begin updateExternalStatsSync reason=" + reason);
+ }
+ try {
+ updateExternalStatsLocked(reason, updateFlags);
+ } finally {
+ if (DEBUG) {
+ Slog.d(TAG, "end updateExternalStatsSync");
+ }
+ }
+ }
+
+ // Clean up any UIDs if necessary.
+ synchronized (mStats) {
+ for (int uid : uidsToRemove) {
+ mStats.removeIsolatedUidLocked(uid);
+ }
+ }
+ }
+ };
+
+ private final Runnable mWriteTask = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mStats) {
+ mStats.writeAsyncLocked();
+ }
+ }
+ };
+
+ private void updateExternalStatsLocked(final String reason, int updateFlags) {
+ // We will request data from external processes asynchronously, and wait on a timeout.
+ SynchronousResultReceiver wifiReceiver = null;
+ SynchronousResultReceiver bluetoothReceiver = null;
+ SynchronousResultReceiver modemReceiver = null;
+
+ if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
+ // We were asked to fetch WiFi data.
+ if (mWifiManager == null) {
+ mWifiManager = IWifiManager.Stub.asInterface(ServiceManager.getService(
+ Context.WIFI_SERVICE));
+ }
+
+ if (mWifiManager != null) {
+ try {
+ wifiReceiver = new SynchronousResultReceiver("wifi");
+ mWifiManager.requestActivityInfo(wifiReceiver);
+ } catch (RemoteException e) {
+ // Oh well.
+ }
+ }
+ }
+
+ if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) {
+ // We were asked to fetch Bluetooth data.
+ final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ bluetoothReceiver = new SynchronousResultReceiver("bluetooth");
+ adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
+ }
+ }
+
+ if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) {
+ // We were asked to fetch Telephony data.
+ if (mTelephony == null) {
+ mTelephony = TelephonyManager.from(mContext);
+ }
+
+ if (mTelephony != null) {
+ modemReceiver = new SynchronousResultReceiver("telephony");
+ mTelephony.requestModemActivityInfo(modemReceiver);
+ }
+ }
+
+ final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
+ final BluetoothActivityEnergyInfo bluetoothInfo = awaitControllerInfo(bluetoothReceiver);
+ final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
+
+ synchronized (mStats) {
+ mStats.addHistoryEventLocked(
+ SystemClock.elapsedRealtime(),
+ SystemClock.uptimeMillis(),
+ BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
+ reason, 0);
+
+ if ((updateFlags & UPDATE_CPU) != 0) {
+ mStats.updateCpuTimeLocked(true /* updateCpuFreqData */);
+ mStats.updateKernelWakelocksLocked();
+ mStats.updateKernelMemoryBandwidthLocked();
+ }
+
+ if (bluetoothInfo != null) {
+ if (bluetoothInfo.isValid()) {
+ mStats.updateBluetoothStateLocked(bluetoothInfo);
+ } else {
+ Slog.e(TAG, "bluetooth info is invalid: " + bluetoothInfo);
+ }
+ }
+ }
+
+ // WiFi and Modem state are updated without the mStats lock held, because they
+ // do some network stats retrieval before internally grabbing the mStats lock.
+
+ if (wifiInfo != null) {
+ if (wifiInfo.isValid()) {
+ mStats.updateWifiState(extractDeltaLocked(wifiInfo));
+ } else {
+ Slog.e(TAG, "wifi info is invalid: " + wifiInfo);
+ }
+ }
+
+ if (modemInfo != null) {
+ if (modemInfo.isValid()) {
+ mStats.updateMobileRadioState(modemInfo);
+ } else {
+ Slog.e(TAG, "modem info is invalid: " + modemInfo);
+ }
+ }
+ }
+
+ /**
+ * Helper method to extract the Parcelable controller info from a
+ * SynchronousResultReceiver.
+ */
+ private static <T extends Parcelable> T awaitControllerInfo(
+ @Nullable SynchronousResultReceiver receiver) {
+ if (receiver == null) {
+ return null;
+ }
+
+ try {
+ final SynchronousResultReceiver.Result result =
+ receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
+ if (result.bundle != null) {
+ // This is the final destination for the Bundle.
+ result.bundle.setDefusable(true);
+
+ final T data = result.bundle.getParcelable(
+ BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY);
+ if (data != null) {
+ return data;
+ }
+ }
+ Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
+ } catch (TimeoutException e) {
+ Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
+ }
+ return null;
+ }
+
+ private WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) {
+ final long timePeriodMs = latest.mTimestamp - mLastInfo.mTimestamp;
+ final long lastIdleMs = mLastInfo.mControllerIdleTimeMs;
+ final long lastTxMs = mLastInfo.mControllerTxTimeMs;
+ final long lastRxMs = mLastInfo.mControllerRxTimeMs;
+ final long lastEnergy = mLastInfo.mControllerEnergyUsed;
+
+ // We will modify the last info object to be the delta, and store the new
+ // WifiActivityEnergyInfo object as our last one.
+ final WifiActivityEnergyInfo delta = mLastInfo;
+ delta.mTimestamp = latest.getTimeStamp();
+ delta.mStackState = latest.getStackState();
+
+ final long txTimeMs = latest.mControllerTxTimeMs - lastTxMs;
+ final long rxTimeMs = latest.mControllerRxTimeMs - lastRxMs;
+ final long idleTimeMs = latest.mControllerIdleTimeMs - lastIdleMs;
+
+ if (txTimeMs < 0 || rxTimeMs < 0) {
+ // The stats were reset by the WiFi system (which is why our delta is negative).
+ // Returns the unaltered stats.
+ delta.mControllerEnergyUsed = latest.mControllerEnergyUsed;
+ delta.mControllerRxTimeMs = latest.mControllerRxTimeMs;
+ delta.mControllerTxTimeMs = latest.mControllerTxTimeMs;
+ delta.mControllerIdleTimeMs = latest.mControllerIdleTimeMs;
+ Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + delta);
+ } else {
+ final long totalActiveTimeMs = txTimeMs + rxTimeMs;
+ long maxExpectedIdleTimeMs;
+ if (totalActiveTimeMs > timePeriodMs) {
+ // Cap the max idle time at zero since the active time consumed the whole time
+ maxExpectedIdleTimeMs = 0;
+ if (totalActiveTimeMs > timePeriodMs + MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Total Active time ");
+ TimeUtils.formatDuration(totalActiveTimeMs, sb);
+ sb.append(" is longer than sample period ");
+ TimeUtils.formatDuration(timePeriodMs, sb);
+ sb.append(".\n");
+ sb.append("Previous WiFi snapshot: ").append("idle=");
+ TimeUtils.formatDuration(lastIdleMs, sb);
+ sb.append(" rx=");
+ TimeUtils.formatDuration(lastRxMs, sb);
+ sb.append(" tx=");
+ TimeUtils.formatDuration(lastTxMs, sb);
+ sb.append(" e=").append(lastEnergy);
+ sb.append("\n");
+ sb.append("Current WiFi snapshot: ").append("idle=");
+ TimeUtils.formatDuration(latest.mControllerIdleTimeMs, sb);
+ sb.append(" rx=");
+ TimeUtils.formatDuration(latest.mControllerRxTimeMs, sb);
+ sb.append(" tx=");
+ TimeUtils.formatDuration(latest.mControllerTxTimeMs, sb);
+ sb.append(" e=").append(latest.mControllerEnergyUsed);
+ Slog.wtf(TAG, sb.toString());
+ }
+ } else {
+ maxExpectedIdleTimeMs = timePeriodMs - totalActiveTimeMs;
+ }
+ // These times seem to be the most reliable.
+ delta.mControllerTxTimeMs = txTimeMs;
+ delta.mControllerRxTimeMs = rxTimeMs;
+ // WiFi calculates the idle time as a difference from the on time and the various
+ // Rx + Tx times. There seems to be some missing time there because this sometimes
+ // becomes negative. Just cap it at 0 and ensure that it is less than the expected idle
+ // time from the difference in timestamps.
+ // b/21613534
+ delta.mControllerIdleTimeMs = Math.min(maxExpectedIdleTimeMs, Math.max(0, idleTimeMs));
+ delta.mControllerEnergyUsed = Math.max(0, latest.mControllerEnergyUsed - lastEnergy);
+ }
+
+ mLastInfo = latest;
+ return delta;
+ }
+}
package com.android.server.am;
-import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
-import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO;
-import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI;
-
-import android.annotation.Nullable;
import android.bluetooth.BluetoothActivityEnergyInfo;
-import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.net.wifi.IWifiManager;
import android.net.wifi.WifiActivityEnergyInfo;
import android.os.PowerSaveState;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFormatException;
-import android.os.Parcelable;
import android.os.PowerManagerInternal;
import android.os.Process;
-import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SynchronousResultReceiver;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.telephony.ModemActivityInfo;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
-import android.util.IntArray;
import android.util.Slog;
-import android.util.TimeUtils;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BatteryStatsHelper;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.PowerProfile;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
-import com.android.server.ServiceThread;
import com.android.server.power.BatterySaverPolicy.ServiceType;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
-import java.util.concurrent.TimeoutException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
/**
* All information we are collecting about things that can happen that impact
static final String TAG = "BatteryStatsService";
static final boolean DBG = false;
- /**
- * How long to wait on an individual subsystem to return its stats.
- */
- private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000;
-
- // There is some accuracy error in wifi reports so allow some slop in the results.
- private static final long MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS = 750;
-
private static IBatteryStats sService;
final BatteryStatsImpl mStats;
- private final BatteryStatsHandler mHandler;
- private Context mContext;
- private IWifiManager mWifiManager;
- private TelephonyManager mTelephony;
-
- // Lock acquired when extracting data from external sources.
- private final Object mExternalStatsLock = new Object();
-
- // WiFi keeps an accumulated total of stats, unlike Bluetooth.
- // Keep the last WiFi stats so we can compute a delta.
- @GuardedBy("mExternalStatsLock")
- private WifiActivityEnergyInfo mLastInfo =
- new WifiActivityEnergyInfo(0, 0, 0, new long[]{0}, 0, 0, 0);
-
- class BatteryStatsHandler extends Handler implements BatteryStatsImpl.ExternalStatsSync {
- public static final int MSG_SYNC_EXTERNAL_STATS = 1;
- public static final int MSG_WRITE_TO_DISK = 2;
-
- private int mUpdateFlags = 0;
- private IntArray mUidsToRemove = new IntArray();
-
- public BatteryStatsHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SYNC_EXTERNAL_STATS:
- final int updateFlags;
- synchronized (this) {
- removeMessages(MSG_SYNC_EXTERNAL_STATS);
- updateFlags = mUpdateFlags;
- mUpdateFlags = 0;
- }
- updateExternalStatsSync((String)msg.obj, updateFlags);
-
- // other parts of the system could be calling into us
- // from mStats in order to report of changes. We must grab the mStats
- // lock before grabbing our own or we'll end up in a deadlock.
- synchronized (mStats) {
- synchronized (this) {
- final int numUidsToRemove = mUidsToRemove.size();
- for (int i = 0; i < numUidsToRemove; i++) {
- mStats.removeIsolatedUidLocked(mUidsToRemove.get(i));
- }
- }
- mUidsToRemove.clear();
- }
- break;
-
- case MSG_WRITE_TO_DISK:
- updateExternalStatsSync("write", UPDATE_ALL);
- if (DBG) Slog.d(TAG, "begin writeAsyncLocked");
- synchronized (mStats) {
- mStats.writeAsyncLocked();
- }
- if (DBG) Slog.d(TAG, "end writeAsyncLocked");
- break;
- }
- }
-
- @Override
- public void scheduleSync(String reason, int updateFlags) {
- synchronized (this) {
- scheduleSyncLocked(reason, updateFlags);
- }
- }
-
- @Override
- public void scheduleCpuSyncDueToRemovedUid(int uid) {
- synchronized (this) {
- scheduleSyncLocked("remove-uid", UPDATE_CPU);
- mUidsToRemove.add(uid);
- }
- }
-
- private void scheduleSyncLocked(String reason, int updateFlags) {
- if (mUpdateFlags == 0) {
- sendMessage(Message.obtain(this, MSG_SYNC_EXTERNAL_STATS, reason));
- }
- mUpdateFlags |= updateFlags;
- }
- }
+ private final Context mContext;
+ private final BatteryExternalStatsWorker mWorker;
private native int getPlatformLowPowerStats(ByteBuffer outBuffer);
private native int getSubsystemLowPowerStats(ByteBuffer outBuffer);
}
}
- BatteryStatsService(File systemDir, Handler handler) {
- // Our handler here will be accessing the disk, use a different thread than
- // what the ActivityManagerService gave us (no I/O on that one!).
- final ServiceThread thread = new ServiceThread("batterystats-sync",
- Process.THREAD_PRIORITY_DEFAULT, true);
- thread.start();
- mHandler = new BatteryStatsHandler(thread.getLooper());
-
+ BatteryStatsService(Context context, File systemDir, Handler handler) {
// BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through.
- mStats = new BatteryStatsImpl(systemDir, handler, mHandler, this);
+ mContext = context;
+ mStats = new BatteryStatsImpl(systemDir, handler, this);
+ mWorker = new BatteryExternalStatsWorker(context, mStats);
+ mStats.setExternalStatsSyncLocked(mWorker);
+ mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
+ mStats.setPowerProfileLocked(new PowerProfile(context));
}
- public void publish(Context context) {
- mContext = context;
- synchronized (mStats) {
- mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
- com.android.internal.R.integer.config_radioScanningTimeout)
- * 1000L);
- mStats.setPowerProfileLocked(new PowerProfile(context));
- }
+ public void publish() {
ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
}
+ private static void awaitUninterruptibly(Future<?> future) {
+ while (true) {
+ try {
+ future.get();
+ return;
+ } catch (ExecutionException e) {
+ return;
+ } catch (InterruptedException e) {
+ // Keep looping
+ }
+ }
+ }
+
/**
* At the time when the constructor runs, the power manager has not yet been
* initialized. So we initialize the low power observer later.
public void shutdown() {
Slog.w("BatteryStats", "Writing battery stats before shutdown...");
- updateExternalStatsSync("shutdown", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+ awaitUninterruptibly(mWorker.scheduleSync("shutdown", BatteryExternalStatsWorker.UPDATE_ALL));
+
synchronized (mStats) {
mStats.shutdownLocked();
}
// Shutdown the thread we made.
- mHandler.getLooper().quit();
+ mWorker.shutdown();
}
public static IBatteryStats getService() {
* object to update with the latest info, then write to disk.
*/
public void scheduleWriteToDisk() {
- mHandler.sendEmptyMessage(BatteryStatsHandler.MSG_WRITE_TO_DISK);
+ mWorker.scheduleWrite();
}
// These are for direct use by the activity manager...
//Slog.i("foo", "SENDING BATTERY INFO:");
//mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
Parcel out = Parcel.obtain();
- updateExternalStatsSync("get-stats", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+ awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
synchronized (mStats) {
mStats.writeToParcel(out, 0);
}
//Slog.i("foo", "SENDING BATTERY INFO:");
//mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
Parcel out = Parcel.obtain();
- updateExternalStatsSync("get-stats", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+ awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
synchronized (mStats) {
mStats.writeToParcel(out, 0);
}
public void noteMobileRadioPowerState(int powerState, long timestampNs, int uid) {
enforceCallingPermission();
- boolean update;
+ final boolean update;
synchronized (mStats) {
update = mStats.noteMobileRadioPowerStateLocked(powerState, timestampNs, uid);
}
if (update) {
- mHandler.scheduleSync("modem-data", UPDATE_RADIO);
+ mWorker.scheduleSync("modem-data", BatteryExternalStatsWorker.UPDATE_RADIO);
}
}
final String type = (powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH ||
powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM) ? "active"
: "inactive";
- mHandler.scheduleSync("wifi-data: " + type,
- UPDATE_WIFI);
+ mWorker.scheduleSync("wifi-data: " + type, BatteryExternalStatsWorker.UPDATE_WIFI);
}
mStats.noteWifiRadioPowerState(powerState, tsNanos, uid);
}
@Override
public void noteNetworkStatsEnabled() {
enforceCallingPermission();
- mHandler.scheduleSync("network-stats-enabled", UPDATE_RADIO | UPDATE_WIFI);
+ // During device boot, qtaguid isn't enabled until after the inital
+ // loading of battery stats. Now that they're enabled, take our initial
+ // snapshot for future delta calculation.
+ mWorker.scheduleSync("network-stats-enabled",
+ BatteryExternalStatsWorker.UPDATE_RADIO | BatteryExternalStatsWorker.UPDATE_WIFI);
}
@Override
return;
}
- synchronized (mStats) {
- mStats.updateBluetoothStateLocked(info);
- }
+ mStats.updateBluetoothStateLocked(info);
}
@Override
// BatteryService calls us here and we may update external state. It would be wrong
// to block such a low level service like BatteryService on external stats like WiFi.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- synchronized (mStats) {
- final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE;
- if (mStats.isOnBattery() == onBattery) {
- // The battery state has not changed, so we don't need to sync external
- // stats immediately.
- mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
- chargeUAh, chargeFullUAh);
- return;
- }
+ mWorker.scheduleRunnable(() -> {
+ synchronized (mStats) {
+ final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE;
+ if (mStats.isOnBattery() == onBattery) {
+ // The battery state has not changed, so we don't need to sync external
+ // stats immediately.
+ mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
+ chargeUAh, chargeFullUAh);
+ return;
}
+ }
- // Sync external stats first as the battery has changed states. If we don't sync
- // immediately here, we may not collect the relevant data later.
- updateExternalStatsSync("battery-state", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+ // Sync external stats first as the battery has changed states. If we don't sync
+ // before changing the state, we may not collect the relevant data later.
+ // Order here is guaranteed since we're scheduling from the same thread and we are
+ // using a single threaded executor.
+ mWorker.scheduleSync("battery-state", BatteryExternalStatsWorker.UPDATE_ALL);
+ mWorker.scheduleRunnable(() -> {
synchronized (mStats) {
mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
chargeUAh, chargeFullUAh);
}
- }
+ });
});
}
pw.println("Battery stats reset.");
noOutput = true;
}
- updateExternalStatsSync("dump", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+ mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL);
} else if ("--write".equals(arg)) {
- updateExternalStatsSync("dump", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+ awaitUninterruptibly(mWorker.scheduleSync("dump",
+ BatteryExternalStatsWorker.UPDATE_ALL));
synchronized (mStats) {
mStats.writeSyncLocked();
pw.println("Battery stats written.");
flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY;
}
// Fetch data from external sources and update the BatteryStatsImpl object with them.
- updateExternalStatsSync("dump", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+ awaitUninterruptibly(mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL));
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
- private WifiActivityEnergyInfo extractDelta(WifiActivityEnergyInfo latest) {
- final long timePeriodMs = latest.mTimestamp - mLastInfo.mTimestamp;
- final long lastIdleMs = mLastInfo.mControllerIdleTimeMs;
- final long lastTxMs = mLastInfo.mControllerTxTimeMs;
- final long lastRxMs = mLastInfo.mControllerRxTimeMs;
- final long lastEnergy = mLastInfo.mControllerEnergyUsed;
-
- // We will modify the last info object to be the delta, and store the new
- // WifiActivityEnergyInfo object as our last one.
- final WifiActivityEnergyInfo delta = mLastInfo;
- delta.mTimestamp = latest.getTimeStamp();
- delta.mStackState = latest.getStackState();
-
- final long txTimeMs = latest.mControllerTxTimeMs - lastTxMs;
- final long rxTimeMs = latest.mControllerRxTimeMs - lastRxMs;
- final long idleTimeMs = latest.mControllerIdleTimeMs - lastIdleMs;
-
- if (txTimeMs < 0 || rxTimeMs < 0) {
- // The stats were reset by the WiFi system (which is why our delta is negative).
- // Returns the unaltered stats.
- delta.mControllerEnergyUsed = latest.mControllerEnergyUsed;
- delta.mControllerRxTimeMs = latest.mControllerRxTimeMs;
- delta.mControllerTxTimeMs = latest.mControllerTxTimeMs;
- delta.mControllerIdleTimeMs = latest.mControllerIdleTimeMs;
- Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + delta);
- } else {
- final long totalActiveTimeMs = txTimeMs + rxTimeMs;
- long maxExpectedIdleTimeMs;
- if (totalActiveTimeMs > timePeriodMs) {
- // Cap the max idle time at zero since the active time consumed the whole time
- maxExpectedIdleTimeMs = 0;
- if (totalActiveTimeMs > timePeriodMs + MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS) {
- StringBuilder sb = new StringBuilder();
- sb.append("Total Active time ");
- TimeUtils.formatDuration(totalActiveTimeMs, sb);
- sb.append(" is longer than sample period ");
- TimeUtils.formatDuration(timePeriodMs, sb);
- sb.append(".\n");
- sb.append("Previous WiFi snapshot: ").append("idle=");
- TimeUtils.formatDuration(lastIdleMs, sb);
- sb.append(" rx=");
- TimeUtils.formatDuration(lastRxMs, sb);
- sb.append(" tx=");
- TimeUtils.formatDuration(lastTxMs, sb);
- sb.append(" e=").append(lastEnergy);
- sb.append("\n");
- sb.append("Current WiFi snapshot: ").append("idle=");
- TimeUtils.formatDuration(latest.mControllerIdleTimeMs, sb);
- sb.append(" rx=");
- TimeUtils.formatDuration(latest.mControllerRxTimeMs, sb);
- sb.append(" tx=");
- TimeUtils.formatDuration(latest.mControllerTxTimeMs, sb);
- sb.append(" e=").append(latest.mControllerEnergyUsed);
- Slog.wtf(TAG, sb.toString());
- }
- } else {
- maxExpectedIdleTimeMs = timePeriodMs - totalActiveTimeMs;
- }
- // These times seem to be the most reliable.
- delta.mControllerTxTimeMs = txTimeMs;
- delta.mControllerRxTimeMs = rxTimeMs;
- // WiFi calculates the idle time as a difference from the on time and the various
- // Rx + Tx times. There seems to be some missing time there because this sometimes
- // becomes negative. Just cap it at 0 and ensure that it is less than the expected idle
- // time from the difference in timestamps.
- // b/21613534
- delta.mControllerIdleTimeMs = Math.min(maxExpectedIdleTimeMs, Math.max(0, idleTimeMs));
- delta.mControllerEnergyUsed = Math.max(0, latest.mControllerEnergyUsed - lastEnergy);
- }
-
- mLastInfo = latest;
- return delta;
- }
-
- /**
- * Helper method to extract the Parcelable controller info from a
- * SynchronousResultReceiver.
- */
- private static <T extends Parcelable> T awaitControllerInfo(
- @Nullable SynchronousResultReceiver receiver) throws TimeoutException {
- if (receiver == null) {
- return null;
- }
-
- final SynchronousResultReceiver.Result result =
- receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
- if (result.bundle != null) {
- // This is the final destination for the Bundle.
- result.bundle.setDefusable(true);
-
- final T data = result.bundle.getParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY);
- if (data != null) {
- return data;
- }
- }
- Slog.e(TAG, "no controller energy info supplied");
- return null;
- }
-
- /**
- * Fetches data from external sources (WiFi controller, bluetooth chipset) and updates
- * batterystats with that information.
- *
- * We first grab a lock specific to this method, then once all the data has been collected,
- * we grab the mStats lock and update the data.
- *
- * @param reason The reason why this collection was requested. Useful for debugging.
- * @param updateFlags Which external stats to update. Can be a combination of
- * {@link BatteryStatsImpl.ExternalStatsSync#UPDATE_CPU},
- * {@link BatteryStatsImpl.ExternalStatsSync#UPDATE_RADIO},
- * {@link BatteryStatsImpl.ExternalStatsSync#UPDATE_WIFI},
- * and {@link BatteryStatsImpl.ExternalStatsSync#UPDATE_BT}.
- */
- void updateExternalStatsSync(final String reason, int updateFlags) {
- SynchronousResultReceiver wifiReceiver = null;
- SynchronousResultReceiver bluetoothReceiver = null;
- SynchronousResultReceiver modemReceiver = null;
-
- if (DBG) Slog.d(TAG, "begin updateExternalStatsSync reason=" + reason);
- synchronized (mExternalStatsLock) {
- if (mContext == null) {
- // Don't do any work yet.
- if (DBG) Slog.d(TAG, "end updateExternalStatsSync");
- return;
- }
-
- if ((updateFlags & UPDATE_WIFI) != 0) {
- if (mWifiManager == null) {
- mWifiManager = IWifiManager.Stub.asInterface(
- ServiceManager.getService(Context.WIFI_SERVICE));
- }
-
- if (mWifiManager != null) {
- try {
- wifiReceiver = new SynchronousResultReceiver();
- mWifiManager.requestActivityInfo(wifiReceiver);
- } catch (RemoteException e) {
- // Oh well.
- }
- }
- }
-
- if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) {
- final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- bluetoothReceiver = new SynchronousResultReceiver();
- adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
- }
- }
-
- if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) {
- if (mTelephony == null) {
- mTelephony = TelephonyManager.from(mContext);
- }
-
- if (mTelephony != null) {
- modemReceiver = new SynchronousResultReceiver();
- mTelephony.requestModemActivityInfo(modemReceiver);
- }
- }
-
- WifiActivityEnergyInfo wifiInfo = null;
- BluetoothActivityEnergyInfo bluetoothInfo = null;
- ModemActivityInfo modemInfo = null;
- try {
- wifiInfo = awaitControllerInfo(wifiReceiver);
- } catch (TimeoutException e) {
- Slog.w(TAG, "Timeout reading wifi stats");
- }
-
- try {
- bluetoothInfo = awaitControllerInfo(bluetoothReceiver);
- } catch (TimeoutException e) {
- Slog.w(TAG, "Timeout reading bt stats");
- }
-
- try {
- modemInfo = awaitControllerInfo(modemReceiver);
- } catch (TimeoutException e) {
- Slog.w(TAG, "Timeout reading modem stats");
- }
-
- synchronized (mStats) {
- mStats.addHistoryEventLocked(
- SystemClock.elapsedRealtime(),
- SystemClock.uptimeMillis(),
- BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
- reason, 0);
-
- if ((updateFlags & UPDATE_CPU) != 0) {
- mStats.updateCpuTimeLocked(true /* updateCpuFreqData */);
- }
- mStats.updateKernelWakelocksLocked();
- mStats.updateKernelMemoryBandwidthLocked();
-
- if (bluetoothInfo != null) {
- if (bluetoothInfo.isValid()) {
- mStats.updateBluetoothStateLocked(bluetoothInfo);
- } else {
- Slog.e(TAG, "bluetooth info is invalid: " + bluetoothInfo);
- }
- }
- }
-
- if (wifiInfo != null) {
- if (wifiInfo.isValid()) {
- mStats.updateWifiState(extractDelta(wifiInfo));
- } else {
- Slog.e(TAG, "wifi info is invalid: " + wifiInfo);
- }
- }
-
- if (modemInfo != null) {
- if (modemInfo.isValid()) {
- mStats.updateMobileRadioState(modemInfo);
- } else {
- Slog.e(TAG, "modem info is invalid: " + modemInfo);
- }
- }
- }
- if (DBG) Slog.d(TAG, "end updateExternalStatsSync");
- }
-
/**
* Gets a snapshot of the system health for a particular uid.
*/
}
long ident = Binder.clearCallingIdentity();
try {
- updateExternalStatsSync("get-health-stats-for-uid",
- BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+ awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
+ BatteryExternalStatsWorker.UPDATE_ALL));
synchronized (mStats) {
return getHealthStatsForUidLocked(requestUid);
}
long ident = Binder.clearCallingIdentity();
int i=-1;
try {
- updateExternalStatsSync("get-health-stats-for-uids",
- BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
+ awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
+ BatteryExternalStatsWorker.UPDATE_ALL));
synchronized (mStats) {
final int N = requestUids.length;
final HealthStatsParceler[] results = new HealthStatsParceler[N];