Merge "Injecting data stall event to statsd"

This commit is contained in:
Chiachang Wang
2019-02-22 05:12:42 +00:00
committed by Gerrit Code Review
6 changed files with 406 additions and 9 deletions

View File

@@ -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"],
}
}

View File

@@ -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);
}
}
}

View File

@@ -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.
}
}

View File

@@ -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;
}
}

View File

@@ -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).
@@ -1617,7 +1694,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;

View File

@@ -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(