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