600 lines
26 KiB
Java
600 lines
26 KiB
Java
/*
|
|
* 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 static com.android.server.wifi.util.InformationElementUtil.BssLoad.CHANNEL_UTILIZATION_SCALE;
|
|
|
|
import android.annotation.IntDef;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.content.Context;
|
|
import android.net.wifi.WifiInfo;
|
|
import android.net.wifi.WifiManager.DeviceMobilityState;
|
|
import android.os.Handler;
|
|
import android.telephony.PhoneStateListener;
|
|
import android.telephony.SubscriptionManager;
|
|
import android.telephony.TelephonyManager;
|
|
import android.util.Log;
|
|
|
|
import com.android.modules.utils.HandlerExecutor;
|
|
import com.android.server.wifi.ActiveModeWarden.PrimaryClientModeManagerChangedCallback;
|
|
import com.android.server.wifi.WifiNative.ConnectionCapabilities;
|
|
import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiIsUnusableEvent;
|
|
import com.android.server.wifi.util.InformationElementUtil.BssLoad;
|
|
import com.android.wifi.resources.R;
|
|
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
|
|
/**
|
|
* Looks for Wifi data stalls
|
|
*/
|
|
public class WifiDataStall {
|
|
private static final String TAG = "WifiDataStall";
|
|
private boolean mVerboseLoggingEnabled = false;
|
|
public static final int INVALID_THROUGHPUT = -1;
|
|
// Maximum time gap between two WifiLinkLayerStats to trigger a data stall
|
|
public static final int 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 = 5;
|
|
// Default CCA level when CCA stats are not available
|
|
public static final int DEFAULT_CCA_LEVEL_2G = CHANNEL_UTILIZATION_SCALE * 16 / 100;
|
|
public static final int DEFAULT_CCA_LEVEL_ABOVE_2G = CHANNEL_UTILIZATION_SCALE * 6 / 100;
|
|
// Minimum time interval in ms between two link layer stats cache updates
|
|
private static final int LLSTATS_CACHE_UPDATE_INTERVAL_MIN_MS = 30_000;
|
|
// Maximum time margin between two link layer stats for connection duration update
|
|
public static final int MAX_TIME_MARGIN_LAST_TWO_POLLS_MS = 200;
|
|
|
|
private final DeviceConfigFacade mDeviceConfigFacade;
|
|
private final WifiMetrics mWifiMetrics;
|
|
private final Context mContext;
|
|
private final WifiChannelUtilization mWifiChannelUtilization;
|
|
private TelephonyManager mTelephonyManager;
|
|
private final ThroughputPredictor mThroughputPredictor;
|
|
private final ActiveModeWarden mActiveModeWarden;
|
|
private final ClientModeImplMonitor mClientModeImplMonitor;
|
|
|
|
private int mLastFrequency = -1;
|
|
private String mLastBssid;
|
|
private long mDataStallStartTimeMs = -1;
|
|
private Clock mClock;
|
|
private boolean mDataStallTx = false;
|
|
private boolean mDataStallRx = false;
|
|
private long mLastTxBytes;
|
|
private long mLastRxBytes;
|
|
private boolean mIsThroughputSufficient = true;
|
|
private boolean mIsCellularDataAvailable = false;
|
|
private final PhoneStateListener mPhoneStateListener;
|
|
private boolean mPhoneStateListenerEnabled = false;
|
|
private int mTxTputKbps = INVALID_THROUGHPUT;
|
|
private int mRxTputKbps = INVALID_THROUGHPUT;
|
|
|
|
/** @hide */
|
|
@IntDef(prefix = { "CELLULAR_DATA_" }, value = {
|
|
CELLULAR_DATA_UNKNOWN,
|
|
CELLULAR_DATA_AVAILABLE,
|
|
CELLULAR_DATA_NOT_AVAILABLE,
|
|
})
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
public @interface CellularDataStatusCode {}
|
|
public static final int CELLULAR_DATA_UNKNOWN = 0;
|
|
public static final int CELLULAR_DATA_AVAILABLE = 1;
|
|
public static final int CELLULAR_DATA_NOT_AVAILABLE = 2;
|
|
|
|
private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback {
|
|
@Override
|
|
public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) {
|
|
update();
|
|
}
|
|
|
|
@Override
|
|
public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) {
|
|
update();
|
|
}
|
|
|
|
@Override
|
|
public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) {
|
|
update();
|
|
}
|
|
|
|
private void update() {
|
|
// Register/Unregister phone listener on wifi on/off.
|
|
if (mActiveModeWarden.getPrimaryClientModeManagerNullable() != null) {
|
|
enablePhoneStateListener();
|
|
} else {
|
|
disablePhoneStateListener();
|
|
}
|
|
}
|
|
}
|
|
|
|
private class PrimaryModeChangeCallback implements PrimaryClientModeManagerChangedCallback {
|
|
@Override
|
|
public void onChange(
|
|
@Nullable ConcreteClientModeManager prevPrimaryClientModeManager,
|
|
@Nullable ConcreteClientModeManager newPrimaryClientModeManager) {
|
|
// This is needed to reset state on an MBB switch or wifi toggle.
|
|
if (prevPrimaryClientModeManager != null) {
|
|
reset();
|
|
}
|
|
if (newPrimaryClientModeManager != null) {
|
|
init();
|
|
}
|
|
}
|
|
}
|
|
|
|
private class ClientModeImplListenerInternal implements ClientModeImplListener {
|
|
@Override
|
|
public void onConnectionEnd(@NonNull ConcreteClientModeManager clientModeManager) {
|
|
if (clientModeManager.getRole() == ActiveModeManager.ROLE_CLIENT_PRIMARY) {
|
|
reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
public WifiDataStall(WifiMetrics wifiMetrics, Context context,
|
|
DeviceConfigFacade deviceConfigFacade, WifiChannelUtilization wifiChannelUtilization,
|
|
Clock clock, Handler handler, ThroughputPredictor throughputPredictor,
|
|
ActiveModeWarden activeModeWarden, ClientModeImplMonitor clientModeImplMonitor) {
|
|
mDeviceConfigFacade = deviceConfigFacade;
|
|
mWifiMetrics = wifiMetrics;
|
|
mContext = context;
|
|
mClock = clock;
|
|
mWifiChannelUtilization = wifiChannelUtilization;
|
|
mWifiChannelUtilization.setCacheUpdateIntervalMs(LLSTATS_CACHE_UPDATE_INTERVAL_MIN_MS);
|
|
mThroughputPredictor = throughputPredictor;
|
|
mActiveModeWarden = activeModeWarden;
|
|
mClientModeImplMonitor = clientModeImplMonitor;
|
|
mPhoneStateListener = new PhoneStateListener(new HandlerExecutor(handler)) {
|
|
@Override
|
|
public void onDataConnectionStateChanged(int state, int networkType) {
|
|
if (state != TelephonyManager.DATA_CONNECTED
|
|
&& state != TelephonyManager.DATA_DISCONNECTED) {
|
|
Log.e(TAG, "onDataConnectionStateChanged unexpected State: " + state);
|
|
return;
|
|
}
|
|
mIsCellularDataAvailable = state == TelephonyManager.DATA_CONNECTED;
|
|
mActiveModeWarden.getPrimaryClientModeManager()
|
|
.onCellularConnectivityChanged(mIsCellularDataAvailable
|
|
? CELLULAR_DATA_AVAILABLE : CELLULAR_DATA_NOT_AVAILABLE);
|
|
logd("Cellular Data: " + mIsCellularDataAvailable);
|
|
}
|
|
};
|
|
mActiveModeWarden.registerPrimaryClientModeManagerChangedCallback(
|
|
new PrimaryModeChangeCallback());
|
|
mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback());
|
|
mClientModeImplMonitor.registerListener(new ClientModeImplListenerInternal());
|
|
}
|
|
|
|
/**
|
|
* initialization after wifi is enabled
|
|
*/
|
|
private void init() {
|
|
mWifiChannelUtilization.init(null);
|
|
reset();
|
|
}
|
|
|
|
/**
|
|
* Reset internal variables
|
|
*/
|
|
private void reset() {
|
|
mLastTxBytes = 0;
|
|
mLastRxBytes = 0;
|
|
mLastFrequency = -1;
|
|
mLastBssid = null;
|
|
mDataStallStartTimeMs = -1;
|
|
mDataStallTx = false;
|
|
mDataStallRx = false;
|
|
mIsThroughputSufficient = true;
|
|
mTxTputKbps = INVALID_THROUGHPUT;
|
|
mRxTputKbps = INVALID_THROUGHPUT;
|
|
}
|
|
|
|
private void createTelephonyManagerForDefaultDataSubIfNeeded() {
|
|
if (mTelephonyManager == null) {
|
|
mTelephonyManager = (TelephonyManager) mContext
|
|
.getSystemService(Context.TELEPHONY_SERVICE);
|
|
}
|
|
int defaultSubscriptionId = SubscriptionManager.getDefaultDataSubscriptionId();
|
|
if (defaultSubscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
|
|
&& defaultSubscriptionId != mTelephonyManager.getSubscriptionId()) {
|
|
mTelephonyManager = mTelephonyManager.createForSubscriptionId(
|
|
SubscriptionManager.getDefaultDataSubscriptionId());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset the PhoneStateListener to listen on the default data SIM.
|
|
*/
|
|
public void resetPhoneStateListener() {
|
|
disablePhoneStateListener();
|
|
mActiveModeWarden.getPrimaryClientModeManager()
|
|
.onCellularConnectivityChanged(CELLULAR_DATA_UNKNOWN);
|
|
enablePhoneStateListener();
|
|
}
|
|
|
|
/**
|
|
* Enable phone state listener
|
|
*/
|
|
private void enablePhoneStateListener() {
|
|
createTelephonyManagerForDefaultDataSubIfNeeded();
|
|
if (mTelephonyManager != null && !mPhoneStateListenerEnabled) {
|
|
mPhoneStateListenerEnabled = true;
|
|
mTelephonyManager.listen(mPhoneStateListener,
|
|
PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disable phone state listener
|
|
*/
|
|
private void disablePhoneStateListener() {
|
|
if (mTelephonyManager != null && mPhoneStateListenerEnabled) {
|
|
mPhoneStateListenerEnabled = false;
|
|
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable/Disable verbose logging.
|
|
* @param verbose true to enable and false to disable.
|
|
*/
|
|
public void enableVerboseLogging(boolean verbose) {
|
|
mVerboseLoggingEnabled = verbose;
|
|
mWifiChannelUtilization.enableVerboseLogging(verbose);
|
|
}
|
|
|
|
/**
|
|
* Update device mobility state
|
|
* @param newState the new device mobility state
|
|
*/
|
|
public void setDeviceMobilityState(@DeviceMobilityState int newState) {
|
|
mWifiChannelUtilization.setDeviceMobilityState(newState);
|
|
}
|
|
|
|
/**
|
|
* Check if current link layer throughput is sufficient.
|
|
* This should be called after checkDataStallAndThroughputSufficiency().
|
|
* @return true if it is sufficient or false if it is insufficient
|
|
*/
|
|
public boolean isThroughputSufficient() {
|
|
return mIsThroughputSufficient;
|
|
}
|
|
|
|
/**
|
|
* Check if cellular data is available
|
|
* @return true if it is available and false otherwise
|
|
*/
|
|
public boolean isCellularDataAvailable() {
|
|
return mIsCellularDataAvailable;
|
|
}
|
|
|
|
/**
|
|
* Get the latest Tx throughput based on Tx link speed, PER and channel utilization
|
|
* @return the latest estimated Tx throughput in Kbps if it is available
|
|
* or INVALID_THROUGHPUT if it is not available
|
|
*/
|
|
public int getTxThroughputKbps() {
|
|
logd("tx tput in kbps: " + mTxTputKbps);
|
|
return mTxTputKbps;
|
|
}
|
|
|
|
/**
|
|
* Get the latest Rx throughput based on Rx link speed and channel utilization
|
|
* @return the latest estimated Rx throughput in Kbps if it is available
|
|
* or INVALID_THROUGHPUT if it is not available
|
|
*/
|
|
public int getRxThroughputKbps() {
|
|
logd("rx tput in kbps: " + mRxTputKbps);
|
|
return mRxTputKbps;
|
|
}
|
|
|
|
/**
|
|
* Update data stall detection, check throughput sufficiency and report wifi health stat
|
|
* with the latest link layer stats
|
|
* @param connectionCapabilities Connection capabilities.
|
|
* @param oldStats second most recent WifiLinkLayerStats
|
|
* @param newStats most recent WifiLinkLayerStats
|
|
* @param wifiInfo WifiInfo for current connection
|
|
* @return trigger type of WifiIsUnusableEvent
|
|
*
|
|
* Note: This is only collected for primary STA currently because RSSI polling is disabled for
|
|
* non-primary STAs.
|
|
*/
|
|
public int checkDataStallAndThroughputSufficiency(
|
|
@NonNull String ifaceName,
|
|
@NonNull ConnectionCapabilities connectionCapabilities,
|
|
@Nullable WifiLinkLayerStats oldStats,
|
|
@Nullable WifiLinkLayerStats newStats,
|
|
@NonNull WifiInfo wifiInfo,
|
|
long txBytes, long rxBytes) {
|
|
int currFrequency = wifiInfo.getFrequency();
|
|
mWifiChannelUtilization.refreshChannelStatsAndChannelUtilization(newStats, currFrequency);
|
|
int ccaLevel = mWifiChannelUtilization.getUtilizationRatio(currFrequency);
|
|
mWifiMetrics.incrementChannelUtilizationCount(ccaLevel, currFrequency);
|
|
if (oldStats == null || newStats == null) {
|
|
// First poll after new association
|
|
// Update throughput with prediction
|
|
if (wifiInfo.getRssi() != WifiInfo.INVALID_RSSI && connectionCapabilities != null) {
|
|
mTxTputKbps = mThroughputPredictor.predictTxThroughput(connectionCapabilities,
|
|
wifiInfo.getRssi(), currFrequency, ccaLevel) * 1000;
|
|
mRxTputKbps = mThroughputPredictor.predictRxThroughput(connectionCapabilities,
|
|
wifiInfo.getRssi(), currFrequency, ccaLevel) * 1000;
|
|
}
|
|
mIsThroughputSufficient = true;
|
|
mWifiMetrics.resetWifiIsUnusableLinkLayerStats();
|
|
mWifiMetrics.incrementThroughputKbpsCount(mTxTputKbps, mRxTputKbps, currFrequency);
|
|
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);
|
|
int timeDeltaLastTwoPollsMs = (int) (newStats.timeStampInMs - oldStats.timeStampInMs);
|
|
|
|
long totalTxDelta = txSuccessDelta + txRetriesDelta;
|
|
boolean isTxTrafficHigh = (totalTxDelta * 1000)
|
|
> (mDeviceConfigFacade.getTxPktPerSecondThr() * timeDeltaLastTwoPollsMs);
|
|
boolean isRxTrafficHigh = (rxSuccessDelta * 1000)
|
|
> (mDeviceConfigFacade.getRxPktPerSecondThr() * timeDeltaLastTwoPollsMs);
|
|
if (timeDeltaLastTwoPollsMs < 0
|
|
|| txSuccessDelta < 0
|
|
|| txRetriesDelta < 0
|
|
|| txBadDelta < 0
|
|
|| rxSuccessDelta < 0) {
|
|
mIsThroughputSufficient = true;
|
|
// There was a reset in WifiLinkLayerStats
|
|
mWifiMetrics.resetWifiIsUnusableLinkLayerStats();
|
|
return WifiIsUnusableEvent.TYPE_UNKNOWN;
|
|
}
|
|
|
|
mWifiMetrics.updateWifiIsUnusableLinkLayerStats(txSuccessDelta, txRetriesDelta,
|
|
txBadDelta, rxSuccessDelta, timeDeltaLastTwoPollsMs);
|
|
|
|
int txLinkSpeedMbps = wifiInfo.getLinkSpeed();
|
|
int rxLinkSpeedMbps = wifiInfo.getRxLinkSpeedMbps();
|
|
boolean isSameBssidAndFreq = mLastBssid == null || mLastFrequency == -1
|
|
|| (mLastBssid.equals(wifiInfo.getBSSID())
|
|
&& mLastFrequency == currFrequency);
|
|
mLastFrequency = currFrequency;
|
|
mLastBssid = wifiInfo.getBSSID();
|
|
|
|
if (ccaLevel == BssLoad.INVALID) {
|
|
ccaLevel = wifiInfo.is24GHz() ? DEFAULT_CCA_LEVEL_2G : DEFAULT_CCA_LEVEL_ABOVE_2G;
|
|
logd(" use default cca Level");
|
|
}
|
|
logd(" ccaLevel = " + ccaLevel);
|
|
|
|
int txPer = updateTxPer(txSuccessDelta, txRetriesDelta, isSameBssidAndFreq,
|
|
isTxTrafficHigh);
|
|
|
|
boolean isTxTputLow = false;
|
|
boolean isRxTputLow = false;
|
|
|
|
if (txLinkSpeedMbps > 0) {
|
|
// Exclude update with low rate management frames
|
|
if (isTxTrafficHigh
|
|
|| txLinkSpeedMbps > mDeviceConfigFacade.getTxLinkSpeedLowThresholdMbps()) {
|
|
mTxTputKbps = (int) ((long) txLinkSpeedMbps * 1000 * (100 - txPer) / 100
|
|
* (CHANNEL_UTILIZATION_SCALE - ccaLevel) / CHANNEL_UTILIZATION_SCALE);
|
|
}
|
|
isTxTputLow = mTxTputKbps < mDeviceConfigFacade.getDataStallTxTputThrKbps();
|
|
} else {
|
|
mTxTputKbps = INVALID_THROUGHPUT;
|
|
}
|
|
|
|
if (rxLinkSpeedMbps > 0) {
|
|
// Exclude update with low rate management frames
|
|
if (isRxTrafficHigh
|
|
|| rxLinkSpeedMbps > mDeviceConfigFacade.getRxLinkSpeedLowThresholdMbps()) {
|
|
mRxTputKbps = (int) ((long) rxLinkSpeedMbps * 1000
|
|
* (CHANNEL_UTILIZATION_SCALE - ccaLevel) / CHANNEL_UTILIZATION_SCALE);
|
|
}
|
|
isRxTputLow = mRxTputKbps < mDeviceConfigFacade.getDataStallRxTputThrKbps();
|
|
} else {
|
|
mRxTputKbps = INVALID_THROUGHPUT;
|
|
}
|
|
mWifiMetrics.incrementThroughputKbpsCount(mTxTputKbps, mRxTputKbps, currFrequency);
|
|
|
|
mIsThroughputSufficient = isThroughputSufficientInternal(mTxTputKbps, mRxTputKbps,
|
|
isTxTrafficHigh, isRxTrafficHigh, timeDeltaLastTwoPollsMs, txBytes, rxBytes);
|
|
|
|
int maxTimeDeltaMs = mContext.getResources().getInteger(
|
|
R.integer.config_wifiPollRssiIntervalMilliseconds)
|
|
+ MAX_TIME_MARGIN_LAST_TWO_POLLS_MS;
|
|
if (timeDeltaLastTwoPollsMs > 0 && timeDeltaLastTwoPollsMs <= maxTimeDeltaMs) {
|
|
mWifiMetrics.incrementConnectionDuration(timeDeltaLastTwoPollsMs,
|
|
mIsThroughputSufficient, mIsCellularDataAvailable);
|
|
}
|
|
|
|
boolean possibleDataStallTx = isTxTputLow
|
|
|| ccaLevel >= mDeviceConfigFacade.getDataStallCcaLevelThr()
|
|
|| txPer >= mDeviceConfigFacade.getDataStallTxPerThr();
|
|
boolean possibleDataStallRx = isRxTputLow
|
|
|| ccaLevel >= mDeviceConfigFacade.getDataStallCcaLevelThr();
|
|
|
|
boolean dataStallTx = isTxTrafficHigh ? possibleDataStallTx : mDataStallTx;
|
|
boolean dataStallRx = isRxTrafficHigh ? possibleDataStallRx : mDataStallRx;
|
|
|
|
return detectConsecutiveTwoDataStalls(ifaceName, timeDeltaLastTwoPollsMs, dataStallTx,
|
|
dataStallRx);
|
|
}
|
|
|
|
// Data stall event is triggered if there are consecutive Tx and/or Rx data stalls
|
|
// 1st data stall should be preceded by no data stall
|
|
// Reset mDataStallStartTimeMs to -1 if currently there is no Tx or Rx data stall
|
|
private int detectConsecutiveTwoDataStalls(String ifaceName, int timeDeltaLastTwoPollsMs,
|
|
boolean dataStallTx, boolean dataStallRx) {
|
|
if (timeDeltaLastTwoPollsMs >= MAX_MS_DELTA_FOR_DATA_STALL) {
|
|
return WifiIsUnusableEvent.TYPE_UNKNOWN;
|
|
}
|
|
|
|
if (dataStallTx || dataStallRx) {
|
|
mDataStallTx = mDataStallTx || dataStallTx;
|
|
mDataStallRx = mDataStallRx || dataStallRx;
|
|
if (mDataStallStartTimeMs == -1) {
|
|
mDataStallStartTimeMs = mClock.getElapsedSinceBootMillis();
|
|
if (mDeviceConfigFacade.getDataStallDurationMs() == 0) {
|
|
mDataStallStartTimeMs = -1;
|
|
int result = calculateUsabilityEventType(ifaceName, mDataStallTx,
|
|
mDataStallRx);
|
|
mDataStallRx = false;
|
|
mDataStallTx = false;
|
|
return result;
|
|
}
|
|
} else {
|
|
long elapsedTime = mClock.getElapsedSinceBootMillis() - mDataStallStartTimeMs;
|
|
if (elapsedTime >= mDeviceConfigFacade.getDataStallDurationMs()) {
|
|
mDataStallStartTimeMs = -1;
|
|
if (elapsedTime <= VALIDITY_PERIOD_OF_DATA_STALL_START_MS) {
|
|
int result = calculateUsabilityEventType(ifaceName, 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 updateTxPer(long txSuccessDelta, long txRetriesDelta, boolean isSameBssidAndFreq,
|
|
boolean isTxTrafficHigh) {
|
|
if (!isSameBssidAndFreq) {
|
|
return DEFAULT_TX_PACKET_ERROR_RATE;
|
|
}
|
|
long txAttempts = txSuccessDelta + txRetriesDelta;
|
|
if (txAttempts <= 0 || !isTxTrafficHigh) {
|
|
return DEFAULT_TX_PACKET_ERROR_RATE;
|
|
}
|
|
return (int) (txRetriesDelta * 100 / txAttempts);
|
|
}
|
|
private int calculateUsabilityEventType(String ifaceName, 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(ifaceName, result);
|
|
return result;
|
|
}
|
|
|
|
private boolean isThroughputSufficientInternal(int l2TxTputKbps, int l2RxTputKbps,
|
|
boolean isTxTrafficHigh, boolean isRxTrafficHigh, int timeDeltaLastTwoPollsMs,
|
|
long txBytes, long rxBytes) {
|
|
if (timeDeltaLastTwoPollsMs > MAX_MS_DELTA_FOR_DATA_STALL
|
|
|| mLastTxBytes == 0 || mLastRxBytes == 0) {
|
|
mLastTxBytes = txBytes;
|
|
mLastRxBytes = rxBytes;
|
|
return true;
|
|
}
|
|
|
|
int l3TxTputKbps = (int) ((txBytes - mLastTxBytes) * 8 / timeDeltaLastTwoPollsMs);
|
|
int l3RxTputKbps = (int) ((rxBytes - mLastRxBytes) * 8 / timeDeltaLastTwoPollsMs);
|
|
|
|
mLastTxBytes = txBytes;
|
|
mLastRxBytes = rxBytes;
|
|
|
|
boolean isTxTputSufficient = isL2ThroughputSufficient(l2TxTputKbps, l3TxTputKbps, false);
|
|
boolean isRxTputSufficient = isL2ThroughputSufficient(l2RxTputKbps, l3RxTputKbps, true);
|
|
isTxTputSufficient = detectAndOverrideFalseInSufficient(
|
|
isTxTputSufficient, isTxTrafficHigh, mIsThroughputSufficient);
|
|
isRxTputSufficient = detectAndOverrideFalseInSufficient(
|
|
isRxTputSufficient, isRxTrafficHigh, mIsThroughputSufficient);
|
|
|
|
boolean isThroughputSufficient = isTxTputSufficient && isRxTputSufficient;
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
logd(sb.append("L2 txTputKbps: ").append(l2TxTputKbps)
|
|
.append(", rxTputKbps: ").append(l2RxTputKbps)
|
|
.append(", L3 txTputKbps: ").append(l3TxTputKbps)
|
|
.append(", rxTputKbps: ").append(l3RxTputKbps)
|
|
.append(", TxTrafficHigh: ").append(isTxTrafficHigh)
|
|
.append(", RxTrafficHigh: ").append(isRxTrafficHigh)
|
|
.append(", Throughput Sufficient: ").append(isThroughputSufficient)
|
|
.toString());
|
|
return isThroughputSufficient;
|
|
}
|
|
|
|
/**
|
|
* L2 tput is sufficient when one of the following conditions is met
|
|
* 1) L3 tput is low and L2 tput is above its low threshold
|
|
* 2) L3 tput is not low and L2 tput over L3 tput ratio is above sufficientRatioThr
|
|
* 3) L3 tput is not low and L2 tput is above its high threshold
|
|
* 4) L2 tput is invalid
|
|
*/
|
|
private boolean isL2ThroughputSufficient(int l2TputKbps, int l3TputKbps, boolean isForRxTput) {
|
|
if (l2TputKbps == INVALID_THROUGHPUT) return true;
|
|
int tputSufficientLowThrKbps = mDeviceConfigFacade.getTxTputSufficientLowThrKbps();
|
|
int tputSufficientHighThrKbps = mDeviceConfigFacade.getTxTputSufficientHighThrKbps();
|
|
if (isForRxTput) {
|
|
tputSufficientLowThrKbps = mDeviceConfigFacade.getRxTputSufficientLowThrKbps();
|
|
tputSufficientHighThrKbps = mDeviceConfigFacade.getRxTputSufficientHighThrKbps();
|
|
}
|
|
boolean isL3TputLow = (l3TputKbps * mDeviceConfigFacade.getTputSufficientRatioThrDen())
|
|
< (tputSufficientLowThrKbps * mDeviceConfigFacade.getTputSufficientRatioThrNum());
|
|
boolean isL2TputAboveLowThr = l2TputKbps >= tputSufficientLowThrKbps;
|
|
if (isL3TputLow) return isL2TputAboveLowThr;
|
|
|
|
boolean isL2TputAboveHighThr = l2TputKbps >= tputSufficientHighThrKbps;
|
|
boolean isL2L3TputRatioAboveThr =
|
|
(l2TputKbps * mDeviceConfigFacade.getTputSufficientRatioThrDen())
|
|
>= (l3TputKbps * mDeviceConfigFacade.getTputSufficientRatioThrNum());
|
|
return isL2TputAboveHighThr || isL2L3TputRatioAboveThr;
|
|
}
|
|
|
|
private boolean detectAndOverrideFalseInSufficient(boolean isTputSufficient,
|
|
boolean isTrafficHigh, boolean lastIsTputSufficient) {
|
|
boolean possibleFalseInsufficient = (!isTrafficHigh && !isTputSufficient);
|
|
return possibleFalseInsufficient ? lastIsTputSufficient : isTputSufficient;
|
|
}
|
|
|
|
private void logd(String string) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.d(TAG, string);
|
|
}
|
|
}
|
|
}
|