Merge "add TCP data stall metrics" into rvc-dev am: 92fbcb262a am: 797bcc347e
Change-Id: Ib5bedf6daea17d09e10278dc4bb452ff3610e7f1
diff --git a/src/android/net/util/DataStallUtils.java b/src/android/net/util/DataStallUtils.java
index 5787879..3391a71 100644
--- a/src/android/net/util/DataStallUtils.java
+++ b/src/android/net/util/DataStallUtils.java
@@ -16,15 +16,30 @@
package android.net.util;
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Collection of utilities for data stall.
*/
public class DataStallUtils {
+ public static final int DATA_STALL_EVALUATION_TYPE_NONE = 0;
/** Detect data stall using dns timeout counts. */
public static final int DATA_STALL_EVALUATION_TYPE_DNS = 1 << 0;
/** Detect data stall using tcp connection fail rate. */
public static final int DATA_STALL_EVALUATION_TYPE_TCP = 1 << 1;
+ @IntDef(prefix = { "DATA_STALL_EVALUATION_TYPE_" }, value = {
+ DATA_STALL_EVALUATION_TYPE_NONE,
+ DATA_STALL_EVALUATION_TYPE_DNS,
+ DATA_STALL_EVALUATION_TYPE_TCP,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EvaluationType {
+ }
+
// Default configuration values for data stall detection.
public static final int DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD = 5;
public static final int DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS = 60 * 1000;
diff --git a/src/com/android/networkstack/metrics/DataStallDetectionStats.java b/src/com/android/networkstack/metrics/DataStallDetectionStats.java
index 7a1f9ac..d294e04 100644
--- a/src/com/android/networkstack/metrics/DataStallDetectionStats.java
+++ b/src/com/android/networkstack/metrics/DataStallDetectionStats.java
@@ -19,8 +19,10 @@
import android.net.util.NetworkStackUtils;
import android.net.wifi.WifiInfo;
+import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.util.HexDump;
import com.android.server.connectivity.nano.CellularData;
@@ -41,18 +43,35 @@
* @hide
*/
public final class DataStallDetectionStats {
- private static final int UNKNOWN_SIGNAL_STRENGTH = -1;
+ public static final int UNKNOWN_SIGNAL_STRENGTH = -1;
+ // Default value of TCP signals.
+ @VisibleForTesting
+ public static final int UNSPECIFIED_TCP_FAIL_RATE = -1;
+ @VisibleForTesting
+ public static final int UNSPECIFIED_TCP_PACKETS_COUNT = -1;
+ @VisibleForTesting
@NonNull
- final byte[] mCellularInfo;
+ public final byte[] mCellularInfo;
+ @VisibleForTesting
@NonNull
- final byte[] mWifiInfo;
+ public final byte[] mWifiInfo;
@NonNull
- final byte[] mDns;
- final int mEvaluationType;
- final int mNetworkType;
+ public final byte[] mDns;
+ @VisibleForTesting
+ public final int mEvaluationType;
+ @VisibleForTesting
+ public final int mNetworkType;
+ // The TCP packets fail rate percentage from the latest tcp polling. -1 means the TCP signal is
+ // not known or not supported on the SDK version of this device.
+ @VisibleForTesting @IntRange(from = -1, to = 100)
+ public final int mTcpFailRate;
+ // Number of packets sent since the last received packet.
+ @VisibleForTesting
+ public final int mTcpSentSinceLastRecv;
public DataStallDetectionStats(@Nullable byte[] cell, @Nullable byte[] wifi,
- @NonNull int[] returnCode, @NonNull long[] dnsTime, int evalType, int netType) {
+ @NonNull int[] returnCode, @NonNull long[] dnsTime, int evalType, int netType,
+ int failRate, int sentSinceLastRecv) {
mCellularInfo = emptyCellDataIfNull(cell);
mWifiInfo = emptyWifiInfoIfNull(wifi);
@@ -62,9 +81,20 @@
mDns = MessageNano.toByteArray(dns);
mEvaluationType = evalType;
mNetworkType = netType;
+ mTcpFailRate = failRate;
+ mTcpSentSinceLastRecv = sentSinceLastRecv;
}
- private byte[] emptyCellDataIfNull(@Nullable byte[] cell) {
+ /**
+ * Because metrics data must contain data for each field even if it's not supported or not
+ * available, generate a byte array representing an empty {@link CellularData} if the
+ * {@link CellularData} is unavailable.
+ *
+ * @param cell a byte array representing current {@link CellularData} of {@code this}
+ * @return a byte array of a {@link CellularData}.
+ */
+ @VisibleForTesting
+ public static byte[] emptyCellDataIfNull(@Nullable byte[] cell) {
if (cell != null) return cell;
CellularData data = new CellularData();
@@ -75,7 +105,16 @@
return MessageNano.toByteArray(data);
}
- private byte[] emptyWifiInfoIfNull(@Nullable byte[] wifi) {
+ /**
+ * Because metrics data must contain data for each field even if it's not supported or not
+ * available, generate a byte array representing an empty {@link WifiData} if the
+ * {@link WiFiData} is unavailable.
+ *
+ * @param wifi a byte array representing current {@link WiFiData} of {@code this}.
+ * @return a byte array of a {@link WiFiData}.
+ */
+ @VisibleForTesting
+ public static byte[] emptyWifiInfoIfNull(@Nullable byte[] wifi) {
if (wifi != null) return wifi;
WifiData data = new WifiData();
@@ -95,7 +134,11 @@
.append(", cell info: ")
.append(HexDump.toHexString(mCellularInfo))
.append(", dns: ")
- .append(HexDump.toHexString(mDns));
+ .append(HexDump.toHexString(mDns))
+ .append(", tcp fail rate: ")
+ .append(mTcpFailRate)
+ .append(", tcp received: ")
+ .append(mTcpSentSinceLastRecv);
return sb.toString();
}
@@ -107,12 +150,15 @@
&& (mEvaluationType == other.mEvaluationType)
&& Arrays.equals(mWifiInfo, other.mWifiInfo)
&& Arrays.equals(mCellularInfo, other.mCellularInfo)
- && Arrays.equals(mDns, other.mDns);
+ && Arrays.equals(mDns, other.mDns)
+ && (mTcpFailRate == other.mTcpFailRate)
+ && (mTcpSentSinceLastRecv == other.mTcpSentSinceLastRecv);
}
@Override
public int hashCode() {
- return Objects.hash(mNetworkType, mEvaluationType, mWifiInfo, mCellularInfo, mDns);
+ return Objects.hash(mNetworkType, mEvaluationType, mWifiInfo, mCellularInfo, mDns,
+ mTcpFailRate, mTcpSentSinceLastRecv);
}
/**
@@ -131,6 +177,8 @@
private final List<Long> mDnsTimeStamp = new ArrayList<Long>();
private int mEvaluationType;
private int mNetworkType;
+ private int mTcpFailRate = UNSPECIFIED_TCP_FAIL_RATE;
+ private int mTcpSentSinceLastRecv = UNSPECIFIED_TCP_PACKETS_COUNT;
/**
* Add a dns event into Builder.
@@ -168,6 +216,34 @@
}
/**
+ * Set the TCP packet fail rate into Builder. The data is included since android R.
+ *
+ * @param rate the TCP packet fail rate of the logged network. The default value is
+ * {@code UNSPECIFIED_TCP_FAIL_RATE}, which means the TCP signal is not known
+ * or not supported on the SDK version of this device.
+ * @return {@code this} {@link Builder} instance.
+ */
+ public Builder setTcpFailRate(@IntRange(from = -1, to = 100) int rate) {
+ mTcpFailRate = rate;
+ return this;
+ }
+
+ /**
+ * Set the number of TCP packets sent since the last received packet into Builder. The data
+ * starts to be included since android R.
+ *
+ * @param count the number of packets sent since the last received packet of the logged
+ * network. Keep it unset as default value or set to
+ * {@code UNSPECIFIED_TCP_PACKETS_COUNT} if the tcp signal is unsupported with
+ * current device android sdk version or the packets count is unknown.
+ * @return {@code this} {@link Builder} instance.
+ */
+ public Builder setTcpSentSinceLastRecv(int count) {
+ mTcpSentSinceLastRecv = count;
+ return this;
+ }
+
+ /**
* Set the wifi data into Builder.
*
* @param info a {@link WifiInfo} of the connected wifi network.
@@ -223,7 +299,7 @@
return new DataStallDetectionStats(mCellularInfo, mWifiInfo,
NetworkStackUtils.convertToIntArray(mDnsReturnCode),
NetworkStackUtils.convertToLongArray(mDnsTimeStamp),
- mEvaluationType, mNetworkType);
+ mEvaluationType, mNetworkType, mTcpFailRate, mTcpSentSinceLastRecv);
}
}
}
diff --git a/src/com/android/networkstack/metrics/DataStallStatsUtils.java b/src/com/android/networkstack/metrics/DataStallStatsUtils.java
index e7a6c3d..c0719c0 100644
--- a/src/com/android/networkstack/metrics/DataStallStatsUtils.java
+++ b/src/com/android/networkstack/metrics/DataStallStatsUtils.java
@@ -74,6 +74,8 @@
stats.mNetworkType,
stats.mWifiInfo,
stats.mCellularInfo,
- stats.mDns);
+ stats.mDns,
+ stats.mTcpFailRate,
+ stats.mTcpSentSinceLastRecv);
}
}
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 7fb6761..093118a 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -48,6 +48,7 @@
import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_TCP_POLLING_INTERVAL;
import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD;
import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS;
+import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_NONE;
import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_TCP;
import static android.net.util.DataStallUtils.DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;
import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_EVALUATION_TYPES;
@@ -112,6 +113,7 @@
import android.net.metrics.ValidationProbeEvent;
import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig;
+import android.net.util.DataStallUtils.EvaluationType;
import android.net.util.NetworkStackUtils;
import android.net.util.SharedLog;
import android.net.util.Stopwatch;
@@ -489,8 +491,8 @@
@Nullable
private final DnsStallDetector mDnsStallDetector;
private long mLastProbeTime;
- // Set to true if data stall is suspected and reset to false after metrics are sent to statsd.
- private boolean mCollectDataStallMetrics;
+ // The signal causing a data stall to be suspected. Reset to 0 after metrics are sent to statsd.
+ private @EvaluationType int mDataStallTypeToCollect;
private boolean mAcceptPartialConnectivity = false;
private final EvaluationState mEvaluationState = new EvaluationState();
@@ -977,8 +979,6 @@
boolean evaluateDataStall() {
if (isDataStall()) {
- // TODO: Add tcp info into metrics.
- mCollectDataStallMetrics = true;
validationLog("Suspecting data stall, reevaluate");
return true;
}
@@ -999,7 +999,8 @@
}
}
- private void writeDataStallStats(@NonNull final CaptivePortalProbeResult result) {
+ private void maybeWriteDataStallStats(@NonNull final CaptivePortalProbeResult result) {
+ if (mDataStallTypeToCollect == DATA_STALL_EVALUATION_TYPE_NONE) return;
/*
* Collect data stall detection level information for each transport type. Collect type
* specific information for cellular and wifi only currently. Generate
@@ -1007,19 +1008,22 @@
* TRANSPORT_WIFI and TRANSPORT_VPN, two DataStallDetectionStats will be generated.
*/
final int[] transports = mNetworkCapabilities.getTransportTypes();
-
for (int i = 0; i < transports.length; i++) {
- final DataStallDetectionStats stats = buildDataStallDetectionStats(transports[i]);
+ final DataStallDetectionStats stats =
+ buildDataStallDetectionStats(transports[i], mDataStallTypeToCollect);
mDependencies.writeDataStallDetectionStats(stats, result);
}
- mCollectDataStallMetrics = false;
+ mDataStallTypeToCollect = DATA_STALL_EVALUATION_TYPE_NONE;
}
@VisibleForTesting
- protected DataStallDetectionStats buildDataStallDetectionStats(int transport) {
+ protected DataStallDetectionStats buildDataStallDetectionStats(int transport,
+ @EvaluationType int evaluationType) {
final DataStallDetectionStats.Builder stats = new DataStallDetectionStats.Builder();
- if (VDBG_STALL) log("collectDataStallMetrics: type=" + transport);
- stats.setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
+ if (VDBG_STALL) {
+ log("collectDataStallMetrics: type=" + transport + ", evaluation=" + evaluationType);
+ }
+ stats.setEvaluationType(evaluationType);
stats.setNetworkType(transport);
switch (transport) {
case NetworkCapabilities.TRANSPORT_WIFI:
@@ -1044,11 +1048,21 @@
// No transport type specific information for the other types.
break;
}
+
addDnsEvents(stats);
+ addTcpStats(stats);
return stats.build();
}
+ private void addTcpStats(@NonNull final DataStallDetectionStats.Builder stats) {
+ final TcpSocketTracker tst = getTcpSocketTracker();
+ if (tst == null) return;
+
+ stats.setTcpSentSinceLastRecv(tst.getSentSinceLastRecv());
+ stats.setTcpFailRate(tst.getLatestPacketFailPercentage());
+ }
+
@VisibleForTesting
protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
final DnsStallDetector dsd = getDnsStallDetector();
@@ -1438,9 +1452,7 @@
(CaptivePortalProbeResult) message.obj;
mLastProbeTime = SystemClock.elapsedRealtime();
- if (mCollectDataStallMetrics) {
- writeDataStallStats(probeResult);
- }
+ maybeWriteDataStallStats(probeResult);
if (probeResult.isSuccessful()) {
// Transit EvaluatingPrivateDnsState to get to Validated
@@ -1919,7 +1931,8 @@
DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS);
}
- private int getDataStallEvaluationType() {
+ @VisibleForTesting
+ int getDataStallEvaluationType() {
return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
CONFIG_DATA_STALL_EVALUATION_TYPE,
DEFAULT_DATA_STALL_EVALUATION_TYPES);
@@ -3140,9 +3153,8 @@
return mDnsStallDetector;
}
- @VisibleForTesting
@Nullable
- protected TcpSocketTracker getTcpSocketTracker() {
+ private TcpSocketTracker getTcpSocketTracker() {
return mTcpTracker;
}
@@ -3179,6 +3191,7 @@
result = false;
} else if (tst.isDataStallSuspected()) {
result = true;
+ mDataStallTypeToCollect = DATA_STALL_EVALUATION_TYPE_TCP;
final DataStallReportParcelable p = new DataStallReportParcelable();
p.detectionMethod = DETECTION_METHOD_TCP_METRICS;
@@ -3203,6 +3216,7 @@
if (dsd.isDataStallSuspected(mConsecutiveDnsTimeoutThreshold,
mDataStallValidDnsTimeThreshold)) {
result = true;
+ mDataStallTypeToCollect = DATA_STALL_EVALUATION_TYPE_DNS;
logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND);
final DataStallReportParcelable p = new DataStallReportParcelable();
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index dda7f08..f0a7bfb 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -39,6 +39,7 @@
import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_VALID_DNS_TIME_THRESHOLD;
import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS;
import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_TCP;
+import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_EVALUATION_TYPES;
import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS;
import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS;
import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS;
@@ -144,10 +145,15 @@
import com.android.networkstack.metrics.DataStallStatsUtils;
import com.android.networkstack.netlink.TcpSocketTracker;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
+import com.android.server.connectivity.nano.CellularData;
+import com.android.server.connectivity.nano.DnsEvent;
+import com.android.server.connectivity.nano.WifiData;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.HandlerUtilsKt;
+import com.google.protobuf.nano.MessageNano;
+
import junit.framework.AssertionFailedError;
import org.junit.After;
@@ -227,7 +233,6 @@
private @Spy Network mCleartextDnsNetwork = new Network(TEST_NETID);
private @Mock Network mNetwork;
private @Mock DataStallStatsUtils mDataStallStatsUtils;
- private @Mock WifiInfo mWifiInfo;
private @Mock TcpSocketTracker.Dependencies mTstDependencies;
private @Mock INetd mNetd;
private @Mock TcpSocketTracker mTst;
@@ -235,6 +240,7 @@
private HashSet<BroadcastReceiver> mRegisteredReceivers;
private @Mock Context mMccContext;
private @Mock Resources mMccResource;
+ private @Mock WifiInfo mWifiInfo;
private static final int TEST_NETID = 4242;
private static final String TEST_HTTP_URL = "http://www.google.com/gen_204";
@@ -252,10 +258,12 @@
private static final String TEST_VENUE_INFO_URL = "https://venue.example.com/info";
private static final String TEST_SPEED_TEST_URL = "https://speedtest.example.com";
private static final String TEST_MCCMNC = "123456";
-
private static final String[] TEST_HTTP_URLS = {TEST_HTTP_OTHER_URL1, TEST_HTTP_OTHER_URL2};
private static final String[] TEST_HTTPS_URLS = {TEST_HTTPS_OTHER_URL1, TEST_HTTPS_OTHER_URL2};
-
+ private static final int TEST_TCP_FAIL_RATE = 99;
+ private static final int TEST_TCP_PACKET_COUNT = 50;
+ private static final long TEST_ELAPSED_TIME_MS = 123456789L;
+ private static final int TEST_SIGNAL_STRENGTH = -100;
private static final int VALIDATION_RESULT_INVALID = 0;
private static final int VALIDATION_RESULT_PORTAL = 0;
private static final String TEST_REDIRECT_URL = "android.com";
@@ -288,6 +296,12 @@
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ private static final NetworkCapabilities WIFI_NOT_METERED_CAPABILITIES =
+ new NetworkCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+
private static final NetworkCapabilities CELL_NO_INTERNET_CAPABILITIES =
new NetworkCapabilities().addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
@@ -445,6 +459,10 @@
when(mServiceManager.getNotifier()).thenReturn(mNotifier);
+ when(mTelephony.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE);
+ when(mTelephony.getNetworkOperator()).thenReturn(TEST_MCCMNC);
+ when(mTelephony.getSimOperator()).thenReturn(TEST_MCCMNC);
+
when(mResources.getString(anyInt())).thenReturn("");
when(mResources.getStringArray(anyInt())).thenReturn(new String[0]);
doReturn(mConfiguration).when(mResources).getConfiguration();
@@ -557,13 +575,21 @@
}
}
+ private TcpSocketTracker getTcpSocketTrackerOrNull(NetworkMonitor.Dependencies dp) {
+ return ((dp.getDeviceConfigPropertyInt(
+ NAMESPACE_CONNECTIVITY,
+ CONFIG_DATA_STALL_EVALUATION_TYPE,
+ DEFAULT_DATA_STALL_EVALUATION_TYPES)
+ & DATA_STALL_EVALUATION_TYPE_TCP) != 0) ? mTst : null;
+ }
+
private class WrappedNetworkMonitor extends NetworkMonitor {
private long mProbeTime = 0;
private final ConditionVariable mQuitCv = new ConditionVariable(false);
WrappedNetworkMonitor() {
super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger, mServiceManager,
- mDependencies, mTst);
+ mDependencies, getTcpSocketTrackerOrNull(mDependencies));
}
@Override
@@ -576,13 +602,10 @@
}
@Override
- protected TcpSocketTracker getTcpSocketTracker() {
- return mTst;
- }
-
- @Override
protected void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
- generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
+ if ((getDataStallEvaluationType() & DATA_STALL_EVALUATION_TYPE_DNS) != 0) {
+ generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
+ }
}
@Override
@@ -622,6 +645,11 @@
return nm;
}
+ private WrappedNetworkMonitor makeWifiNotMeteredNetworkMonitor() {
+ final WrappedNetworkMonitor nm = makeMonitor(WIFI_NOT_METERED_CAPABILITIES);
+ return nm;
+ }
+
private void setNetworkCapabilities(NetworkMonitor nm, NetworkCapabilities nc) {
nm.notifyNetworkCapabilitiesChanged(nc);
HandlerUtilsKt.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
@@ -1728,85 +1756,284 @@
}
@Test
- public void testDataStall_StallDnsSuspectedAndSendMetrics() throws Exception {
- // Connect a VALID network to simulate the data stall detection because data stall
- // evaluation will only start from validated state.
- setStatus(mHttpsConnection, 204);
- WrappedNetworkMonitor wrappedMonitor = makeCellNotMeteredNetworkMonitor();
- wrappedMonitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, CELL_METERED_CAPABILITIES);
- verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID,
- NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
- // Setup dns data stall signal.
- wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
- makeDnsTimeoutEvent(wrappedMonitor, 5);
- assertTrue(wrappedMonitor.isDataStall());
+ public void testDataStall_StallDnsSuspectedAndSendMetricsOnCell() throws Exception {
+ testDataStall_StallDnsSuspectedAndSendMetrics(NetworkCapabilities.TRANSPORT_CELLULAR,
+ CELL_METERED_CAPABILITIES);
+ }
+
+ @Test
+ public void testDataStall_StallDnsSuspectedAndSendMetricsOnWifi() throws Exception {
+ testDataStall_StallDnsSuspectedAndSendMetrics(NetworkCapabilities.TRANSPORT_WIFI,
+ WIFI_NOT_METERED_CAPABILITIES);
+ }
+
+ private void testDataStall_StallDnsSuspectedAndSendMetrics(int transport,
+ NetworkCapabilities nc) throws Exception {
+ // NM suspects data stall from DNS signal and sends data stall metrics.
+ final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(nc);
+ makeDnsTimeoutEvent(nm, 5);
// Trigger a dns signal to start evaluate data stall and upload metrics.
- wrappedMonitor.notifyDnsResponse(RETURN_CODE_DNS_TIMEOUT);
- // Setup information to prevent null data and cause NPE during testing.
- when(mTelephony.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE);
- when(mTelephony.getNetworkOperator()).thenReturn(TEST_MCCMNC);
- when(mTelephony.getSimOperator()).thenReturn(TEST_MCCMNC);
- // Verify data sent as expectation.
- final DataStallDetectionStats stats = wrappedMonitor.buildDataStallDetectionStats(
- NetworkCapabilities.TRANSPORT_CELLULAR);
- final ArgumentCaptor<CaptivePortalProbeResult> probeResultCaptor =
- ArgumentCaptor.forClass(CaptivePortalProbeResult.class);
- verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).times(1))
- .writeDataStallDetectionStats(eq(stats), probeResultCaptor.capture());
- assertTrue(probeResultCaptor.getValue().isSuccessful());
+ nm.notifyDnsResponse(RETURN_CODE_DNS_TIMEOUT);
+ // Verify data sent as expected.
+ verifySendDataStallDetectionStats(nm, DATA_STALL_EVALUATION_TYPE_DNS, transport);
}
@Test
public void testDataStall_NoStallSuspectedAndSendMetrics() throws Exception {
- // Connect a VALID network to simulate the data stall detection because data stall
- // evaluation will only start from validated state.
- setStatus(mHttpsConnection, 204);
- WrappedNetworkMonitor wrappedMonitor = makeCellNotMeteredNetworkMonitor();
- wrappedMonitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, CELL_METERED_CAPABILITIES);
- verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID,
- NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
+ final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(
+ CELL_METERED_CAPABILITIES);
// Setup no data stall dns signal.
- wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
- makeDnsTimeoutEvent(wrappedMonitor, 3);
- assertFalse(wrappedMonitor.isDataStall());
+ makeDnsTimeoutEvent(nm, 3);
+ assertFalse(nm.isDataStall());
// Trigger a dns signal to start evaluate data stall.
- wrappedMonitor.notifyDnsResponse(RETURN_CODE_DNS_SUCCESS);
+ nm.notifyDnsResponse(RETURN_CODE_DNS_SUCCESS);
verify(mDependencies, never()).writeDataStallDetectionStats(any(), any());
}
@Test
- public void testCollectDataStallMetrics() {
- WrappedNetworkMonitor wrappedMonitor = makeCellNotMeteredNetworkMonitor();
+ public void testDataStall_StallTcpSuspectedAndSendMetricsOnCell() throws Exception {
+ testDataStall_StallTcpSuspectedAndSendMetrics(NetworkCapabilities.TRANSPORT_CELLULAR,
+ CELL_METERED_CAPABILITIES);
+ }
- when(mTelephony.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE);
- when(mTelephony.getNetworkOperator()).thenReturn(TEST_MCCMNC);
- when(mTelephony.getSimOperator()).thenReturn(TEST_MCCMNC);
+ @Test
+ public void testDataStall_StallTcpSuspectedAndSendMetricsOnWifi() throws Exception {
+ testDataStall_StallTcpSuspectedAndSendMetrics(NetworkCapabilities.TRANSPORT_WIFI,
+ WIFI_NOT_METERED_CAPABILITIES);
+ }
- DataStallDetectionStats.Builder stats =
- new DataStallDetectionStats.Builder()
- .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
- .setNetworkType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .setCellData(TelephonyManager.NETWORK_TYPE_LTE /* radioType */,
+ private void testDataStall_StallTcpSuspectedAndSendMetrics(int transport,
+ NetworkCapabilities nc) throws Exception {
+ assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
+ // NM suspects data stall from TCP signal and sends data stall metrics.
+ setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_TCP);
+ final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(nc);
+ setupTcpDataStall();
+ // Trigger a tcp event immediately.
+ setTcpPollingInterval(0);
+ nm.sendTcpPollingEvent();
+ verifySendDataStallDetectionStats(nm, DATA_STALL_EVALUATION_TYPE_TCP, transport);
+ }
+
+ private WrappedNetworkMonitor prepareNetworkMonitorForVerifyDataStall(NetworkCapabilities nc)
+ throws Exception {
+ // Connect a VALID network to simulate the data stall detection because data stall
+ // evaluation will only start from validated state.
+ setStatus(mHttpsConnection, 204);
+ final WrappedNetworkMonitor nm;
+ final int[] transports = nc.getTransportTypes();
+ // Though multiple transport types are allowed, use the first transport type for
+ // simplification.
+ switch (transports[0]) {
+ case NetworkCapabilities.TRANSPORT_CELLULAR:
+ nm = makeCellMeteredNetworkMonitor();
+ break;
+ case NetworkCapabilities.TRANSPORT_WIFI:
+ nm = makeWifiNotMeteredNetworkMonitor();
+ setupTestWifiInfo();
+ break;
+ default:
+ nm = null;
+ fail("Undefined transport type");
+ }
+ nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc);
+ verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID,
+ NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
+ nm.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
+ return nm;
+ }
+
+ private void setupTcpDataStall() {
+ when(mTstDependencies.isTcpInfoParsingSupported()).thenReturn(true);
+ when(mTst.getLatestReceivedCount()).thenReturn(0);
+ when(mTst.getLatestPacketFailPercentage()).thenReturn(TEST_TCP_FAIL_RATE);
+ when(mTst.getSentSinceLastRecv()).thenReturn(TEST_TCP_PACKET_COUNT);
+ when(mTst.isDataStallSuspected()).thenReturn(true);
+ when(mTst.pollSocketsInfo()).thenReturn(true);
+ }
+
+ private void verifySendDataStallDetectionStats(WrappedNetworkMonitor nm, int evalType,
+ int transport) {
+ // Verify data sent as expectated.
+ final ArgumentCaptor<CaptivePortalProbeResult> probeResultCaptor =
+ ArgumentCaptor.forClass(CaptivePortalProbeResult.class);
+ final ArgumentCaptor<DataStallDetectionStats> statsCaptor =
+ ArgumentCaptor.forClass(DataStallDetectionStats.class);
+ verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .writeDataStallDetectionStats(statsCaptor.capture(), probeResultCaptor.capture());
+ assertTrue(nm.isDataStall());
+ assertTrue(probeResultCaptor.getValue().isSuccessful());
+ verifyTestDataStallDetectionStats(evalType, transport, statsCaptor.getValue());
+ }
+
+ private void verifyTestDataStallDetectionStats(int evalType, int transport,
+ DataStallDetectionStats stats) {
+ assertEquals(transport, stats.mNetworkType);
+ switch (transport) {
+ case NetworkCapabilities.TRANSPORT_WIFI:
+ assertArrayEquals(makeTestWifiDataNano(), stats.mWifiInfo);
+ // Expedient way to check stats.mCellularInfo contains the neutral byte array that
+ // is sent to represent a lack of data, as stats.mCellularInfo is not supposed to
+ // contain null.
+ assertArrayEquals(DataStallDetectionStats.emptyCellDataIfNull(null),
+ stats.mCellularInfo);
+ break;
+ case NetworkCapabilities.TRANSPORT_CELLULAR:
+ // Expedient way to check stats.mWifiInfo contains the neutral byte array that is
+ // sent to represent a lack of data, as stats.mWifiInfo is not supposed to contain
+ // null.
+ assertArrayEquals(DataStallDetectionStats.emptyWifiInfoIfNull(null),
+ stats.mWifiInfo);
+ assertArrayEquals(makeTestCellDataNano(), stats.mCellularInfo);
+ break;
+ default:
+ // Add other cases.
+ fail("Unexpected transport type");
+ }
+
+ assertEquals(evalType, stats.mEvaluationType);
+ if ((evalType & DATA_STALL_EVALUATION_TYPE_TCP) != 0) {
+ assertEquals(TEST_TCP_FAIL_RATE, stats.mTcpFailRate);
+ assertEquals(TEST_TCP_PACKET_COUNT, stats.mTcpSentSinceLastRecv);
+ } else {
+ assertEquals(DataStallDetectionStats.UNSPECIFIED_TCP_FAIL_RATE, stats.mTcpFailRate);
+ assertEquals(DataStallDetectionStats.UNSPECIFIED_TCP_PACKETS_COUNT,
+ stats.mTcpSentSinceLastRecv);
+ }
+
+ if ((evalType & DATA_STALL_EVALUATION_TYPE_DNS) != 0) {
+ assertArrayEquals(stats.mDns, makeTestDnsTimeoutNano(DEFAULT_DNS_TIMEOUT_THRESHOLD));
+ } else {
+ assertArrayEquals(stats.mDns, makeTestDnsTimeoutNano(0 /* times */));
+ }
+ }
+
+ private DataStallDetectionStats makeTestDataStallDetectionStats(int evaluationType,
+ int transportType) {
+ final DataStallDetectionStats.Builder stats = new DataStallDetectionStats.Builder()
+ .setEvaluationType(evaluationType)
+ .setNetworkType(transportType);
+ switch (transportType) {
+ case NetworkCapabilities.TRANSPORT_CELLULAR:
+ stats.setCellData(TelephonyManager.NETWORK_TYPE_LTE /* radioType */,
true /* roaming */,
TEST_MCCMNC /* networkMccmnc */,
TEST_MCCMNC /* simMccmnc */,
CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN /* signalStrength */);
- generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
+ break;
+ case NetworkCapabilities.TRANSPORT_WIFI:
+ setupTestWifiInfo();
+ stats.setWiFiData(mWifiInfo);
+ break;
+ default:
+ break;
+ }
- assertEquals(wrappedMonitor.buildDataStallDetectionStats(
- NetworkCapabilities.TRANSPORT_CELLULAR), stats.build());
+ if ((evaluationType & DATA_STALL_EVALUATION_TYPE_TCP) != 0) {
+ generateTestTcpStats(stats);
+ }
+ if ((evaluationType & DATA_STALL_EVALUATION_TYPE_DNS) != 0) {
+ generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
+ }
+
+ return stats.build();
+ }
+
+ private byte[] makeTestDnsTimeoutNano(int timeoutCount) {
+ // Make a expected nano dns message.
+ final DnsEvent event = new DnsEvent();
+ event.dnsReturnCode = new int[timeoutCount];
+ event.dnsTime = new long[timeoutCount];
+ Arrays.fill(event.dnsReturnCode, RETURN_CODE_DNS_TIMEOUT);
+ Arrays.fill(event.dnsTime, TEST_ELAPSED_TIME_MS);
+ return MessageNano.toByteArray(event);
+ }
+
+ private byte[] makeTestCellDataNano() {
+ final CellularData data = new CellularData();
+ data.ratType = DataStallEventProto.RADIO_TECHNOLOGY_LTE;
+ data.networkMccmnc = TEST_MCCMNC;
+ data.simMccmnc = TEST_MCCMNC;
+ data.isRoaming = true;
+ data.signalStrength = 0;
+ return MessageNano.toByteArray(data);
+ }
+
+ private byte[] makeTestWifiDataNano() {
+ final WifiData data = new WifiData();
+ data.wifiBand = DataStallEventProto.AP_BAND_2GHZ;
+ data.signalStrength = TEST_SIGNAL_STRENGTH;
+ return MessageNano.toByteArray(data);
+ }
+
+ private void setupTestWifiInfo() {
when(mWifi.getConnectionInfo()).thenReturn(mWifiInfo);
+ when(mWifiInfo.getRssi()).thenReturn(TEST_SIGNAL_STRENGTH);
+ // Set to 2.4G band. Map to DataStallEventProto.AP_BAND_2GHZ proto definition.
+ when(mWifiInfo.getFrequency()).thenReturn(2450);
+ }
- stats = new DataStallDetectionStats.Builder()
- .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
- .setNetworkType(NetworkCapabilities.TRANSPORT_WIFI)
- .setWiFiData(mWifiInfo);
- generateTimeoutDnsEvent(stats, DEFAULT_DNS_TIMEOUT_THRESHOLD);
+ private void testDataStallMetricsWithCellular(int evalType) {
+ testDataStallMetrics(evalType, NetworkCapabilities.TRANSPORT_CELLULAR);
+ }
- assertEquals(
- wrappedMonitor.buildDataStallDetectionStats(NetworkCapabilities.TRANSPORT_WIFI),
- stats.build());
+ private void testDataStallMetricsWithWiFi(int evalType) {
+ testDataStallMetrics(evalType, NetworkCapabilities.TRANSPORT_WIFI);
+ }
+
+ private void testDataStallMetrics(int evalType, int transportType) {
+ setDataStallEvaluationType(evalType);
+ final NetworkCapabilities nc = new NetworkCapabilities()
+ .addTransportType(transportType)
+ .addCapability(NET_CAPABILITY_INTERNET);
+ final WrappedNetworkMonitor wrappedMonitor = makeMonitor(nc);
+ setupTestWifiInfo();
+ final DataStallDetectionStats stats =
+ makeTestDataStallDetectionStats(evalType, transportType);
+ assertEquals(wrappedMonitor.buildDataStallDetectionStats(transportType, evalType), stats);
+
+ if ((evalType & DATA_STALL_EVALUATION_TYPE_TCP) != 0) {
+ verify(mTst, timeout(HANDLER_TIMEOUT_MS).atLeastOnce()).getLatestPacketFailPercentage();
+ } else {
+ verify(mTst, never()).getLatestPacketFailPercentage();
+ }
+ }
+
+ @Test
+ public void testCollectDataStallMetrics_DnsWithCellular() {
+ testDataStallMetricsWithCellular(DATA_STALL_EVALUATION_TYPE_DNS);
+ }
+
+ @Test
+ public void testCollectDataStallMetrics_DnsWithWiFi() {
+ testDataStallMetricsWithWiFi(DATA_STALL_EVALUATION_TYPE_DNS);
+ }
+
+ @Test
+ public void testCollectDataStallMetrics_TcpWithCellular() {
+ assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
+ testDataStallMetricsWithCellular(DATA_STALL_EVALUATION_TYPE_TCP);
+ }
+
+ @Test
+ public void testCollectDataStallMetrics_TcpWithWiFi() {
+ assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
+ testDataStallMetricsWithWiFi(DATA_STALL_EVALUATION_TYPE_TCP);
+ }
+
+ @Test
+ public void testCollectDataStallMetrics_TcpAndDnsWithWifi() {
+ assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
+ testDataStallMetricsWithWiFi(
+ DATA_STALL_EVALUATION_TYPE_TCP | DATA_STALL_EVALUATION_TYPE_DNS);
+ }
+
+ @Test
+ public void testCollectDataStallMetrics_TcpAndDnsWithCellular() {
+ assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
+ testDataStallMetricsWithCellular(
+ DATA_STALL_EVALUATION_TYPE_TCP | DATA_STALL_EVALUATION_TYPE_DNS);
}
@Test
@@ -2334,10 +2561,16 @@
private void generateTimeoutDnsEvent(DataStallDetectionStats.Builder stats, int num) {
for (int i = 0; i < num; i++) {
- stats.addDnsEvent(RETURN_CODE_DNS_TIMEOUT, 123456789 /* timeMs */);
+ stats.addDnsEvent(RETURN_CODE_DNS_TIMEOUT, TEST_ELAPSED_TIME_MS /* timeMs */);
}
}
+ private void generateTestTcpStats(DataStallDetectionStats.Builder stats) {
+ when(mTst.getLatestPacketFailPercentage()).thenReturn(TEST_TCP_FAIL_RATE);
+ when(mTst.getSentSinceLastRecv()).thenReturn(TEST_TCP_PACKET_COUNT);
+ stats.setTcpFailRate(TEST_TCP_FAIL_RATE).setTcpSentSinceLastRecv(TEST_TCP_PACKET_COUNT);
+ }
+
private NetworkTestResultParcelable matchNetworkTestResultParcelable(final int result,
final int probesSucceeded) {
return matchNetworkTestResultParcelable(result, probesSucceeded, null /* redirectUrl */);