diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/android/net/dhcp/DhcpClient.java | 31 | ||||
-rw-r--r-- | src/android/net/ip/IpClient.java | 70 | ||||
-rwxr-xr-x | src/android/net/util/NetworkStackUtils.java | 18 | ||||
-rw-r--r-- | src/android/net/util/Stopwatch.java | 8 | ||||
-rw-r--r-- | src/com/android/networkstack/metrics/IpProvisioningMetrics.java | 163 |
5 files changed, 270 insertions, 20 deletions
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.Message; 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.CaptivePortalDataShimImpl; 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 @@ public class DhcpClient extends StateMachine { 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 class DhcpClient extends StateMachine { */ 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 @@ public class DhcpClient extends StateMachine { } /** + * 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 @@ public class DhcpClient extends StateMachine { mController = controller; mIfaceName = iface; mIpMemoryStore = deps.getIpMemoryStore(); + mMetrics = deps.getIpProvisioningMetrics(); // CHECKSTYLE:OFF IndentationCheck addState(mStoppedState); @@ -484,6 +498,7 @@ public class DhcpClient extends StateMachine { 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 @@ public class DhcpClient extends StateMachine { 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 @@ public class DhcpClient extends StateMachine { EventLog.writeEvent(snetTagId, bugId, uid, data); } mMetricsLog.log(mIfaceName, new DhcpErrorEvent(e.errorCode)); + mMetrics.addDhcpErrorCode(e.errorCode); } } @@ -687,6 +712,7 @@ public class DhcpClient extends StateMachine { 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 @@ public class DhcpClient extends StateMachine { String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() + " request=" + requestedAddress.getHostAddress() + " serverid=" + serverStr; + mMetrics.incrementCountForRequest(); return transmitPacket(packet, description, encap, to); } @@ -937,6 +964,7 @@ public class DhcpClient extends StateMachine { } else { startInitRebootOrInit(); } + recordMetricEnabledFeatures(); return HANDLED; default: return NOT_HANDLED; @@ -1422,6 +1450,7 @@ public class DhcpClient extends StateMachine { 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.Message; 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.internal.util.WakeupMessage; 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 @@ public class IpClient extends StateMachine { 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 @@ public class IpClient extends StateMachine { * 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 @@ public class IpClient extends StateMachine { * <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 @@ public class IpClient extends StateMachine { 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 @@ public class IpClient extends StateMachine { 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 @@ public class IpClient extends StateMachine { } 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 @@ public class IpClient extends StateMachine { dispatchCallback(delta, newLp); if (delta == PROV_CHANGE_LOST_PROVISIONING) { - transitionTo(mStoppingState); + transitionToStoppingState(code); } } @@ -1723,7 +1743,7 @@ public class IpClient extends StateMachine { 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 @@ public class IpClient extends StateMachine { 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 @@ public class IpClient extends StateMachine { 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 @@ public class IpClient extends StateMachine { @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 @@ public class IpClient extends StateMachine { break; case EVENT_PROVISIONING_TIMEOUT: - handleProvisioningFailure(); + handleProvisioningFailure(DisconnectCode.DC_PROVISIONING_TIMEOUT); break; default: @@ -1912,13 +1938,13 @@ public class IpClient extends StateMachine { 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 @@ public class IpClient extends StateMachine { 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 @@ public class IpClient extends StateMachine { 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 @@ public class IpClient extends StateMachine { 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 @@ public class IpClient extends StateMachine { 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 @@ public class IpClient extends StateMachine { } 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 @@ public class NetworkStackUtils { 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 @@ public class Stopwatch { } /** + * 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; + } +} |