| /* |
| * Copyright 2018 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.wifi; |
| |
| import android.content.Context; |
| import android.net.wifi.WifiInfo; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.provider.Settings; |
| |
| import com.android.server.wifi.nano.WifiMetricsProto.WifiIsUnusableEvent; |
| |
| /** |
| * Looks for Wifi data stalls |
| */ |
| public class WifiDataStall { |
| |
| // Default minimum number of txBadDelta to trigger data stall |
| public static final int MIN_TX_BAD_DEFAULT = 1; |
| // Default minimum number of txSuccessDelta to trigger data stall |
| // when rxSuccessDelta is 0 |
| public static final int MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT = 50; |
| // Maximum time gap between two WifiLinkLayerStats to trigger a data stall |
| public static final long MAX_MS_DELTA_FOR_DATA_STALL = 60 * 1000; // 1 minute |
| // Maximum time that a data stall start time stays valid. |
| public static final long VALIDITY_PERIOD_OF_DATA_STALL_START_MS = 30 * 1000; // 0.5 minutes |
| // Default Tx packet error rate when there is no Tx attempt |
| public static final int DEFAULT_TX_PACKET_ERROR_RATE = 20; |
| // Default CCA level when CCA stats are not available |
| public static final int DEFAULT_CCA_LEVEL = 0; |
| |
| private final Context mContext; |
| private final DeviceConfigFacade mDeviceConfigFacade; |
| private final FrameworkFacade mFacade; |
| private final WifiMetrics mWifiMetrics; |
| |
| private Handler mHandler; |
| private int mMinTxBad; |
| private int mMinTxSuccessWithoutRx; |
| private int mDataStallDurationMs; |
| private int mDataStallTxTputThrMbps; |
| private int mDataStallRxTputThrMbps; |
| private int mDataStallTxPerThr; |
| private int mDataStallCcaLevelThr; |
| private int mLastFrequency = -1; |
| private String mLastBssid; |
| private long mLastTotalRadioOnFreqTimeMs = -1; |
| private long mLastTotalCcaBusyFreqTimeMs = -1; |
| private long mDataStallStartTimeMs = -1; |
| private Clock mClock; |
| private boolean mDataStallTx = false; |
| private boolean mDataStallRx = false; |
| |
| public WifiDataStall(Context context, FrameworkFacade facade, WifiMetrics wifiMetrics, |
| DeviceConfigFacade deviceConfigFacade, Looper clientModeImplLooper, Clock clock) { |
| mContext = context; |
| mDeviceConfigFacade = deviceConfigFacade; |
| mFacade = facade; |
| mHandler = new Handler(clientModeImplLooper); |
| mWifiMetrics = wifiMetrics; |
| mClock = clock; |
| loadSettings(); |
| |
| mDeviceConfigFacade.addOnPropertiesChangedListener( |
| command -> mHandler.post(command), |
| properties -> { |
| updateUsabilityDataCollectionFlags(); |
| }); |
| } |
| |
| /** |
| * Load setting values related to wifi data stall. |
| */ |
| public void loadSettings() { |
| mMinTxBad = mFacade.getIntegerSetting( |
| mContext, Settings.Global.WIFI_DATA_STALL_MIN_TX_BAD, MIN_TX_BAD_DEFAULT); |
| mMinTxSuccessWithoutRx = mFacade.getIntegerSetting( |
| mContext, Settings.Global.WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX, |
| MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT); |
| mWifiMetrics.setWifiDataStallMinTxBad(mMinTxBad); |
| mWifiMetrics.setWifiDataStallMinRxWithoutTx(mMinTxSuccessWithoutRx); |
| updateUsabilityDataCollectionFlags(); |
| } |
| |
| /** |
| * Checks for data stall by looking at tx/rx packet counts |
| * @param oldStats second most recent WifiLinkLayerStats |
| * @param newStats most recent WifiLinkLayerStats |
| * @param wifiInfo WifiInfo for current connection |
| * @return trigger type of WifiIsUnusableEvent |
| */ |
| public int checkForDataStall(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats, |
| WifiInfo wifiInfo) { |
| if (oldStats == null || newStats == null) { |
| mWifiMetrics.resetWifiIsUnusableLinkLayerStats(); |
| return WifiIsUnusableEvent.TYPE_UNKNOWN; |
| } |
| |
| long txSuccessDelta = (newStats.txmpdu_be + newStats.txmpdu_bk |
| + newStats.txmpdu_vi + newStats.txmpdu_vo) |
| - (oldStats.txmpdu_be + oldStats.txmpdu_bk |
| + oldStats.txmpdu_vi + oldStats.txmpdu_vo); |
| long txRetriesDelta = (newStats.retries_be + newStats.retries_bk |
| + newStats.retries_vi + newStats.retries_vo) |
| - (oldStats.retries_be + oldStats.retries_bk |
| + oldStats.retries_vi + oldStats.retries_vo); |
| long txBadDelta = (newStats.lostmpdu_be + newStats.lostmpdu_bk |
| + newStats.lostmpdu_vi + newStats.lostmpdu_vo) |
| - (oldStats.lostmpdu_be + oldStats.lostmpdu_bk |
| + oldStats.lostmpdu_vi + oldStats.lostmpdu_vo); |
| long rxSuccessDelta = (newStats.rxmpdu_be + newStats.rxmpdu_bk |
| + newStats.rxmpdu_vi + newStats.rxmpdu_vo) |
| - (oldStats.rxmpdu_be + oldStats.rxmpdu_bk |
| + oldStats.rxmpdu_vi + oldStats.rxmpdu_vo); |
| long timeMsDelta = newStats.timeStampInMs - oldStats.timeStampInMs; |
| |
| if (timeMsDelta < 0 |
| || txSuccessDelta < 0 |
| || txRetriesDelta < 0 |
| || txBadDelta < 0 |
| || rxSuccessDelta < 0) { |
| // There was a reset in WifiLinkLayerStats |
| mWifiMetrics.resetWifiIsUnusableLinkLayerStats(); |
| return WifiIsUnusableEvent.TYPE_UNKNOWN; |
| } |
| |
| mWifiMetrics.updateWifiIsUnusableLinkLayerStats(txSuccessDelta, txRetriesDelta, |
| txBadDelta, rxSuccessDelta, timeMsDelta); |
| |
| if (timeMsDelta < MAX_MS_DELTA_FOR_DATA_STALL) { |
| int txLinkSpeed = wifiInfo.getLinkSpeed(); |
| int rxLinkSpeed = wifiInfo.getRxLinkSpeedMbps(); |
| boolean isSameBssidAndFreq = mLastBssid == null || mLastFrequency == -1 |
| || (mLastBssid.equals(wifiInfo.getBSSID()) |
| && mLastFrequency == wifiInfo.getFrequency()); |
| mLastFrequency = wifiInfo.getFrequency(); |
| mLastBssid = wifiInfo.getBSSID(); |
| |
| int ccaLevel = updateCcaLevel(newStats, wifiInfo, isSameBssidAndFreq); |
| int txPer = updateTxPer(txSuccessDelta, txRetriesDelta, isSameBssidAndFreq); |
| |
| boolean isTxTputLow = false; |
| boolean isRxTputLow = false; |
| if (txLinkSpeed > 0) { |
| int txTput = txLinkSpeed * (100 - txPer) * (100 - ccaLevel); |
| isTxTputLow = txTput < mDataStallTxTputThrMbps * 100 * 100; |
| } |
| if (rxLinkSpeed > 0) { |
| int rxTput = rxLinkSpeed * (100 - ccaLevel); |
| isRxTputLow = rxTput < mDataStallRxTputThrMbps * 100; |
| } |
| |
| boolean dataStallTx = isTxTputLow || ccaLevel >= mDataStallCcaLevelThr |
| || txPer >= mDataStallTxPerThr; |
| boolean dataStallRx = isRxTputLow || ccaLevel >= mDataStallCcaLevelThr; |
| |
| // Data stall event is triggered if there are consecutive Tx and/or Rx data stalls |
| // Reset mDataStallStartTimeMs to -1 if currently there is no Tx or Rx data stall |
| if (dataStallTx || dataStallRx) { |
| mDataStallTx = mDataStallTx || dataStallTx; |
| mDataStallRx = mDataStallRx || dataStallRx; |
| if (mDataStallStartTimeMs == -1) { |
| mDataStallStartTimeMs = mClock.getElapsedSinceBootMillis(); |
| if (mDataStallDurationMs == 0) { |
| mDataStallStartTimeMs = -1; |
| int result = calculateUsabilityEventType(mDataStallTx, mDataStallRx); |
| mDataStallRx = false; |
| mDataStallTx = false; |
| return result; |
| } |
| } else { |
| long elapsedTime = mClock.getElapsedSinceBootMillis() - mDataStallStartTimeMs; |
| if (elapsedTime >= mDataStallDurationMs) { |
| mDataStallStartTimeMs = -1; |
| if (elapsedTime <= VALIDITY_PERIOD_OF_DATA_STALL_START_MS) { |
| int result = calculateUsabilityEventType(mDataStallTx, mDataStallRx); |
| mDataStallRx = false; |
| mDataStallTx = false; |
| return result; |
| } else { |
| mDataStallTx = false; |
| mDataStallRx = false; |
| } |
| } else { |
| // No need to do anything. |
| } |
| } |
| } else { |
| mDataStallStartTimeMs = -1; |
| mDataStallTx = false; |
| mDataStallRx = false; |
| } |
| } |
| |
| return WifiIsUnusableEvent.TYPE_UNKNOWN; |
| } |
| |
| private int updateCcaLevel(WifiLinkLayerStats newStats, WifiInfo wifiInfo, |
| boolean isSameBssidAndFreq) { |
| WifiLinkLayerStats.ChannelStats statsMap = newStats.channelStatsMap.get(mLastFrequency); |
| if (statsMap == null || !isSameBssidAndFreq) { |
| return DEFAULT_CCA_LEVEL; |
| } |
| int radioOnTimeDelta = (int) (statsMap.radioOnTimeMs - mLastTotalRadioOnFreqTimeMs); |
| int ccaBusyTimeDelta = (int) (statsMap.ccaBusyTimeMs - mLastTotalCcaBusyFreqTimeMs); |
| mLastTotalRadioOnFreqTimeMs = statsMap.radioOnTimeMs; |
| mLastTotalCcaBusyFreqTimeMs = statsMap.ccaBusyTimeMs; |
| |
| boolean isCcaValid = (radioOnTimeDelta > 0) && (ccaBusyTimeDelta >= 0) |
| && (ccaBusyTimeDelta <= radioOnTimeDelta); |
| // Update CCA level only if CCA stats are valid. |
| if (!isCcaValid) { |
| return DEFAULT_CCA_LEVEL; |
| } |
| return (int) (ccaBusyTimeDelta * 100 / radioOnTimeDelta); |
| } |
| |
| private int updateTxPer(long txSuccessDelta, long txRetriesDelta, boolean isSameBssidAndFreq) { |
| if (!isSameBssidAndFreq) { |
| return DEFAULT_TX_PACKET_ERROR_RATE; |
| } |
| long txAttempts = txSuccessDelta + txRetriesDelta; |
| if (txAttempts <= 0) { |
| return DEFAULT_TX_PACKET_ERROR_RATE; |
| } |
| return (int) (txRetriesDelta * 100 / txAttempts); |
| } |
| |
| private int calculateUsabilityEventType(boolean dataStallTx, boolean dataStallRx) { |
| int result = WifiIsUnusableEvent.TYPE_UNKNOWN; |
| if (dataStallTx && dataStallRx) { |
| result = WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH; |
| } else if (dataStallTx) { |
| result = WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX; |
| } else if (dataStallRx) { |
| result = WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX; |
| } |
| mWifiMetrics.logWifiIsUnusableEvent(result); |
| return result; |
| } |
| |
| private void updateUsabilityDataCollectionFlags() { |
| mDataStallDurationMs = mDeviceConfigFacade.getDataStallDurationMs(); |
| mDataStallTxTputThrMbps = mDeviceConfigFacade.getDataStallTxTputThrMbps(); |
| mDataStallRxTputThrMbps = mDeviceConfigFacade.getDataStallRxTputThrMbps(); |
| mDataStallTxPerThr = mDeviceConfigFacade.getDataStallTxPerThr(); |
| mDataStallCcaLevelThr = mDeviceConfigFacade.getDataStallCcaLevelThr(); |
| |
| mWifiMetrics.setDataStallDurationMs(mDataStallDurationMs); |
| mWifiMetrics.setDataStallTxTputThrMbps(mDataStallTxTputThrMbps); |
| mWifiMetrics.setDataStallRxTputThrMbps(mDataStallRxTputThrMbps); |
| mWifiMetrics.setDataStallTxPerThr(mDataStallTxPerThr); |
| mWifiMetrics.setDataStallCcaLevelThr(mDataStallCcaLevelThr); |
| } |
| } |