blob: 5cd8a5ce94400b9e3d6861a6da04d85d5b3518e4 [file] [log] [blame]
/*
* Copyright 2019 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.INVALID;
import static com.android.server.wifi.util.InformationElementUtil.BssLoad.MAX_CHANNEL_UTILIZATION;
import static com.android.server.wifi.util.InformationElementUtil.BssLoad.MIN_CHANNEL_UTILIZATION;
import android.annotation.NonNull;
import android.content.Context;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiAnnotations.WifiStandard;
import android.net.wifi.WifiInfo;
import android.net.wifi.nl80211.DeviceWiphyCapabilities;
import android.util.Log;
import com.android.wifi.resources.R;
/**
* A class that predicts network throughput based on RSSI, channel utilization, channel width,
* WiFi standard (PHY/MAC mode), Nss and other radio information.
*/
public class ThroughputPredictor {
private static final String TAG = "WifiThroughputPredictor";
private boolean mVerboseLoggingEnabled = false;
// Default value of channel utilization at 2G when channel utilization is not available from
// BssLoad IE or from link layer stats
public static final int CHANNEL_UTILIZATION_DEFAULT_2G = MAX_CHANNEL_UTILIZATION * 6 / 16;
// Default value of channel utilization at 5G when channel utilization is not available from
// BssLoad IE or from link layer stats
public static final int CHANNEL_UTILIZATION_DEFAULT_ABOVE_2G = MAX_CHANNEL_UTILIZATION / 16;
// Channel utilization boost when bluetooth is in the connected mode
public static final int CHANNEL_UTILIZATION_BOOST_BT_CONNECTED_2G = MAX_CHANNEL_UTILIZATION / 4;
//TODO: b/145133625 Need to consider 6GHz
// Number of data tones per OFDM symbol
private static final int NUM_TONE_PER_SYM_LEGACY = 48;
private static final int NUM_TONE_PER_SYM_11N_20MHZ = 52;
private static final int NUM_TONE_PER_SYM_11N_40MHZ = 108;
private static final int NUM_TONE_PER_SYM_11AC_20MHZ = 52;
private static final int NUM_TONE_PER_SYM_11AC_40MHZ = 108;
private static final int NUM_TONE_PER_SYM_11AC_80MHZ = 234;
private static final int NUM_TONE_PER_SYM_11AC_160MHZ = 468;
private static final int NUM_TONE_PER_SYM_11AX_20MHZ = 234;
private static final int NUM_TONE_PER_SYM_11AX_40MHZ = 468;
private static final int NUM_TONE_PER_SYM_11AX_80MHZ = 980;
private static final int NUM_TONE_PER_SYM_11AX_160MHZ = 1960;
// 11ag OFDM symbol duration in ns
private static final int SYM_DURATION_LEGACY_NS = 4000;
// 11n OFDM symbol duration in ns with 0.4us guard interval
private static final int SYM_DURATION_11N_NS = 3600;
// 11ac OFDM symbol duration in ns with 0.4us guard interval
private static final int SYM_DURATION_11AC_NS = 3600;
// 11ax OFDM symbol duration in ns with 0.8us guard interval
private static final int SYM_DURATION_11AX_NS = 13600;
private static final int MICRO_TO_NANO_RATIO = 1000;
// The scaling factor for integer representation of bitPerTone and MAX_BITS_PER_TONE_XXX
private static final int BIT_PER_TONE_SCALE = 1000;
private static final int MAX_BITS_PER_TONE_LEGACY =
(int) Math.round((6 * 3.0 * BIT_PER_TONE_SCALE) / 4.0);
private static final int MAX_BITS_PER_TONE_11N =
(int) Math.round((6 * 5.0 * BIT_PER_TONE_SCALE) / 6.0);
private static final int MAX_BITS_PER_TONE_11AC =
(int) Math.round((8 * 5.0 * BIT_PER_TONE_SCALE) / 6.0);
private static final int MAX_BITS_PER_TONE_11AX =
(int) Math.round((10 * 5.0 * BIT_PER_TONE_SCALE) / 6.0);
// snrDb-to-bitPerTone lookup table (LUT) used at low SNR
// snr = Math.pow(10.0, snrDb / 10.0);
// bitPerTone = (int) (Math.log10(1 + snr) / Math.log10(2.0) * BIT_PER_TONE_SCALE)
private static final int TWO_IN_DB = 3;
private static final int SNR_DB_TO_BIT_PER_TONE_HIGH_SNR_SCALE = BIT_PER_TONE_SCALE / TWO_IN_DB;
private static final int SNR_DB_TO_BIT_PER_TONE_LUT_MIN = -10; // minimum snrDb supported by LUT
private static final int SNR_DB_TO_BIT_PER_TONE_LUT_MAX = 9; // maximum snrDb supported by LUT
private static final int[] SNR_DB_TO_BIT_PER_TONE_LUT = {0, 171, 212, 262, 323, 396, 484, 586,
706, 844, 1000, 1176, 1370, 1583, 1812, 2058, 2317, 2588, 2870, 3161};
// Thermal noise floor power in dBm integrated over 20MHz with 5.5dB noise figure at 25C
private static final int NOISE_FLOOR_20MHZ_DBM = -96;
// A fudge factor to represent HW implementation margin in dB.
// Predicted throughput matches pretty well with OTA throughput with this fudge factor.
private static final int SNR_MARGIN_DB = 16;
private static final int MAX_NUM_SPATIAL_STREAM_11AX = 8;
private static final int MAX_NUM_SPATIAL_STREAM_11AC = 8;
private static final int MAX_NUM_SPATIAL_STREAM_11N = 4;
private static final int MAX_NUM_SPATIAL_STREAM_LEGACY = 1;
private final Context mContext;
ThroughputPredictor(Context context) {
mContext = context;
}
/**
* Enable/Disable verbose logging.
*
* @param verbose true to enable and false to disable.
*/
public void enableVerboseLogging(boolean verbose) {
mVerboseLoggingEnabled = verbose;
}
/**
* Predict maximum Tx throughput supported by connected network at the highest RSSI
* with the lowest channel utilization
* @return predicted maximum Tx throughput in Mbps
*/
public int predictMaxTxThroughput(@NonNull WifiNative.ConnectionCapabilities capabilities) {
return predictThroughputInternal(capabilities.wifiStandard, capabilities.channelBandwidth,
WifiInfo.MAX_RSSI, capabilities.maxNumberTxSpatialStreams, MIN_CHANNEL_UTILIZATION);
}
/**
* Predict maximum Rx throughput supported by connected network at the highest RSSI
* with the lowest channel utilization
* @return predicted maximum Rx throughput in Mbps
*/
public int predictMaxRxThroughput(@NonNull WifiNative.ConnectionCapabilities capabilities) {
return predictThroughputInternal(capabilities.wifiStandard, capabilities.channelBandwidth,
WifiInfo.MAX_RSSI, capabilities.maxNumberRxSpatialStreams, MIN_CHANNEL_UTILIZATION);
}
/**
* Predict Tx throughput with current connection capabilities, RSSI and channel utilization
* @return predicted Tx throughput in Mbps
*/
public int predictTxThroughput(@NonNull WifiNative.ConnectionCapabilities capabilities,
int rssiDbm, int frequency, int channelUtilization) {
int channelUtilizationFinal = getValidChannelUtilization(frequency,
INVALID, channelUtilization, false);
return predictThroughputInternal(capabilities.wifiStandard, capabilities.channelBandwidth,
rssiDbm, capabilities.maxNumberTxSpatialStreams, channelUtilizationFinal);
}
/**
* Predict Rx throughput with current connection capabilities, RSSI and channel utilization
* @return predicted Rx throughput in Mbps
*/
public int predictRxThroughput(@NonNull WifiNative.ConnectionCapabilities capabilities,
int rssiDbm, int frequency, int channelUtilization) {
int channelUtilizationFinal = getValidChannelUtilization(frequency,
INVALID, channelUtilization, false);
return predictThroughputInternal(capabilities.wifiStandard, capabilities.channelBandwidth,
rssiDbm, capabilities.maxNumberRxSpatialStreams, channelUtilizationFinal);
}
/**
* Predict network throughput given by the current channel condition and RSSI
* @param deviceCapabilities Phy Capabilities of the device
* @param wifiStandardAp the highest wifi standard supported by AP
* @param channelWidthAp the channel bandwidth of AP
* @param rssiDbm the scan RSSI in dBm
* @param frequency the center frequency of primary 20MHz channel
* @param maxNumSpatialStreamAp the maximum number of spatial streams supported by AP
* @param channelUtilizationBssLoad the channel utilization ratio indicated from BssLoad IE
* @param channelUtilizationLinkLayerStats the channel utilization ratio detected from scan
* @param isBluetoothConnected whether the bluetooth adaptor is in connected mode
* @return predicted throughput in Mbps
*/
public int predictThroughput(DeviceWiphyCapabilities deviceCapabilities,
@WifiStandard int wifiStandardAp,
int channelWidthAp, int rssiDbm, int frequency, int maxNumSpatialStreamAp,
int channelUtilizationBssLoad, int channelUtilizationLinkLayerStats,
boolean isBluetoothConnected) {
if (deviceCapabilities == null) {
Log.e(TAG, "Null device capabilities passed to throughput predictor");
return 0;
}
int maxNumSpatialStreamDevice = Math.min(deviceCapabilities.getMaxNumberTxSpatialStreams(),
deviceCapabilities.getMaxNumberRxSpatialStreams());
if (mContext.getResources().getBoolean(
R.bool.config_wifiFrameworkMaxNumSpatialStreamDeviceOverrideEnable)) {
maxNumSpatialStreamDevice = mContext.getResources().getInteger(
R.integer.config_wifiFrameworkMaxNumSpatialStreamDeviceOverrideValue);
}
int maxNumSpatialStream = Math.min(maxNumSpatialStreamDevice, maxNumSpatialStreamAp);
// Get minimum standard support between device and AP
int wifiStandard;
switch (wifiStandardAp) {
case ScanResult.WIFI_STANDARD_11AX:
if (deviceCapabilities.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11AX)) {
wifiStandard = ScanResult.WIFI_STANDARD_11AX;
break;
}
//FALL THROUGH
case ScanResult.WIFI_STANDARD_11AC:
if (deviceCapabilities.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11AC)) {
wifiStandard = ScanResult.WIFI_STANDARD_11AC;
break;
}
//FALL THROUGH
case ScanResult.WIFI_STANDARD_11N:
if (deviceCapabilities.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11N)) {
wifiStandard = ScanResult.WIFI_STANDARD_11N;
break;
}
//FALL THROUGH
default:
wifiStandard = ScanResult.WIFI_STANDARD_LEGACY;
}
// Calculate channel width
int channelWidth;
switch (channelWidthAp) {
case ScanResult.CHANNEL_WIDTH_160MHZ:
if (deviceCapabilities.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_160MHZ)) {
channelWidth = ScanResult.CHANNEL_WIDTH_160MHZ;
break;
}
// FALL THROUGH
case ScanResult.CHANNEL_WIDTH_80MHZ:
if (deviceCapabilities.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ)) {
channelWidth = ScanResult.CHANNEL_WIDTH_80MHZ;
break;
}
// FALL THROUGH
case ScanResult.CHANNEL_WIDTH_40MHZ:
if (deviceCapabilities.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_40MHZ)) {
channelWidth = ScanResult.CHANNEL_WIDTH_40MHZ;
break;
}
// FALL THROUGH
default:
channelWidth = ScanResult.CHANNEL_WIDTH_20MHZ;
}
if (mVerboseLoggingEnabled) {
StringBuilder sb = new StringBuilder();
Log.d(TAG, sb.append("AP Nss: ").append(maxNumSpatialStreamAp)
.append(", Device Nss: ").append(maxNumSpatialStreamDevice)
.append(", freq: ").append(frequency)
.toString());
}
int channelUtilization = getValidChannelUtilization(frequency,
channelUtilizationBssLoad,
channelUtilizationLinkLayerStats,
isBluetoothConnected);
return predictThroughputInternal(wifiStandard, channelWidth, rssiDbm, maxNumSpatialStream,
channelUtilization);
}
private int predictThroughputInternal(@WifiStandard int wifiStandard,
int channelWidth, int rssiDbm, int maxNumSpatialStream, int channelUtilization) {
// channel bandwidth in MHz = 20MHz * (2 ^ channelWidthFactor);
int channelWidthFactor;
int numTonePerSym;
int symDurationNs;
int maxBitsPerTone;
if (maxNumSpatialStream < 1) {
Log.e(TAG, "maxNumSpatialStream < 1 due to wrong implementation. Overridden to 1");
maxNumSpatialStream = 1;
}
if (wifiStandard == ScanResult.WIFI_STANDARD_UNKNOWN) {
return WifiInfo.LINK_SPEED_UNKNOWN;
} else if (wifiStandard == ScanResult.WIFI_STANDARD_LEGACY) {
numTonePerSym = NUM_TONE_PER_SYM_LEGACY;
channelWidthFactor = 0;
maxNumSpatialStream = MAX_NUM_SPATIAL_STREAM_LEGACY;
maxBitsPerTone = MAX_BITS_PER_TONE_LEGACY;
symDurationNs = SYM_DURATION_LEGACY_NS;
} else if (wifiStandard == ScanResult.WIFI_STANDARD_11N) {
if (channelWidth == ScanResult.CHANNEL_WIDTH_20MHZ) {
numTonePerSym = NUM_TONE_PER_SYM_11N_20MHZ;
channelWidthFactor = 0;
} else {
numTonePerSym = NUM_TONE_PER_SYM_11N_40MHZ;
channelWidthFactor = 1;
}
maxNumSpatialStream = Math.min(maxNumSpatialStream, MAX_NUM_SPATIAL_STREAM_11N);
maxBitsPerTone = MAX_BITS_PER_TONE_11N;
symDurationNs = SYM_DURATION_11N_NS;
} else if (wifiStandard == ScanResult.WIFI_STANDARD_11AC) {
if (channelWidth == ScanResult.CHANNEL_WIDTH_20MHZ) {
numTonePerSym = NUM_TONE_PER_SYM_11AC_20MHZ;
channelWidthFactor = 0;
} else if (channelWidth == ScanResult.CHANNEL_WIDTH_40MHZ) {
numTonePerSym = NUM_TONE_PER_SYM_11AC_40MHZ;
channelWidthFactor = 1;
} else if (channelWidth == ScanResult.CHANNEL_WIDTH_80MHZ) {
numTonePerSym = NUM_TONE_PER_SYM_11AC_80MHZ;
channelWidthFactor = 2;
} else {
numTonePerSym = NUM_TONE_PER_SYM_11AC_160MHZ;
channelWidthFactor = 3;
}
maxNumSpatialStream = Math.min(maxNumSpatialStream, MAX_NUM_SPATIAL_STREAM_11AC);
maxBitsPerTone = MAX_BITS_PER_TONE_11AC;
symDurationNs = SYM_DURATION_11AC_NS;
} else { // ScanResult.WIFI_STANDARD_11AX
if (channelWidth == ScanResult.CHANNEL_WIDTH_20MHZ) {
numTonePerSym = NUM_TONE_PER_SYM_11AX_20MHZ;
channelWidthFactor = 0;
} else if (channelWidth == ScanResult.CHANNEL_WIDTH_40MHZ) {
numTonePerSym = NUM_TONE_PER_SYM_11AX_40MHZ;
channelWidthFactor = 1;
} else if (channelWidth == ScanResult.CHANNEL_WIDTH_80MHZ) {
numTonePerSym = NUM_TONE_PER_SYM_11AX_80MHZ;
channelWidthFactor = 2;
} else {
numTonePerSym = NUM_TONE_PER_SYM_11AX_160MHZ;
channelWidthFactor = 3;
}
maxNumSpatialStream = Math.min(maxNumSpatialStream, MAX_NUM_SPATIAL_STREAM_11AX);
maxBitsPerTone = MAX_BITS_PER_TONE_11AX;
symDurationNs = SYM_DURATION_11AX_NS;
}
// noiseFloorDbBoost = 10 * log10 * (2 ^ channelWidthFactor)
int noiseFloorDbBoost = TWO_IN_DB * channelWidthFactor;
int noiseFloorDbm = NOISE_FLOOR_20MHZ_DBM + noiseFloorDbBoost + SNR_MARGIN_DB;
int snrDb = rssiDbm - noiseFloorDbm;
int bitPerTone = calculateBitPerTone(snrDb);
bitPerTone = Math.min(bitPerTone, maxBitsPerTone);
long bitPerToneTotal = bitPerTone * maxNumSpatialStream;
long numBitPerSym = bitPerToneTotal * numTonePerSym;
int phyRateMbps = (int) ((numBitPerSym * MICRO_TO_NANO_RATIO)
/ (symDurationNs * BIT_PER_TONE_SCALE));
int airTimeFraction = calculateAirTimeFraction(channelUtilization, channelWidthFactor);
int throughputMbps = (phyRateMbps * airTimeFraction) / MAX_CHANNEL_UTILIZATION;
if (mVerboseLoggingEnabled) {
StringBuilder sb = new StringBuilder();
Log.d(TAG, sb.append(" BW: ").append(channelWidth)
.append(" RSSI: ").append(rssiDbm)
.append(" Nss: ").append(maxNumSpatialStream)
.append(" Mode: ").append(wifiStandard)
.append(" symDur: ").append(symDurationNs)
.append(" snrDb ").append(snrDb)
.append(" bitPerTone: ").append(bitPerTone)
.append(" rate: ").append(phyRateMbps)
.append(" throughput: ").append(throughputMbps)
.toString());
}
return throughputMbps;
}
// Calculate the number of bits per tone based on the input of SNR in dB
// The output is scaled up by BIT_PER_TONE_SCALE for integer representation
private static int calculateBitPerTone(int snrDb) {
int bitPerTone;
if (snrDb <= SNR_DB_TO_BIT_PER_TONE_LUT_MAX) {
int lut_in_idx = Math.max(snrDb, SNR_DB_TO_BIT_PER_TONE_LUT_MIN)
- SNR_DB_TO_BIT_PER_TONE_LUT_MIN;
lut_in_idx = Math.min(lut_in_idx, SNR_DB_TO_BIT_PER_TONE_LUT.length - 1);
bitPerTone = SNR_DB_TO_BIT_PER_TONE_LUT[lut_in_idx];
} else {
// bitPerTone = Math.log10(1+snr)/Math.log10(2) can be approximated as
// Math.log10(snr) / 0.3 = log10(10^(snrDb/10)) / 0.3 = snrDb / 3
// SNR_DB_TO_BIT_PER_TONE_HIGH_SNR_SCALE = BIT_PER_TONE_SCALE / 3
bitPerTone = snrDb * SNR_DB_TO_BIT_PER_TONE_HIGH_SNR_SCALE;
}
return bitPerTone;
}
private int getValidChannelUtilization(int frequency, int channelUtilizationBssLoad,
int channelUtilizationLinkLayerStats, boolean isBluetoothConnected) {
int channelUtilization;
boolean is2G = ScanResult.is24GHz(frequency);
if (isValidUtilizationRatio(channelUtilizationBssLoad)) {
channelUtilization = channelUtilizationBssLoad;
} else if (isValidUtilizationRatio(channelUtilizationLinkLayerStats)) {
channelUtilization = channelUtilizationLinkLayerStats;
} else {
channelUtilization = is2G ? CHANNEL_UTILIZATION_DEFAULT_2G :
CHANNEL_UTILIZATION_DEFAULT_ABOVE_2G;
}
if (is2G && isBluetoothConnected) {
channelUtilization += CHANNEL_UTILIZATION_BOOST_BT_CONNECTED_2G;
channelUtilization = Math.min(channelUtilization, MAX_CHANNEL_UTILIZATION);
}
if (mVerboseLoggingEnabled) {
StringBuilder sb = new StringBuilder();
Log.d(TAG, sb.append(" utilization (BssLoad) ").append(channelUtilizationBssLoad)
.append(" utilization (LLStats) ").append(channelUtilizationLinkLayerStats)
.append(" isBluetoothConnected: ").append(isBluetoothConnected)
.append(" final utilization: ").append(channelUtilization)
.toString());
}
return channelUtilization;
}
/**
* Check if the channel utilization ratio is valid
*/
private static boolean isValidUtilizationRatio(int utilizationRatio) {
return (utilizationRatio <= MAX_CHANNEL_UTILIZATION
&& utilizationRatio >= MIN_CHANNEL_UTILIZATION);
}
// Calculate the available airtime fraction value which is multiplied by
// MAX_CHANNEL_UTILIZATION for integer representation. It is calculated as
// (1 - channelUtilization / MAX_CHANNEL_UTILIZATION) * MAX_CHANNEL_UTILIZATION
private int calculateAirTimeFraction(int channelUtilization, int channelWidthFactor) {
int airTimeFraction20MHz = MAX_CHANNEL_UTILIZATION - channelUtilization;
int airTimeFraction = airTimeFraction20MHz;
// For the cases of 40MHz or above, need to take
// (1 - channelUtilization / MAX_CHANNEL_UTILIZATION) ^ (2 ^ channelWidthFactor)
// because channelUtilization is defined for primary 20MHz channel
for (int i = 1; i <= channelWidthFactor; ++i) {
airTimeFraction *= airTimeFraction;
airTimeFraction /= MAX_CHANNEL_UTILIZATION;
}
if (mVerboseLoggingEnabled) {
Log.d(TAG, " airTime20: " + airTimeFraction20MHz + " airTime: " + airTimeFraction);
}
return airTimeFraction;
}
}