diff options
author | Chiachang Wang <chiachangwang@google.com> | 2020-05-27 03:53:16 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-05-27 03:53:16 +0000 |
commit | 92fbcb262a7341a1148b2fba5672736ac692afe2 (patch) | |
tree | f7048a07447dbc407213e60816b584e38cb77bac | |
parent | 3f0922c166db1b85de9ab9daa6df9cbd33f1e044 (diff) | |
parent | 0b34ae627c27877412d800dfc0e1883f8ca0b5e3 (diff) |
Merge "add TCP data stall metrics" into rvc-dev
5 files changed, 438 insertions, 98 deletions
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 @@ package com.android.networkstack.metrics; 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 @@ import java.util.Objects; * @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 @@ public final class DataStallDetectionStats { 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 @@ public final class DataStallDetectionStats { 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 @@ public final class DataStallDetectionStats { .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 @@ public final class DataStallDetectionStats { && (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 @@ public final class DataStallDetectionStats { 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 @@ public final class DataStallDetectionStats { } /** + * 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 @@ public final class DataStallDetectionStats { 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 @@ public class DataStallStatsUtils { 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_MIN_EVALUATE_INT 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.NetworkEvent; 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 @@ public class NetworkMonitor extends StateMachine { @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 @@ public class NetworkMonitor extends StateMachine { boolean evaluateDataStall() { if (isDataStall()) { - // TODO: Add tcp info into metrics. - mCollectDataStallMetrics = true; validationLog("Suspecting data stall, reevaluate"); return true; } @@ -999,7 +999,8 @@ public class NetworkMonitor extends StateMachine { } } - 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 @@ public class NetworkMonitor extends StateMachine { * 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 @@ public class NetworkMonitor extends StateMachine { // 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 @@ public class NetworkMonitor extends StateMachine { (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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { return mDnsStallDetector; } - @VisibleForTesting @Nullable - protected TcpSocketTracker getTcpSocketTracker() { + private TcpSocketTracker getTcpSocketTracker() { return mTcpTracker; } @@ -3179,6 +3191,7 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { 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_TCP_POLLING_INTE 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.DataStallDetectionStats; 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 @@ public class NetworkMonitorTest { 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 @@ public class NetworkMonitorTest { 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 @@ public class NetworkMonitorTest { 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 @@ public class NetworkMonitorTest { .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 @@ public class NetworkMonitorTest { 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 @@ public class NetworkMonitorTest { } } + 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 @@ public class NetworkMonitorTest { } @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 @@ public class NetworkMonitorTest { 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 @@ public class NetworkMonitorTest { } @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 { + final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall( + CELL_METERED_CAPABILITIES); + // Setup no data stall dns signal. + makeDnsTimeoutEvent(nm, 3); + assertFalse(nm.isDataStall()); + // Trigger a dns signal to start evaluate data stall. + nm.notifyDnsResponse(RETURN_CODE_DNS_SUCCESS); + verify(mDependencies, never()).writeDataStallDetectionStats(any(), any()); + } + + @Test + public void testDataStall_StallTcpSuspectedAndSendMetricsOnCell() throws Exception { + testDataStall_StallTcpSuspectedAndSendMetrics(NetworkCapabilities.TRANSPORT_CELLULAR, + CELL_METERED_CAPABILITIES); + } + + @Test + public void testDataStall_StallTcpSuspectedAndSendMetricsOnWifi() throws Exception { + testDataStall_StallTcpSuspectedAndSendMetrics(NetworkCapabilities.TRANSPORT_WIFI, + WIFI_NOT_METERED_CAPABILITIES); + } + + 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); - WrappedNetworkMonitor wrappedMonitor = makeCellNotMeteredNetworkMonitor(); - wrappedMonitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, CELL_METERED_CAPABILITIES); + 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); - // Setup no data stall dns signal. - wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); - makeDnsTimeoutEvent(wrappedMonitor, 3); - assertFalse(wrappedMonitor.isDataStall()); - // Trigger a dns signal to start evaluate data stall. - wrappedMonitor.notifyDnsResponse(RETURN_CODE_DNS_SUCCESS); - verify(mDependencies, never()).writeDataStallDetectionStats(any(), any()); + nm.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); + return nm; } - @Test - public void testCollectDataStallMetrics() { - WrappedNetworkMonitor wrappedMonitor = makeCellNotMeteredNetworkMonitor(); + 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); + } - when(mTelephony.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE); - when(mTelephony.getNetworkOperator()).thenReturn(TEST_MCCMNC); - when(mTelephony.getSimOperator()).thenReturn(TEST_MCCMNC); + 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 */)); + } + } - DataStallDetectionStats.Builder stats = - new DataStallDetectionStats.Builder() - .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS) - .setNetworkType(NetworkCapabilities.TRANSPORT_CELLULAR) - .setCellData(TelephonyManager.NETWORK_TYPE_LTE /* radioType */, + 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; + } + + 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(); + } - assertEquals(wrappedMonitor.buildDataStallDetectionStats( - NetworkCapabilities.TRANSPORT_CELLULAR), 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 @@ public class NetworkMonitorTest { 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 */); |