Merge "Injecting network ip provision stats into statsd" into rvc-dev
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index 4404273..4fedf30 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -80,6 +80,7 @@
import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.Settings;
+import android.stats.connectivity.DhcpFeature;
import android.system.ErrnoException;
import android.system.Os;
import android.util.EventLog;
@@ -101,6 +102,7 @@
import com.android.networkstack.apishim.SocketUtilsShimImpl;
import com.android.networkstack.apishim.common.ShimUtils;
import com.android.networkstack.arp.ArpPacket;
+import com.android.networkstack.metrics.IpProvisioningMetrics;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -305,6 +307,8 @@
private final Context mContext;
private final Random mRandom;
private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+ @NonNull
+ private final IpProvisioningMetrics mMetrics;
// We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
// be off-link as well as on-link).
@@ -378,9 +382,11 @@
*/
public static class Dependencies {
private final NetworkStackIpMemoryStore mNetworkStackIpMemoryStore;
+ private final IpProvisioningMetrics mMetrics;
- public Dependencies(NetworkStackIpMemoryStore store) {
+ public Dependencies(NetworkStackIpMemoryStore store, IpProvisioningMetrics metrics) {
mNetworkStackIpMemoryStore = store;
+ mMetrics = metrics;
}
/**
@@ -407,6 +413,13 @@
}
/**
+ * Get a IpProvisioningMetrics instance.
+ */
+ public IpProvisioningMetrics getIpProvisioningMetrics() {
+ return mMetrics;
+ }
+
+ /**
* Return whether a feature guarded by a feature flag is enabled.
* @see NetworkStackUtils#isFeatureEnabled(Context, String, String)
*/
@@ -444,6 +457,7 @@
mController = controller;
mIfaceName = iface;
mIpMemoryStore = deps.getIpMemoryStore();
+ mMetrics = deps.getIpProvisioningMetrics();
// CHECKSTYLE:OFF IndentationCheck
addState(mStoppedState);
@@ -484,6 +498,7 @@
final boolean sendHostname = deps.getSendHostnameOption(context);
mHostname = sendHostname ? new HostnameTransliterator().transliterate(
deps.getDeviceName(mContext)) : null;
+ mMetrics.setHostnameTransinfo(sendHostname, mHostname != null);
}
public void registerForPreDhcpNotification() {
@@ -529,6 +544,15 @@
false /* defaultEnabled */);
}
+ private void recordMetricEnabledFeatures() {
+ if (isDhcpLeaseCacheEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_INITREBOOT);
+ if (isDhcpRapidCommitEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_RAPIDCOMMIT);
+ if (isDhcpIpConflictDetectEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_DAD);
+ if (mConfiguration.isPreconnectionEnabled) {
+ mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_FILS);
+ }
+ }
+
private void confirmDhcpLease(DhcpPacket packet, DhcpResults results) {
setDhcpLeaseExpiry(packet);
acceptDhcpResults(results, "Confirmed");
@@ -610,6 +634,7 @@
EventLog.writeEvent(snetTagId, bugId, uid, data);
}
mMetricsLog.log(mIfaceName, new DhcpErrorEvent(e.errorCode));
+ mMetrics.addDhcpErrorCode(e.errorCode);
}
}
@@ -687,6 +712,7 @@
final ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
DO_UNICAST, getRequestedParams(), isDhcpRapidCommitEnabled(), mHostname);
+ mMetrics.incrementCountForDiscover();
return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
}
@@ -705,6 +731,7 @@
String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
" request=" + requestedAddress.getHostAddress() +
" serverid=" + serverStr;
+ mMetrics.incrementCountForRequest();
return transmitPacket(packet, description, encap, to);
}
@@ -937,6 +964,7 @@
} else {
startInitRebootOrInit();
}
+ recordMetricEnabledFeatures();
return HANDLED;
default:
return NOT_HANDLED;
@@ -1422,6 +1450,7 @@
try {
final ArpPacket packet = ArpPacket.parseArpPacket(recvbuf, length);
if (hasIpAddressConflict(packet, mTargetIp)) {
+ mMetrics.incrementCountForIpConflict();
sendMessage(EVENT_IP_CONFLICT);
}
} catch (ArpPacket.ParseException e) {
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 5d5b025..eeff157 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -60,6 +60,7 @@
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
+import android.stats.connectivity.DisconnectCode;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Log;
@@ -79,6 +80,7 @@
import com.android.networkstack.apishim.NetworkInformationShimImpl;
import com.android.networkstack.apishim.common.NetworkInformationShim;
import com.android.networkstack.apishim.common.ShimUtils;
+import com.android.networkstack.metrics.IpProvisioningMetrics;
import com.android.server.NetworkObserverRegistry;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
@@ -129,6 +131,7 @@
private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>();
private final NetworkStackIpMemoryStore mIpMemoryStore;
private final NetworkInformationShim mShim = NetworkInformationShimImpl.newInstance();
+ private final IpProvisioningMetrics mIpProvisioningMetrics = new IpProvisioningMetrics();
/**
* Dump all state machine and connectivity packet logs to the specified writer.
@@ -527,8 +530,8 @@
* Get a DhcpClient Dependencies instance.
*/
public DhcpClient.Dependencies getDhcpClientDependencies(
- NetworkStackIpMemoryStore ipMemoryStore) {
- return new DhcpClient.Dependencies(ipMemoryStore);
+ NetworkStackIpMemoryStore ipMemoryStore, IpProvisioningMetrics metrics) {
+ return new DhcpClient.Dependencies(ipMemoryStore, metrics);
}
/**
@@ -818,7 +821,10 @@
* <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
*/
public void stop() {
- sendMessage(CMD_STOP);
+ // The message "arg1" parameter is used to record the disconnect code metrics.
+ // Usually this method is called by the peer (e.g. wifi) intentionally to stop IpClient,
+ // consider that's the normal user termination.
+ sendMessage(CMD_STOP, DisconnectCode.DC_NORMAL_TERMINATION.getNumber());
}
/**
@@ -1072,6 +1078,14 @@
mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
}
+ // Record the DisconnectCode and transition to StoppingState.
+ // When jumping to mStoppingState This function will ensure
+ // that you will not forget to fill in DisconnectCode.
+ private void transitionToStoppingState(final DisconnectCode code) {
+ mIpProvisioningMetrics.setDisconnectCode(code);
+ transitionTo(mStoppingState);
+ }
+
// For now: use WifiStateMachine's historical notion of provisioned.
@VisibleForTesting
static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
@@ -1352,6 +1366,12 @@
if (Objects.equals(newLp, mLinkProperties)) {
return true;
}
+
+ // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update,
+ // wait for the provisioning completion and record the latency.
+ mIpProvisioningMetrics.setIPv4ProvisionedLatencyOnFirstTime(newLp.isIpv4Provisioned());
+ mIpProvisioningMetrics.setIPv6ProvisionedLatencyOnFirstTime(newLp.isIpv6Provisioned());
+
final int delta = setLinkProperties(newLp);
// Most of the attributes stored in the memory store are deduced from
// the link properties, therefore when the properties update the memory
@@ -1447,10 +1467,10 @@
}
mCallback.onNewDhcpResults(null);
- handleProvisioningFailure();
+ handleProvisioningFailure(DisconnectCode.DC_PROVISIONING_FAIL);
}
- private void handleProvisioningFailure() {
+ private void handleProvisioningFailure(final DisconnectCode code) {
final LinkProperties newLp = assembleLinkProperties();
int delta = setLinkProperties(newLp);
// If we've gotten here and we're still not provisioned treat that as
@@ -1467,7 +1487,7 @@
dispatchCallback(delta, newLp);
if (delta == PROV_CHANGE_LOST_PROVISIONING) {
- transitionTo(mStoppingState);
+ transitionToStoppingState(code);
}
}
@@ -1723,7 +1743,7 @@
private void startDhcpClient() {
// Start DHCPv4.
mDhcpClient = mDependencies.makeDhcpClient(mContext, IpClient.this, mInterfaceParams,
- mDependencies.getDhcpClientDependencies(mIpMemoryStore));
+ mDependencies.getDhcpClientDependencies(mIpMemoryStore, mIpProvisioningMetrics));
// If preconnection is enabled, there is no need to ask Wi-Fi to disable powersaving
// during DHCP, because the DHCP handshake will happen during association. In order to
@@ -1744,7 +1764,8 @@
if (mInterfaceParams == null) {
logError("Failed to find InterfaceParams for " + mInterfaceName);
doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND);
- deferMessage(obtainMessage(CMD_STOP));
+ deferMessage(obtainMessage(CMD_STOP,
+ DisconnectCode.DC_INTERFACE_NOT_FOUND.getNumber()));
return;
}
@@ -1836,6 +1857,7 @@
class StartedState extends State {
@Override
public void enter() {
+ mIpProvisioningMetrics.reset();
mStartTimeMillis = SystemClock.elapsedRealtime();
if (mConfiguration.mProvisioningTimeoutMs > 0) {
final long alarmTime = SystemClock.elapsedRealtime()
@@ -1847,13 +1869,17 @@
@Override
public void exit() {
mProvisioningTimeoutAlarm.cancel();
+
+ // Record metrics information once this provisioning has completed due to certain
+ // reason (normal termination, provisioning timeout, lost provisioning and etc).
+ mIpProvisioningMetrics.statsWrite();
}
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case CMD_STOP:
- transitionTo(mStoppingState);
+ transitionToStoppingState(DisconnectCode.forNumber(msg.arg1));
break;
case CMD_UPDATE_L2KEY_CLUSTER: {
@@ -1875,7 +1901,7 @@
break;
case EVENT_PROVISIONING_TIMEOUT:
- handleProvisioningFailure();
+ handleProvisioningFailure(DisconnectCode.DC_PROVISIONING_TIMEOUT);
break;
default:
@@ -1912,13 +1938,13 @@
if (mConfiguration.mEnableIPv6 && !startIPv6()) {
doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
- enqueueJumpToStoppingState();
+ enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPV6);
return;
}
if (mConfiguration.mEnableIPv4 && !isUsingPreconnection() && !startIPv4()) {
doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
- enqueueJumpToStoppingState();
+ enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPV4);
return;
}
@@ -1926,14 +1952,14 @@
if ((config != null) && !applyInitialConfig(config)) {
// TODO introduce a new IpManagerEvent constant to distinguish this error case.
doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
- enqueueJumpToStoppingState();
+ enqueueJumpToStoppingState(DisconnectCode.DC_INVALID_PROVISIONING);
return;
}
if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
doImmediateProvisioningFailure(
IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
- enqueueJumpToStoppingState();
+ enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPREACHABILITYMONITOR);
return;
}
}
@@ -1965,8 +1991,8 @@
resetLinkProperties();
}
- private void enqueueJumpToStoppingState() {
- deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING));
+ private void enqueueJumpToStoppingState(final DisconnectCode code) {
+ deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING, code.getNumber()));
}
private ConnectivityPacketTracker createPacketTracker() {
@@ -2001,7 +2027,7 @@
switch (msg.what) {
case CMD_JUMP_RUNNING_TO_STOPPING:
case CMD_STOP:
- transitionTo(mStoppingState);
+ transitionToStoppingState(DisconnectCode.forNumber(msg.arg1));
break;
case CMD_START:
@@ -2028,8 +2054,14 @@
break;
case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ // EVENT_NETLINK_LINKPROPERTIES_CHANGED message will be received in both of
+ // provisioning loss and normal user termination case (e.g. turn off wifi or
+ // switch to another wifi ssid), hence, checking current interface change
+ // status (down or up) would help distinguish.
+ final boolean ifUp = (msg.arg1 != 0);
if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
- transitionTo(mStoppingState);
+ transitionToStoppingState(ifUp ? DisconnectCode.DC_PROVISIONING_FAIL
+ : DisconnectCode.DC_NORMAL_TERMINATION);
}
break;
@@ -2109,7 +2141,7 @@
} else {
logError("Failed to set IPv4 address.");
dispatchCallback(PROV_CHANGE_LOST_PROVISIONING, mLinkProperties);
- transitionTo(mStoppingState);
+ transitionToStoppingState(DisconnectCode.DC_PROVISIONING_FAIL);
}
break;
}
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 99563ee..19ca4b5 100755
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -436,4 +436,22 @@
return addr instanceof Inet6Address
&& ((addr.getAddress()[0] & 0xfe) == 0xfc);
}
+
+ /**
+ * Returns the {@code int} nearest in value to {@code value}.
+ *
+ * @param value any {@code long} value
+ * @return the same value cast to {@code int} if it is in the range of the {@code int}
+ * type, {@link Integer#MAX_VALUE} if it is too large, or {@link Integer#MIN_VALUE} if
+ * it is too small
+ */
+ public static int saturatedCast(long value) {
+ if (value > Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ }
+ if (value < Integer.MIN_VALUE) {
+ return Integer.MIN_VALUE;
+ }
+ return (int) value;
+ }
}
diff --git a/src/android/net/util/Stopwatch.java b/src/android/net/util/Stopwatch.java
index 07618e9..33653dd 100644
--- a/src/android/net/util/Stopwatch.java
+++ b/src/android/net/util/Stopwatch.java
@@ -49,6 +49,14 @@
}
/**
+ * Retart the Stopwatch.
+ */
+ public Stopwatch restart() {
+ mStartTimeNs = SystemClock.elapsedRealtimeNanos();
+ return this;
+ }
+
+ /**
* Stop the Stopwatch.
* @return the total time recorded, in microseconds, or 0 if not started.
*/
diff --git a/src/com/android/networkstack/metrics/IpProvisioningMetrics.java b/src/com/android/networkstack/metrics/IpProvisioningMetrics.java
new file mode 100644
index 0000000..1f969d4
--- /dev/null
+++ b/src/com/android/networkstack/metrics/IpProvisioningMetrics.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.metrics;
+
+import android.net.util.NetworkStackUtils;
+import android.net.util.Stopwatch;
+import android.stats.connectivity.DhcpErrorCode;
+import android.stats.connectivity.DhcpFeature;
+import android.stats.connectivity.DisconnectCode;
+import android.stats.connectivity.HostnameTransResult;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Class to record the network IpProvisioning into statsd.
+ * 1. Fill in NetworkIpProvisioningReported proto.
+ * 2. Write the NetworkIpProvisioningReported proto into statsd.
+ * 3. This class is not thread-safe, and should always be accessed from the same thread.
+ * @hide
+ */
+
+public class IpProvisioningMetrics {
+ private static final String TAG = IpProvisioningMetrics.class.getSimpleName();
+ private final NetworkIpProvisioningReported.Builder mStatsBuilder =
+ NetworkIpProvisioningReported.newBuilder();
+ private final DhcpSession.Builder mDhcpSessionBuilder = DhcpSession.newBuilder();
+ private final Stopwatch mIpv4Watch = new Stopwatch().start();
+ private final Stopwatch mIpv6Watch = new Stopwatch().start();
+ private final Stopwatch mWatch = new Stopwatch().start();
+ private final Set<DhcpFeature> mDhcpFeatures = new HashSet<DhcpFeature>();
+
+ // Define a maximum number of the DhcpErrorCode.
+ public static final int MAX_DHCP_ERROR_COUNT = 20;
+
+ /**
+ * reset this all metrics members
+ */
+ public void reset() {
+ mStatsBuilder.clear();
+ mDhcpSessionBuilder.clear();
+ mDhcpFeatures.clear();
+ mIpv4Watch.restart();
+ mIpv6Watch.restart();
+ mWatch.restart();
+ }
+
+ /**
+ * Write the TransportType into mStatsBuilder.
+ * TODO: implement this
+ */
+ public void setTransportType() {}
+
+ /**
+ * Write the IPv4Provisioned latency into mStatsBuilder.
+ */
+ public void setIPv4ProvisionedLatencyOnFirstTime(final boolean isIpv4Provisioned) {
+ if (isIpv4Provisioned && !mStatsBuilder.hasIpv4LatencyMicros()) {
+ mStatsBuilder.setIpv4LatencyMicros(NetworkStackUtils.saturatedCast(mIpv4Watch.stop()));
+ }
+ }
+
+ /**
+ * Write the IPv6Provisioned latency into mStatsBuilder.
+ */
+ public void setIPv6ProvisionedLatencyOnFirstTime(final boolean isIpv6Provisioned) {
+ if (isIpv6Provisioned && !mStatsBuilder.hasIpv6LatencyMicros()) {
+ mStatsBuilder.setIpv6LatencyMicros(NetworkStackUtils.saturatedCast(mIpv6Watch.stop()));
+ }
+ }
+
+ /**
+ * Write the DhcpFeature proto into mStatsBuilder.
+ */
+ public void setDhcpEnabledFeature(final DhcpFeature feature) {
+ if (feature == DhcpFeature.DF_UNKNOWN) return;
+ mDhcpFeatures.add(feature);
+ }
+
+ /**
+ * Write the DHCPDISCOVER transmission count into DhcpSession.
+ */
+ public void incrementCountForDiscover() {
+ mDhcpSessionBuilder.setDiscoverCount(mDhcpSessionBuilder.getDiscoverCount() + 1);
+ }
+
+ /**
+ * Write the DHCPREQUEST transmission count into DhcpSession.
+ */
+ public void incrementCountForRequest() {
+ mDhcpSessionBuilder.setRequestCount(mDhcpSessionBuilder.getRequestCount() + 1);
+ }
+
+ /**
+ * Write the IPv4 address conflict count into DhcpSession.
+ */
+ public void incrementCountForIpConflict() {
+ mDhcpSessionBuilder.setConflictCount(mDhcpSessionBuilder.getConflictCount() + 1);
+ }
+
+ /**
+ * Write the hostname transliteration result into DhcpSession.
+ */
+ public void setHostnameTransinfo(final boolean isOptionEnabled, final boolean transSuccess) {
+ mDhcpSessionBuilder.setHtResult(!isOptionEnabled ? HostnameTransResult.HTR_DISABLE :
+ transSuccess ? HostnameTransResult.HTR_SUCCESS : HostnameTransResult.HTR_FAILURE);
+ }
+
+ /**
+ * write the DHCP error code into DhcpSession.
+ */
+ public void addDhcpErrorCode(final int errorCode) {
+ if (mDhcpSessionBuilder.getErrorCodeCount() >= MAX_DHCP_ERROR_COUNT) return;
+ mDhcpSessionBuilder.addErrorCode(DhcpErrorCode.forNumber(errorCode));
+ }
+
+ /**
+ * Write the IP provision disconnect code into DhcpSession.
+ */
+ public void setDisconnectCode(final DisconnectCode disconnectCode) {
+ if (mStatsBuilder.hasDisconnectCode()) return;
+ mStatsBuilder.setDisconnectCode(disconnectCode);
+ }
+
+ /**
+ * Write the NetworkIpProvisioningReported proto into statsd.
+ */
+ public NetworkIpProvisioningReported statsWrite() {
+ if (!mWatch.isStarted()) return null;
+ for (DhcpFeature feature : mDhcpFeatures) {
+ mDhcpSessionBuilder.addUsedFeatures(feature);
+ }
+ mStatsBuilder.setDhcpSession(mDhcpSessionBuilder);
+ mStatsBuilder.setProvisioningDurationMicros(mWatch.stop());
+ mStatsBuilder.setRandomNumber((int) (Math.random() * 1000));
+ final NetworkIpProvisioningReported Stats = mStatsBuilder.build();
+ final byte[] DhcpSession = Stats.getDhcpSession().toByteArray();
+ NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_IP_PROVISIONING_REPORTED,
+ Stats.getTransportType().getNumber(),
+ Stats.getIpv4LatencyMicros(),
+ Stats.getIpv6LatencyMicros(),
+ Stats.getProvisioningDurationMicros(),
+ Stats.getDisconnectCode().getNumber(),
+ DhcpSession,
+ Stats.getRandomNumber());
+ mWatch.reset();
+ return Stats;
+ }
+}
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
index 150fd00..38eb84e 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
@@ -136,6 +136,7 @@
import com.android.networkstack.apishim.ConstantsShim;
import com.android.networkstack.apishim.common.ShimUtils;
import com.android.networkstack.arp.ArpPacket;
+import com.android.networkstack.metrics.IpProvisioningMetrics;
import com.android.server.NetworkObserver;
import com.android.server.NetworkObserverRegistry;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
@@ -318,8 +319,8 @@
@Override
public DhcpClient.Dependencies getDhcpClientDependencies(
- NetworkStackIpMemoryStore ipMemoryStore) {
- return new DhcpClient.Dependencies(ipMemoryStore) {
+ NetworkStackIpMemoryStore ipMemoryStore, IpProvisioningMetrics metrics) {
+ return new DhcpClient.Dependencies(ipMemoryStore, metrics) {
@Override
public boolean isFeatureEnabled(final Context context, final String name,
final boolean defaultEnabled) {
diff --git a/tests/unit/src/com/android/networkstack/metrics/NetworkIpProvisioningMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/NetworkIpProvisioningMetricsTest.java
new file mode 100644
index 0000000..39906e2
--- /dev/null
+++ b/tests/unit/src/com/android/networkstack/metrics/NetworkIpProvisioningMetricsTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.metrics;
+
+import android.net.metrics.DhcpErrorEvent;
+import android.stats.connectivity.DhcpErrorCode;
+import android.stats.connectivity.DhcpFeature;
+import android.stats.connectivity.DisconnectCode;
+import android.stats.connectivity.HostnameTransResult;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/**
+ * Tests for IpProvisioningMetrics.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkIpProvisioningMetricsTest {
+ @Test
+ public void testIpProvisioningMetrics_setHostnameTransinfo() throws Exception {
+ NetworkIpProvisioningReported mStats;
+ final IpProvisioningMetrics mMetrics = new IpProvisioningMetrics();
+
+ mMetrics.reset();
+ mMetrics.setHostnameTransinfo(false /* isOptionEnabled */, false /* transSuccess */);
+ mStats = mMetrics.statsWrite();
+ assertEquals(HostnameTransResult.HTR_DISABLE, mStats.getDhcpSession().getHtResult());
+
+ mMetrics.reset();
+ mMetrics.setHostnameTransinfo(true /* isOptionEnabled */, false /* transSuccess */);
+ mStats = mMetrics.statsWrite();
+ assertEquals(HostnameTransResult.HTR_FAILURE, mStats.getDhcpSession().getHtResult());
+
+ mMetrics.reset();
+ mMetrics.setHostnameTransinfo(true /* isOptionEnabled */, true /* transSuccess */);
+ mStats = mMetrics.statsWrite();
+ assertEquals(HostnameTransResult.HTR_SUCCESS, mStats.getDhcpSession().getHtResult());
+ }
+
+ @Test
+ public void testIpProvisioningMetrics_addDhcpErrorCode() throws Exception {
+ final NetworkIpProvisioningReported mStats;
+ final IpProvisioningMetrics mMetrics = new IpProvisioningMetrics();
+ mMetrics.reset();
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.DHCP_ERROR);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.L2_WRONG_ETH_TYPE);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.L3_INVALID_IP);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.L4_WRONG_PORT);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.BOOTP_TOO_SHORT);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.DHCP_NO_COOKIE);
+ for (int i = 0; i < mMetrics.MAX_DHCP_ERROR_COUNT; i++) {
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.PARSING_ERROR);
+ }
+ mStats = mMetrics.statsWrite();
+ assertEquals(DhcpErrorCode.ET_DHCP_ERROR, mStats.getDhcpSession().getErrorCode(0));
+ assertEquals(DhcpErrorCode.ET_L2_WRONG_ETH_TYPE, mStats.getDhcpSession().getErrorCode(1));
+ assertEquals(DhcpErrorCode.ET_L3_INVALID_IP, mStats.getDhcpSession().getErrorCode(2));
+ assertEquals(DhcpErrorCode.ET_L4_WRONG_PORT, mStats.getDhcpSession().getErrorCode(3));
+ assertEquals(DhcpErrorCode.ET_BOOTP_TOO_SHORT, mStats.getDhcpSession().getErrorCode(4));
+ assertEquals(DhcpErrorCode.ET_DHCP_NO_COOKIE, mStats.getDhcpSession().getErrorCode(5));
+ // Check can record the same error code
+ assertEquals(DhcpErrorCode.ET_PARSING_ERROR, mStats.getDhcpSession().getErrorCode(6));
+ assertEquals(DhcpErrorCode.ET_PARSING_ERROR, mStats.getDhcpSession().getErrorCode(6));
+ // The maximum number of DHCP error code counts is MAX_DHCP_ERROR_COUNT
+ assertEquals(mMetrics.MAX_DHCP_ERROR_COUNT, mStats.getDhcpSession().getErrorCodeCount());
+ }
+ @Test
+ public void testIpProvisioningMetrics_CollectMetrics() throws Exception {
+ final NetworkIpProvisioningReported mStats;
+ final IpProvisioningMetrics mMetrics = new IpProvisioningMetrics();
+ mMetrics.reset();
+ // Entering 3 DISCOVER_SEND_COUNTs
+ mMetrics.incrementCountForDiscover();
+ mMetrics.incrementCountForDiscover();
+ mMetrics.incrementCountForDiscover();
+
+ // Entering 2 SEND_REQUEST_COUNTs
+ mMetrics.incrementCountForRequest();
+ mMetrics.incrementCountForRequest();
+
+ // Entering 1 IP_CONFLICT_COUNT
+ mMetrics.incrementCountForIpConflict();
+
+ // Entering 4 DhcpFeatures and one is repeated, so it should only count to 3
+ mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_INITREBOOT);
+ mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_RAPIDCOMMIT);
+ mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_DAD);
+ mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_DAD);
+
+ // Entering 6 DhcpErrorCodes
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.L3_TOO_SHORT);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.DHCP_INVALID_OPTION_LENGTH);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.RECEIVE_ERROR);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.RECEIVE_ERROR);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.PARSING_ERROR);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.PARSING_ERROR);
+
+ mMetrics.setHostnameTransinfo(true /* isOptionEnabled */, true /* transSuccess */);
+
+ // Only the first IP provisioning disconnect code is recorded.
+ mMetrics.setDisconnectCode(DisconnectCode.DC_PROVISIONING_TIMEOUT);
+ mMetrics.setDisconnectCode(DisconnectCode.DC_ERROR_STARTING_IPV4);
+
+ // Writing the metrics into statsd
+ mStats = mMetrics.statsWrite();
+
+ // Verifing the result of the metrics.
+ assertEquals(3, mStats.getDhcpSession().getDiscoverCount());
+ assertEquals(2, mStats.getDhcpSession().getRequestCount());
+ assertEquals(1, mStats.getDhcpSession().getConflictCount());
+ assertEquals(3, mStats.getDhcpSession().getUsedFeaturesCount());
+ assertEquals(6, mStats.getDhcpSession().getErrorCodeCount());
+ assertEquals(HostnameTransResult.HTR_SUCCESS, mStats.getDhcpSession().getHtResult());
+ assertEquals(DisconnectCode.DC_PROVISIONING_TIMEOUT, mStats.getDisconnectCode());
+ }
+}