diff options
author | Chiachang Wang <chiachangwang@google.com> | 2019-02-22 11:13:07 +0800 |
---|---|---|
committer | Chiachang Wang <chiachangwang@google.com> | 2019-02-22 03:14:45 +0000 |
commit | 8b5f84ac9c08f27bea6fcbfacbabcfda8773ea07 (patch) | |
tree | 63b7cab0e0c33e901418060f6e56c9d6a9b255ec | |
parent | 0aceb93c918dcc3a3da49338181fddb00774badc (diff) |
Injecting data stall event to statsd
Bug: 121185319
Bug: 120452078
Test: - Build and test against data stall code
- atest NetworkStackTests
Change-Id: I2ee16085e7861d38bcd203c98ffd682f86fdc867
-rw-r--r-- | Android.bp | 3 | ||||
-rw-r--r-- | src/android/net/metrics/DataStallDetectionStats.java | 209 | ||||
-rw-r--r-- | src/android/net/metrics/DataStallStatsUtils.java | 66 | ||||
-rw-r--r-- | src/android/net/util/NetworkStackUtils.java | 25 | ||||
-rw-r--r-- | src/com/android/server/connectivity/NetworkMonitor.java | 82 | ||||
-rw-r--r-- | tests/src/com/android/server/connectivity/NetworkMonitorTest.java | 30 |
6 files changed, 406 insertions, 9 deletions
@@ -28,6 +28,7 @@ java_library { static_libs: [ "netd_aidl_interface-java", "networkstack-aidl-interfaces-java", + "datastallprotosnano", ] } @@ -43,4 +44,4 @@ android_app { jarjar_rules: "jarjar-rules-shared.txt", manifest: "AndroidManifest.xml", required: ["NetworkStackPermissionStub"], -}
\ No newline at end of file +} diff --git a/src/android/net/metrics/DataStallDetectionStats.java b/src/android/net/metrics/DataStallDetectionStats.java new file mode 100644 index 0000000..6ea1e3d --- /dev/null +++ b/src/android/net/metrics/DataStallDetectionStats.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 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 android.net.metrics; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.util.NetworkStackUtils; +import android.net.wifi.WifiInfo; + +import com.android.internal.util.HexDump; +import com.android.server.connectivity.nano.CellularData; +import com.android.server.connectivity.nano.DataStallEventProto; +import com.android.server.connectivity.nano.DnsEvent; +import com.android.server.connectivity.nano.WifiData; + +import com.google.protobuf.nano.MessageNano; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class to record the stats of detection level information for data stall. + * + * @hide + */ +public final class DataStallDetectionStats { + private static final int UNKNOWN_SIGNAL_STRENGTH = -1; + @NonNull + final byte[] mCellularInfo; + @NonNull + final byte[] mWifiInfo; + @NonNull + final byte[] mDns; + final int mEvaluationType; + final int mNetworkType; + + public DataStallDetectionStats(@Nullable byte[] cell, @Nullable byte[] wifi, + @NonNull int[] returnCode, @NonNull long[] dnsTime, int evalType, int netType) { + mCellularInfo = emptyCellDataIfNull(cell); + mWifiInfo = emptyWifiInfoIfNull(wifi); + + DnsEvent dns = new DnsEvent(); + dns.dnsReturnCode = returnCode; + dns.dnsTime = dnsTime; + mDns = MessageNano.toByteArray(dns); + mEvaluationType = evalType; + mNetworkType = netType; + } + + private byte[] emptyCellDataIfNull(@Nullable byte[] cell) { + if (cell != null) return cell; + + CellularData data = new CellularData(); + data.ratType = DataStallEventProto.RADIO_TECHNOLOGY_UNKNOWN; + data.networkMccmnc = ""; + data.simMccmnc = ""; + data.signalStrength = UNKNOWN_SIGNAL_STRENGTH; + return MessageNano.toByteArray(data); + } + + private byte[] emptyWifiInfoIfNull(@Nullable byte[] wifi) { + if (wifi != null) return wifi; + + WifiData data = new WifiData(); + data.wifiBand = DataStallEventProto.AP_BAND_UNKNOWN; + data.signalStrength = UNKNOWN_SIGNAL_STRENGTH; + return MessageNano.toByteArray(data); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("type: ").append(mNetworkType) + .append(", evaluation type: ") + .append(mEvaluationType) + .append(", wifi info: ") + .append(HexDump.toHexString(mWifiInfo)) + .append(", cell info: ") + .append(HexDump.toHexString(mCellularInfo)) + .append(", dns: ") + .append(HexDump.toHexString(mDns)); + return sb.toString(); + } + + /** + * Utility to create an instance of {@Link DataStallDetectionStats} + * + * @hide + */ + public static class Builder { + @Nullable + private byte[] mCellularInfo; + @Nullable + private byte[] mWifiInfo; + @NonNull + private final List<Integer> mDnsReturnCode = new ArrayList<Integer>(); + @NonNull + private final List<Long> mDnsTimeStamp = new ArrayList<Long>(); + private int mEvaluationType; + private int mNetworkType; + + /** + * Add a dns event into Builder. + * + * @param code the return code of the dns event. + * @param timeMs the elapsedRealtime in ms that the the dns event was received from netd. + * @return {@code this} {@link Builder} instance. + */ + public Builder addDnsEvent(int code, long timeMs) { + mDnsReturnCode.add(code); + mDnsTimeStamp.add(timeMs); + return this; + } + + /** + * Set the dns evaluation type into Builder. + * + * @param type the return code of the dns event. + * @return {@code this} {@link Builder} instance. + */ + public Builder setEvaluationType(int type) { + mEvaluationType = type; + return this; + } + + /** + * Set the network type into Builder. + * + * @param type the network type of the logged network. + * @return {@code this} {@link Builder} instance. + */ + public Builder setNetworkType(int type) { + mNetworkType = type; + return this; + } + + /** + * Set the wifi data into Builder. + * + * @param info a {@link WifiInfo} of the connected wifi network. + * @return {@code this} {@link Builder} instance. + */ + public Builder setWiFiData(@Nullable final WifiInfo info) { + WifiData data = new WifiData(); + data.wifiBand = getWifiBand(info); + data.signalStrength = (info != null) ? info.getRssi() : UNKNOWN_SIGNAL_STRENGTH; + mWifiInfo = MessageNano.toByteArray(data); + return this; + } + + private static int getWifiBand(@Nullable final WifiInfo info) { + if (info != null) { + int freq = info.getFrequency(); + // Refer to ScanResult.is5GHz() and ScanResult.is24GHz(). + if (freq > 4900 && freq < 5900) { + return DataStallEventProto.AP_BAND_5GHZ; + } else if (freq > 2400 && freq < 2500) { + return DataStallEventProto.AP_BAND_2GHZ; + } + } + return DataStallEventProto.AP_BAND_UNKNOWN; + } + + /** + * Set the cellular data into Builder. + * + * @param radioType the radio technology of the logged cellular network. + * @param roaming a boolean indicates if logged cellular network is roaming or not. + * @param networkMccmnc the mccmnc of the camped network. + * @param simMccmnc the mccmnc of the sim. + * @return {@code this} {@link Builder} instance. + */ + public Builder setCellData(int radioType, boolean roaming, + @NonNull String networkMccmnc, @NonNull String simMccmnc, int ss) { + CellularData data = new CellularData(); + data.ratType = radioType; + data.isRoaming = roaming; + data.networkMccmnc = networkMccmnc; + data.simMccmnc = simMccmnc; + data.signalStrength = ss; + mCellularInfo = MessageNano.toByteArray(data); + return this; + } + + /** + * Create a new {@Link DataStallDetectionStats}. + */ + public DataStallDetectionStats build() { + return new DataStallDetectionStats(mCellularInfo, mWifiInfo, + NetworkStackUtils.convertToIntArray(mDnsReturnCode), + NetworkStackUtils.convertToLongArray(mDnsTimeStamp), + mEvaluationType, mNetworkType); + } + } +} diff --git a/src/android/net/metrics/DataStallStatsUtils.java b/src/android/net/metrics/DataStallStatsUtils.java new file mode 100644 index 0000000..17a36ad --- /dev/null +++ b/src/android/net/metrics/DataStallStatsUtils.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 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 android.net.metrics; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.captiveportal.CaptivePortalProbeResult; +import android.util.Log; + +import com.android.internal.util.HexDump; +import com.android.server.connectivity.nano.DataStallEventProto; + +/** + * Collection of utilities for data stall metrics. + * + * To see if the logs are properly sent to statsd, execute following command. + * + * $ adb shell cmd stats print-logs + * $ adb logcat | grep statsd OR $ adb logcat -b stats + * + * @hide + */ +public class DataStallStatsUtils { + private static final String TAG = DataStallStatsUtils.class.getSimpleName(); + private static final boolean DBG = false; + + private static int probeResultToEnum(@Nullable final CaptivePortalProbeResult result) { + if (result == null) return DataStallEventProto.INVALID; + + // TODO: Add partial connectivity support. + if (result.isSuccessful()) { + return DataStallEventProto.VALID; + } else if (result.isPortal()) { + return DataStallEventProto.PORTAL; + } else { + return DataStallEventProto.INVALID; + } + } + + /** + * Write the metric to {@link StatsLog}. + */ + public static void write(@NonNull final DataStallDetectionStats stats, + @NonNull final CaptivePortalProbeResult result) { + int validationResult = probeResultToEnum(result); + if (DBG) { + Log.d(TAG, "write: " + stats + " with result: " + validationResult + + ", dns: " + HexDump.toHexString(stats.mDns)); + } + // TODO(b/124613085): Send to Statsd once the public StatsLog API is ready. + } +} diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java index 98123a5..481dbda 100644 --- a/src/android/net/util/NetworkStackUtils.java +++ b/src/android/net/util/NetworkStackUtils.java @@ -16,8 +16,11 @@ package android.net.util; +import android.annotation.NonNull; + import java.io.FileDescriptor; import java.io.IOException; +import java.util.List; /** * Collection of utilities for the network stack. @@ -40,4 +43,26 @@ public class NetworkStackUtils { } catch (IOException ignored) { } } + + /** + * Returns an int array from the given Integer list. + */ + public static int[] convertToIntArray(@NonNull List<Integer> list) { + int[] array = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + array[i] = list.get(i); + } + return array; + } + + /** + * Returns a long array from the given long list. + */ + public static long[] convertToLongArray(@NonNull List<Long> list) { + long[] array = new long[list.size()]; + for (int i = 0; i < list.size(); i++) { + array[i] = list.get(i); + } + return array; + } } diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java index ec4a479..1c803dd 100644 --- a/src/com/android/server/connectivity/NetworkMonitor.java +++ b/src/com/android/server/connectivity/NetworkMonitor.java @@ -33,6 +33,7 @@ import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK; import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS; import static android.net.util.NetworkStackUtils.isEmpty; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -50,6 +51,8 @@ import android.net.TrafficStats; import android.net.Uri; import android.net.captiveportal.CaptivePortalProbeResult; import android.net.captiveportal.CaptivePortalProbeSpec; +import android.net.metrics.DataStallDetectionStats; +import android.net.metrics.DataStallStatsUtils; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.metrics.ValidationProbeEvent; @@ -66,8 +69,10 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.telephony.AccessNetworkConstants; +import android.telephony.CellSignalStrength; import android.telephony.NetworkRegistrationState; import android.telephony.ServiceState; +import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; @@ -126,6 +131,9 @@ public class NetworkMonitor extends StateMachine { private static final int DATA_STALL_EVALUATION_TYPE_DNS = 1; private static final int DEFAULT_DATA_STALL_EVALUATION_TYPES = (1 << DATA_STALL_EVALUATION_TYPE_DNS); + // Reevaluate it as intending to increase the number. Larger log size may cause statsd + // log buffer bust and have stats log lost. + private static final int DEFAULT_DNS_LOG_SIZE = 20; enum EvaluationResult { VALIDATED(true), @@ -244,6 +252,7 @@ public class NetworkMonitor extends StateMachine { private final ConnectivityManager mCm; private final IpConnectivityLog mMetricsLog; private final Dependencies mDependencies; + private final DataStallStatsUtils mDetectionStatsUtils; // Configuration values for captive portal detection probes. private final String mCaptivePortalUserAgent; @@ -302,17 +311,19 @@ public class NetworkMonitor extends StateMachine { private final int mDataStallEvaluationType; 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 = false; public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, SharedLog validationLog) { this(context, cb, network, new IpConnectivityLog(), validationLog, - Dependencies.DEFAULT); + Dependencies.DEFAULT, new DataStallStatsUtils()); } @VisibleForTesting protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, IpConnectivityLog logger, SharedLog validationLogs, - Dependencies deps) { + Dependencies deps, DataStallStatsUtils detectionStatsUtils) { // Add suffix indicating which NetworkMonitor we're talking about. super(TAG + "/" + network.toString()); @@ -325,6 +336,7 @@ public class NetworkMonitor extends StateMachine { mValidationLogs = validationLogs; mCallback = cb; mDependencies = deps; + mDetectionStatsUtils = detectionStatsUtils; mNonPrivateDnsBypassNetwork = network; mNetwork = deps.getPrivateDnsBypassNetwork(network); mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); @@ -656,6 +668,7 @@ public class NetworkMonitor extends StateMachine { case EVENT_DNS_NOTIFICATION: mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1); if (isDataStall()) { + mCollectDataStallMetrics = true; validationLog("Suspecting data stall, reevaluate"); transitionTo(mEvaluatingState); } @@ -667,6 +680,65 @@ public class NetworkMonitor extends StateMachine { } } + private void writeDataStallStats(@NonNull final CaptivePortalProbeResult result) { + /* + * Collect data stall detection level information for each transport type. Collect type + * specific information for cellular and wifi only currently. Generate + * DataStallDetectionStats for each transport type. E.g., if a network supports both + * TRANSPORT_WIFI and TRANSPORT_VPN, two DataStallDetectionStats will be generated. + */ + final int[] transports = mNetworkCapabilities.getTransportTypes(); + + for (int i = 0; i < transports.length; i++) { + DataStallStatsUtils.write(buildDataStallDetectionStats(transports[i]), result); + } + mCollectDataStallMetrics = false; + } + + @VisibleForTesting + protected DataStallDetectionStats buildDataStallDetectionStats(int transport) { + final DataStallDetectionStats.Builder stats = new DataStallDetectionStats.Builder(); + if (VDBG_STALL) log("collectDataStallMetrics: type=" + transport); + stats.setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS); + stats.setNetworkType(transport); + switch (transport) { + case NetworkCapabilities.TRANSPORT_WIFI: + // TODO: Update it if status query in dual wifi is supported. + final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); + stats.setWiFiData(wifiInfo); + break; + case NetworkCapabilities.TRANSPORT_CELLULAR: + final boolean isRoaming = !mNetworkCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); + final SignalStrength ss = mTelephonyManager.getSignalStrength(); + // TODO(b/120452078): Support multi-sim. + stats.setCellData( + mTelephonyManager.getDataNetworkType(), + isRoaming, + mTelephonyManager.getNetworkOperator(), + mTelephonyManager.getSimOperator(), + (ss != null) + ? ss.getLevel() : CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN); + break; + default: + // No transport type specific information for the other types. + break; + } + addDnsEvents(stats); + + return stats.build(); + } + + private void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) { + final int size = mDnsStallDetector.mResultIndices.size(); + for (int i = 1; i <= DEFAULT_DNS_LOG_SIZE && i <= size; i++) { + final int index = mDnsStallDetector.mResultIndices.indexOf(size - i); + stats.addDnsEvent(mDnsStallDetector.mDnsEvents[index].mReturnCode, + mDnsStallDetector.mDnsEvents[index].mTimeStamp); + } + } + + // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in // is required. This State takes care to clear the notification upon exit from the State. private class MaybeNotifyState extends State { @@ -972,6 +1044,11 @@ public class NetworkMonitor extends StateMachine { final CaptivePortalProbeResult probeResult = (CaptivePortalProbeResult) message.obj; mLastProbeTime = SystemClock.elapsedRealtime(); + + if (mCollectDataStallMetrics) { + writeDataStallStats(probeResult); + } + if (probeResult.isSuccessful()) { // Transit EvaluatingPrivateDnsState to get to Validated // state (even if no Private DNS validation required). @@ -1615,7 +1692,6 @@ public class NetworkMonitor extends StateMachine { */ @VisibleForTesting protected class DnsStallDetector { - private static final int DEFAULT_DNS_LOG_SIZE = 50; private int mConsecutiveTimeoutCount = 0; private int mSize; final DnsResult[] mDnsEvents; diff --git a/tests/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/src/com/android/server/connectivity/NetworkMonitorTest.java index 9a16bb7..beb975d 100644 --- a/tests/src/com/android/server/connectivity/NetworkMonitorTest.java +++ b/tests/src/com/android/server/connectivity/NetworkMonitorTest.java @@ -30,6 +30,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyObject; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; @@ -48,6 +49,7 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.captiveportal.CaptivePortalProbeResult; +import android.net.metrics.DataStallStatsUtils; import android.net.metrics.IpConnectivityLog; import android.net.util.SharedLog; import android.net.wifi.WifiManager; @@ -98,6 +100,7 @@ public class NetworkMonitorTest { private @Mock NetworkMonitor.Dependencies mDependencies; private @Mock INetworkMonitorCallbacks mCallbacks; private @Spy Network mNetwork = new Network(TEST_NETID); + private @Mock DataStallStatsUtils mDataStallStatsUtils; private static final int TEST_NETID = 4242; @@ -186,9 +189,9 @@ public class NetworkMonitorTest { private long mProbeTime = 0; WrappedNetworkMonitor(Context context, Network network, IpConnectivityLog logger, - Dependencies deps) { + Dependencies deps, DataStallStatsUtils statsUtils) { super(context, mCallbacks, network, logger, - new SharedLog("test_nm"), deps); + new SharedLog("test_nm"), deps, statsUtils); } @Override @@ -203,7 +206,7 @@ public class NetworkMonitorTest { private WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() { final WrappedNetworkMonitor nm = new WrappedNetworkMonitor( - mContext, mNetwork, mLogger, mDependencies); + mContext, mNetwork, mLogger, mDependencies, mDataStallStatsUtils); when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES); nm.start(); waitForIdle(nm.getHandler()); @@ -212,7 +215,7 @@ public class NetworkMonitorTest { private WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() { final WrappedNetworkMonitor nm = new WrappedNetworkMonitor( - mContext, mNetwork, mLogger, mDependencies); + mContext, mNetwork, mLogger, mDependencies, mDataStallStatsUtils); when(mCm.getNetworkCapabilities(any())).thenReturn(NOT_METERED_CAPABILITIES); nm.start(); waitForIdle(nm.getHandler()); @@ -222,7 +225,7 @@ public class NetworkMonitorTest { private NetworkMonitor makeMonitor() { final NetworkMonitor nm = new NetworkMonitor( mContext, mCallbacks, mNetwork, mLogger, mValidationLogger, - mDependencies); + mDependencies, mDataStallStatsUtils); nm.start(); waitForIdle(nm.getHandler()); return nm; @@ -505,6 +508,23 @@ public class NetworkMonitorTest { .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null); } + @Test + public void testDataStall_StallSuspectedAndSendMetrics() throws IOException { + WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor(); + wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); + makeDnsTimeoutEvent(wrappedMonitor, 5); + assertTrue(wrappedMonitor.isDataStall()); + verify(mDataStallStatsUtils, times(1)).write(eq(anyObject()), eq(anyObject())); + } + + @Test + public void testDataStall_NoStallSuspectedAndSendMetrics() throws IOException { + WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor(); + wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); + makeDnsTimeoutEvent(wrappedMonitor, 3); + assertFalse(wrappedMonitor.isDataStall()); + verify(mDataStallStatsUtils, times(0)).write(eq(anyObject()), eq(anyObject())); + } private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) { for (int i = 0; i < count; i++) { wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount( |