From 9a3473bafbffd386b54743500ac875f10bab459b Mon Sep 17 00:00:00 2001 From: Mingguang Xu Date: Fri, 13 Sep 2019 17:27:13 -0700 Subject: Wifi usability: Data stall detection based on link layer stats This is an effort to define new criterion for triggering Wifi data stall: (1) Defined data stall criterion based on transmit and/or receive throughput, transmit packet error rate, and CCA level. Data stall is triggered when transmit and/or receive links are consecutively bad over multiple RSSI polls. (2) Added DeviceConfig flags (that is disabled by default) which may be configured on the server side to tune the thresholds at which data stall gets triggered. Bug: 141027476 Test: frameworks/opt/net/wifi/tests/wifitests/runtests.sh Change-Id: I3c5d9756ff74c0b5682a0b2a51ed64c27c679d72 Signed-off-by: Mingguang Xu Merged-In: I76d6338cd2d482d198fde1e5a2d1a0540c087ca6 --- .../com/android/server/wifi/ClientModeImpl.java | 28 +++- .../android/server/wifi/DeviceConfigFacade.java | 51 ++++++ .../com/android/server/wifi/WifiDataStall.java | 176 +++++++++++++++++++-- .../java/com/android/server/wifi/WifiInjector.java | 3 +- .../java/com/android/server/wifi/WifiMetrics.java | 55 +++++++ 5 files changed, 290 insertions(+), 23 deletions(-) (limited to 'service') diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java index 8e8361ed4..b80371285 100644 --- a/service/java/com/android/server/wifi/ClientModeImpl.java +++ b/service/java/com/android/server/wifi/ClientModeImpl.java @@ -765,6 +765,11 @@ public class ClientModeImpl extends StateMachine { private final WrongPasswordNotifier mWrongPasswordNotifier; private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager; private boolean mConnectedMacRandomzationSupported; + // Maximum duration to continue to log Wifi usability stats after a data stall is triggered. + @VisibleForTesting + public static final long DURATION_TO_WAIT_ADD_STATS_AFTER_DATA_STALL_MS = 30 * 1000; + private long mDataStallTriggerTimeMs = -1; + private int mLastStatusDataStall = WifiIsUnusableEvent.TYPE_UNKNOWN; public ClientModeImpl(Context context, FrameworkFacade facade, Looper looper, UserManager userManager, WifiInjector wifiInjector, @@ -5109,11 +5114,24 @@ public class ClientModeImpl extends StateMachine { } mWifiScoreReport.noteIpCheck(); } - int statusDataStall = - mWifiDataStall.checkForDataStall(mLastLinkLayerStats, stats); - if (statusDataStall != WifiIsUnusableEvent.TYPE_UNKNOWN) { - mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD, - convertToUsabilityStatsTriggerType(statusDataStall), -1); + int statusDataStall = mWifiDataStall.checkForDataStall( + mLastLinkLayerStats, stats, mWifiInfo); + if (mDataStallTriggerTimeMs == -1 + && statusDataStall != WifiIsUnusableEvent.TYPE_UNKNOWN) { + mDataStallTriggerTimeMs = mClock.getElapsedSinceBootMillis(); + mLastStatusDataStall = statusDataStall; + } + if (mDataStallTriggerTimeMs != -1) { + long elapsedTime = mClock.getElapsedSinceBootMillis() + - mDataStallTriggerTimeMs; + if (elapsedTime >= DURATION_TO_WAIT_ADD_STATS_AFTER_DATA_STALL_MS) { + mDataStallTriggerTimeMs = -1; + mWifiMetrics.addToWifiUsabilityStatsList( + WifiUsabilityStats.LABEL_BAD, + convertToUsabilityStatsTriggerType(mLastStatusDataStall), + -1); + mLastStatusDataStall = WifiIsUnusableEvent.TYPE_UNKNOWN; + } } mWifiMetrics.incrementWifiLinkLayerUsageStats(stats); mLastLinkLayerStats = stats; diff --git a/service/java/com/android/server/wifi/DeviceConfigFacade.java b/service/java/com/android/server/wifi/DeviceConfigFacade.java index c64cd5259..a9889f424 100644 --- a/service/java/com/android/server/wifi/DeviceConfigFacade.java +++ b/service/java/com/android/server/wifi/DeviceConfigFacade.java @@ -28,6 +28,17 @@ public class DeviceConfigFacade { private static final int DEFAULT_ABNORMAL_CONNECTION_DURATION_MS = (int) TimeUnit.SECONDS.toMillis(30); private static final String NAMESPACE = "wifi"; + // Default duration for evaluating Wifi condition to trigger a data stall + // measured in milliseconds + public static final int DEFAULT_DATA_STALL_DURATION_MS = 1500; + // Default threshold of Tx throughput below which to trigger a data stall measured in Mbps + public static final int DEFAULT_DATA_STALL_TX_TPUT_THR_MBPS = 2; + // Default threshold of Rx throughput below which to trigger a data stall measured in Mbps + public static final int DEFAULT_DATA_STALL_RX_TPUT_THR_MBPS = 2; + // Default threshold of Tx packet error rate above which to trigger a data stall in percentage + public static final int DEFAULT_DATA_STALL_TX_PER_THR = 90; + // Default threshold of CCA level above which to trigger a data stall in percentage + public static final int DEFAULT_DATA_STALL_CCA_LEVEL_THR = 100; /** * Gets the feature flag for reporting abnormally long connections. @@ -54,4 +65,44 @@ public class DeviceConfigFacade { DeviceConfig.addOnPropertiesChangedListener(NAMESPACE, executor, onPropertiesChangedListener); } + + /** + * Gets the duration of evaluating Wifi condition to trigger a data stall. + */ + public int getDataStallDurationMs() { + return DeviceConfig.getInt(NAMESPACE, "data_stall_duration_ms", + DEFAULT_DATA_STALL_DURATION_MS); + } + + /** + * Gets the threshold of Tx throughput below which to trigger a data stall. + */ + public int getDataStallTxTputThrMbps() { + return DeviceConfig.getInt(NAMESPACE, "data_stall_tx_tput_thr_mbps", + DEFAULT_DATA_STALL_TX_TPUT_THR_MBPS); + } + + /** + * Gets the threshold of Rx throughput below which to trigger a data stall. + */ + public int getDataStallRxTputThrMbps() { + return DeviceConfig.getInt(NAMESPACE, "data_stall_rx_tput_thr_mbps", + DEFAULT_DATA_STALL_RX_TPUT_THR_MBPS); + } + + /** + * Gets the threshold of Tx packet error rate above which to trigger a data stall. + */ + public int getDataStallTxPerThr() { + return DeviceConfig.getInt(NAMESPACE, "data_stall_tx_per_thr", + DEFAULT_DATA_STALL_TX_PER_THR); + } + + /** + * Gets the threshold of CCA level above which to trigger a data stall. + */ + public int getDataStallCcaLevelThr() { + return DeviceConfig.getInt(NAMESPACE, "data_stall_cca_level_thr", + DEFAULT_DATA_STALL_CCA_LEVEL_THR); + } } diff --git a/service/java/com/android/server/wifi/WifiDataStall.java b/service/java/com/android/server/wifi/WifiDataStall.java index 1054c2ef1..6eb3b41e5 100644 --- a/service/java/com/android/server/wifi/WifiDataStall.java +++ b/service/java/com/android/server/wifi/WifiDataStall.java @@ -17,6 +17,9 @@ 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; @@ -33,19 +36,50 @@ public class WifiDataStall { 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) { + 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(); + }); } /** @@ -59,15 +93,18 @@ public class WifiDataStall { 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) { + public int checkForDataStall(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats, + WifiInfo wifiInfo) { if (oldStats == null || newStats == null) { mWifiMetrics.resetWifiIsUnusableLinkLayerStats(); return WifiIsUnusableEvent.TYPE_UNKNOWN; @@ -103,25 +140,130 @@ public class WifiDataStall { mWifiMetrics.updateWifiIsUnusableLinkLayerStats(txSuccessDelta, txRetriesDelta, txBadDelta, rxSuccessDelta, timeMsDelta); + if (timeMsDelta < MAX_MS_DELTA_FOR_DATA_STALL) { - // There is a data stall if there are too many tx failures - // or if we are not receiving any packets despite many tx successes - boolean dataStallBadTx = (txBadDelta >= mMinTxBad); - boolean dataStallTxSuccessWithoutRx = - (rxSuccessDelta == 0 && txSuccessDelta >= mMinTxSuccessWithoutRx); - if (dataStallBadTx && dataStallTxSuccessWithoutRx) { - mWifiMetrics.logWifiIsUnusableEvent(WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH); - return WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH; - } else if (dataStallBadTx) { - mWifiMetrics.logWifiIsUnusableEvent(WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX); - return WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX; - } else if (dataStallTxSuccessWithoutRx) { - mWifiMetrics.logWifiIsUnusableEvent( - WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX); - return WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX; + 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); + } } diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java index 3a79e590b..fe9ebea17 100644 --- a/service/java/com/android/server/wifi/WifiInjector.java +++ b/service/java/com/android/server/wifi/WifiInjector.java @@ -301,7 +301,8 @@ public class WifiInjector { mWifiDiagnostics = new WifiDiagnostics( mContext, this, mWifiNative, mBuildProperties, new LastMileLogger(this), mClock); - mWifiDataStall = new WifiDataStall(mContext, mFrameworkFacade, mWifiMetrics); + mWifiDataStall = new WifiDataStall(mContext, mFrameworkFacade, mWifiMetrics, + mDeviceConfigFacade, clientModeImplLooper, mClock); mWifiMetrics.setWifiDataStall(mWifiDataStall); mLinkProbeManager = new LinkProbeManager(mClock, mWifiNative, mWifiMetrics, mFrameworkFacade, mWifiCoreHandlerThread.getLooper(), mContext); diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java index c0b04d34c..1b7e8cdb3 100644 --- a/service/java/com/android/server/wifi/WifiMetrics.java +++ b/service/java/com/android/server/wifi/WifiMetrics.java @@ -2782,6 +2782,16 @@ public class WifiMetrics { + mExperimentValues.wifiDataStallMinTxSuccessWithoutRx); pw.println("mExperimentValues.linkSpeedCountsLoggingEnabled=" + mExperimentValues.linkSpeedCountsLoggingEnabled); + pw.println("mExperimentValues.dataStallDurationMs=" + + mExperimentValues.dataStallDurationMs); + pw.println("mExperimentValues.dataStallTxTputThrMbps=" + + mExperimentValues.dataStallTxTputThrMbps); + pw.println("mExperimentValues.dataStallRxTputThrMbps=" + + mExperimentValues.dataStallRxTputThrMbps); + pw.println("mExperimentValues.dataStallTxPerThr=" + + mExperimentValues.dataStallTxPerThr); + pw.println("mExperimentValues.dataStallCcaLevelThr=" + + mExperimentValues.dataStallCcaLevelThr); pw.println("WifiIsUnusableEventList: "); for (WifiIsUnusableWithTime event : mWifiIsUnusableList) { pw.println(event); @@ -5191,4 +5201,49 @@ public class WifiMetrics { mNumProvisionSuccess++; } } + + /** + * Sets the duration for evaluating Wifi condition to trigger a data stall + */ + public void setDataStallDurationMs(int duration) { + synchronized (mLock) { + mExperimentValues.dataStallDurationMs = duration; + } + } + + /** + * Sets the threshold of Tx throughput below which to trigger a data stall + */ + public void setDataStallTxTputThrMbps(int txTputThr) { + synchronized (mLock) { + mExperimentValues.dataStallTxTputThrMbps = txTputThr; + } + } + + /** + * Sets the threshold of Rx throughput below which to trigger a data stall + */ + public void setDataStallRxTputThrMbps(int rxTputThr) { + synchronized (mLock) { + mExperimentValues.dataStallRxTputThrMbps = rxTputThr; + } + } + + /** + * Sets the threshold of Tx packet error rate above which to trigger a data stall + */ + public void setDataStallTxPerThr(int txPerThr) { + synchronized (mLock) { + mExperimentValues.dataStallTxPerThr = txPerThr; + } + } + + /** + * Sets the threshold of CCA level above which to trigger a data stall + */ + public void setDataStallCcaLevelThr(int ccaLevel) { + synchronized (mLock) { + mExperimentValues.dataStallCcaLevelThr = ccaLevel; + } + } } -- cgit v1.2.3