Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 342cb2c7 authored by lifr's avatar lifr Committed by Frank Li
Browse files

Injecting network ip provision stats into statsd

1. Fill in each field of the NetworkIpProvisioningReported
2. Write the NetworkIpProvisioningReported into statsd

Bug: 151796056
Test: atest NetworkStackIntegrationTests NetworkStackTests
Test: atest FrameworksNetTests
Test: Manual test with statsd_testdrive

Change-Id: If4bc6af1b794a8620a08858d6cfd85e661865bd7
parent b6db57bb
Loading
Loading
Loading
Loading
+30 −1
Original line number Diff line number Diff line
@@ -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;
        }

        /**
@@ -406,6 +412,13 @@ public class DhcpClient extends StateMachine {
            return mNetworkStackIpMemoryStore;
        }

        /**
         * 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) {
+51 −19
Original line number Diff line number Diff line
@@ -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;
                }
+18 −0
Original line number Diff line number Diff line
@@ -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;
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -48,6 +48,14 @@ public class Stopwatch {
        return this;
    }

    /**
     * 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.
+163 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
Loading