/* * 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); } }