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

Commit dfc2cc58 authored by Hugo Benichi's avatar Hugo Benichi
Browse files

Connectivity metrics: add transports to connect stats

This patch groups connect() events per netId. It adds netid and
transport information to serialized ConnectStatistics events.

Test: updated NetdEventListenerServiceTest
      updated IpConnectivityMetricsTest
      $ runtest frameworks-net passes
Bug: 34901696

Change-Id: I4769496383943e714a1d350c298e093c2ed57477
parent 0699cf98
Loading
Loading
Loading
Loading
+35 −50
Original line number Diff line number Diff line
@@ -16,53 +16,47 @@

package android.net.metrics;

import android.net.NetworkCapabilities;
import android.system.OsConstants;
import android.util.IntArray;
import android.util.SparseIntArray;
import com.android.internal.util.BitUtils;
import com.android.internal.util.TokenBucket;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.ConnectStatistics;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.Pair;

/**
 * A class that aggregates connect() statistics and helps build
 * IpConnectivityLogClass.ConnectStatistics instances.
 *
 * A class that aggregates connect() statistics.
 * {@hide}
 */
public class ConnectStats {
    private final static int EALREADY     = OsConstants.EALREADY;
    private final static int EINPROGRESS  = OsConstants.EINPROGRESS;

    /** Network id of the network associated with the event, or 0 if unspecified. */
    public final int netId;
    /** Transports of the network associated with the event, as defined in NetworkCapabilities. */
    public final long transports;
    /** How many events resulted in a given errno. */
    private final SparseIntArray mErrnos = new SparseIntArray();
    /** Latencies of blocking connects. TODO: add non-blocking connects latencies. */
    private final IntArray mLatencies = new IntArray();
    public final SparseIntArray errnos = new SparseIntArray();
    /** Latencies of successful blocking connects. TODO: add non-blocking connects latencies. */
    public final IntArray latencies = new IntArray();
    /** TokenBucket for rate limiting latency recording. */
    private final TokenBucket mLatencyTb;
    public final TokenBucket mLatencyTb;
    /** Maximum number of latency values recorded. */
    private final int mMaxLatencyRecords;
    public final int mMaxLatencyRecords;
    /** Total count of successful connects. */
    private int mConnectCount = 0;
    public int connectCount = 0;
    /** Total count of successful connects done in blocking mode. */
    private int mConnectBlockingCount = 0;
    public int connectBlockingCount = 0;
    /** Total count of successful connects with IPv6 socket address. */
    private int mIpv6ConnectCount = 0;
    public int ipv6ConnectCount = 0;

    public ConnectStats(TokenBucket tb, int maxLatencyRecords) {
    public ConnectStats(int netId, long transports, TokenBucket tb, int maxLatencyRecords) {
        this.netId = netId;
        this.transports = transports;
        mLatencyTb = tb;
        mMaxLatencyRecords = maxLatencyRecords;
    }

    public ConnectStatistics toProto() {
        ConnectStatistics stats = new ConnectStatistics();
        stats.connectCount = mConnectCount;
        stats.connectBlockingCount = mConnectBlockingCount;
        stats.ipv6AddrCount = mIpv6ConnectCount;
        stats.latenciesMs = mLatencies.toArray();
        stats.errnosCounters = toPairArrays(mErrnos);
        return stats;
    }

    public void addEvent(int errno, int latencyMs, String ipAddr) {
        if (isSuccess(errno)) {
            countConnect(errno, ipAddr);
@@ -73,12 +67,12 @@ public class ConnectStats {
    }

    private void countConnect(int errno, String ipAddr) {
        mConnectCount++;
        connectCount++;
        if (!isNonBlocking(errno)) {
            mConnectBlockingCount++;
            connectBlockingCount++;
        }
        if (isIPv6(ipAddr)) {
            mIpv6ConnectCount++;
            ipv6ConnectCount++;
        }
    }

@@ -91,16 +85,16 @@ public class ConnectStats {
            // Rate limited
            return;
        }
        if (mLatencies.size() >= mMaxLatencyRecords) {
        if (latencies.size() >= mMaxLatencyRecords) {
            // Hard limit the total number of latency measurements.
            return;
        }
        mLatencies.add(ms);
        latencies.add(ms);
    }

    private void countError(int errno) {
        final int newcount = mErrnos.get(errno, 0) + 1;
        mErrnos.put(errno, newcount);
        final int newcount = errnos.get(errno, 0) + 1;
        errnos.put(errno, newcount);
    }

    private static boolean isSuccess(int errno) {
@@ -117,27 +111,18 @@ public class ConnectStats {
        return ipAddr.contains(":");
    }

    private static Pair[] toPairArrays(SparseIntArray counts) {
        final int s = counts.size();
        Pair[] pairs = new Pair[s];
        for (int i = 0; i < s; i++) {
            Pair p = new Pair();
            p.key = counts.keyAt(i);
            p.value = counts.valueAt(i);
            pairs[i] = p;
        }
        return pairs;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("ConnectStats(")
                .append(String.format("%d success, ", mConnectCount))
                .append(String.format("%d blocking, ", mConnectBlockingCount))
                .append(String.format("%d IPv6 dst", mIpv6ConnectCount));
        for (int i = 0; i < mErrnos.size(); i++) {
            String errno = OsConstants.errnoName(mErrnos.keyAt(i));
            int count = mErrnos.valueAt(i);
        StringBuilder builder = new StringBuilder("ConnectStats(").append(netId).append(", ");
        for (int t : BitUtils.unpackBits(transports)) {
            builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
        }
        builder.append(String.format("%d success, ", connectCount));
        builder.append(String.format("%d blocking, ", connectBlockingCount));
        builder.append(String.format("%d IPv6 dst", ipv6ConnectCount));
        for (int i = 0; i < errnos.size(); i++) {
            String errno = OsConstants.errnoName(errnos.keyAt(i));
            int count = errnos.valueAt(i);
            builder.append(String.format(", %s: %d", errno, count));
        }
        return builder.append(")").toString();
+7 −1
Original line number Diff line number Diff line
@@ -16,7 +16,9 @@

package android.net.metrics;

import android.net.NetworkCapabilities;
import java.util.Arrays;
import com.android.internal.util.BitUtils;

/**
 * A DNS event recorded by NetdEventListenerService.
@@ -74,6 +76,10 @@ final public class DnsEvent {

    @Override
    public String toString() {
        return String.format("DnsEvent(%d events)", eventCount);
        StringBuilder builder = new StringBuilder("DnsEvent(").append(netId).append(", ");
        for (int t : BitUtils.unpackBits(transports)) {
            builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
        }
        return builder.append(eventCount).append(" events)").toString();
    }
}
+46 −15
Original line number Diff line number Diff line
@@ -23,9 +23,6 @@ import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.NetworkId;

import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent;
@@ -34,6 +31,7 @@ import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.DhcpClientEvent;
import android.net.metrics.DhcpErrorEvent;
import android.net.metrics.DnsEvent;
import android.net.metrics.ConnectStats;
import android.net.metrics.IpManagerEvent;
import android.net.metrics.IpReachabilityEvent;
import android.net.metrics.NetworkEvent;
@@ -41,7 +39,12 @@ import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
import android.os.Parcelable;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.NetworkId;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.Pair;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -77,35 +80,51 @@ final public class IpConnectivityEventBuilder {
    }

    public static IpConnectivityEvent toProto(ConnectivityMetricsEvent ev) {
        final IpConnectivityEvent out = new IpConnectivityEvent();
        final IpConnectivityEvent out = buildEvent(ev.netId, ev.transports, ev.ifname);
        out.timeMs = ev.timestamp;
        if (!setEvent(out, ev.data)) {
            return null;
        }
        out.timeMs = ev.timestamp;
        out.networkId = ev.netId;
        out.transports = ev.transports;
        if (ev.ifname != null) {
          out.ifName = ev.ifname;
        return out;
    }
        inferLinkLayer(out);

    public static IpConnectivityEvent toProto(ConnectStats in) {
        IpConnectivityLogClass.ConnectStatistics stats =
                new IpConnectivityLogClass.ConnectStatistics();
        stats.connectCount = in.connectCount;
        stats.connectBlockingCount = in.connectBlockingCount;
        stats.ipv6AddrCount = in.ipv6ConnectCount;
        stats.latenciesMs = in.latencies.toArray();
        stats.errnosCounters = toPairArray(in.errnos);
        final IpConnectivityEvent out = buildEvent(in.netId, in.transports, null);
        out.setConnectStatistics(stats);
        return out;
    }


    public static IpConnectivityEvent toProto(DnsEvent in) {
        final IpConnectivityEvent out = new IpConnectivityEvent();
        IpConnectivityLogClass.DNSLookupBatch dnsLookupBatch =
                new IpConnectivityLogClass.DNSLookupBatch();
        in.resize(in.eventCount);
        dnsLookupBatch.eventTypes = bytesToInts(in.eventTypes);
        dnsLookupBatch.returnCodes = bytesToInts(in.returnCodes);
        dnsLookupBatch.latenciesMs = in.latenciesMs;
        final IpConnectivityEvent out = buildEvent(in.netId, in.transports, null);
        out.setDnsLookupBatch(dnsLookupBatch);
        out.networkId = in.netId;
        out.transports = in.transports;
        inferLinkLayer(out);
        return out;
    }

    private static IpConnectivityEvent buildEvent(int netId, long transports, String ifname) {
        final IpConnectivityEvent ev = new IpConnectivityEvent();
        ev.networkId = netId;
        ev.transports = transports;
        if (ifname != null) {
            ev.ifName = ifname;
        }
        inferLinkLayer(ev);
        return ev;
    }

    private static boolean setEvent(IpConnectivityEvent out, Parcelable in) {
        if (in instanceof DhcpErrorEvent) {
            setDhcpErrorEvent(out, (DhcpErrorEvent) in);
@@ -268,6 +287,18 @@ final public class IpConnectivityEventBuilder {
        return out;
    }

    private static Pair[] toPairArray(SparseIntArray counts) {
        final int s = counts.size();
        Pair[] pairs = new Pair[s];
        for (int i = 0; i < s; i++) {
            Pair p = new Pair();
            p.key = counts.keyAt(i);
            p.value = counts.valueAt(i);
            pairs[i] = p;
        }
        return pairs;
    }

    private static NetworkId netIdOf(int netid) {
        final NetworkId ni = new NetworkId();
        ni.networkId = netid;
+34 −39
Original line number Diff line number Diff line
@@ -34,10 +34,11 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.BitUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.TokenBucket;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.ConnectStatistics;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
import java.io.PrintWriter;
import java.util.List;
import java.util.function.Function;
import java.util.function.IntFunction;

/**
 * Implementation of the INetdEventListener interface.
@@ -58,18 +59,17 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
    private static final int CONNECT_LATENCY_FILL_RATE    = 15 * (int) DateUtils.SECOND_IN_MILLIS;
    private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;

    // Sparse array of DNS events, grouped by net id.
    // Sparse arrays of DNS and connect events, grouped by net id.
    @GuardedBy("this")
    private final SparseArray<DnsEvent> mDnsEvents = new SparseArray<>();
    @GuardedBy("this")
    private final SparseArray<ConnectStats> mConnectEvents = new SparseArray<>();

    private final ConnectivityManager mCm;

    @GuardedBy("this")
    private final TokenBucket mConnectTb =
            new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT);
    @GuardedBy("this")
    private ConnectStats mConnectStats = makeConnectStats();

    // Callback should only be registered/unregistered when logging is being enabled/disabled in DPM
    // by the device owner. It's DevicePolicyManager's responsibility to ensure that.
    @GuardedBy("this")
@@ -123,7 +123,12 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
            int port, int uid) throws RemoteException {
        maybeVerboseLog("onConnectEvent(%d, %d, %dms)", netId, error, latencyMs);

        mConnectStats.addEvent(error, latencyMs, ipAddr);
        ConnectStats connectStats = mConnectEvents.get(netId);
        if (connectStats == null) {
            connectStats = makeConnectStats(netId);
            mConnectEvents.put(netId, connectStats);
        }
        connectStats.addEvent(error, latencyMs, ipAddr);

        if (mNetdEventCallback != null) {
            mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid);
@@ -131,29 +136,8 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
    }

    public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
        flushConnectStats(events);
        flushDnsStats(events);
    }

    private static IpConnectivityEvent connectStatsProto(ConnectStats connectStats) {
        // TODO: add transport information
        IpConnectivityEvent ev = new IpConnectivityEvent();
        ev.setConnectStatistics(connectStats.toProto());
        return ev;
    }

    private void flushConnectStats(List<IpConnectivityEvent> events) {
        events.add(connectStatsProto(mConnectStats));
        mConnectStats = makeConnectStats();
    }

    private void flushDnsStats(List<IpConnectivityEvent> events) {
        // TODO: migrate DnsEventBatch to IpConnectivityLogClass.DNSLatencies
        for (int i = 0; i < mDnsEvents.size(); i++) {
            IpConnectivityEvent ev = IpConnectivityEventBuilder.toProto(mDnsEvents.valueAt(i));
            events.add(ev);
        }
        mDnsEvents.clear();
        flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto);
        flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto);
    }

    public synchronized void dump(PrintWriter writer) {
@@ -165,22 +149,33 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
    }

    public synchronized void list(PrintWriter pw) {
        for (int i = 0; i < mDnsEvents.size(); i++) {
            pw.println(mDnsEvents.valueAt(i).toString());
        }
        pw.println(mConnectStats.toString());
        listEvents(pw, mConnectEvents, (x) -> x);
        listEvents(pw, mDnsEvents, (x) -> x);
    }

    public synchronized void listAsProtos(PrintWriter pw) {
        for (int i = 0; i < mDnsEvents.size(); i++) {
            IpConnectivityEvent ev = IpConnectivityEventBuilder.toProto(mDnsEvents.valueAt(i));
            pw.println(ev.toString());
        listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto);
        listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto);
    }

    private static <T> void flushProtos(List<IpConnectivityEvent> out, SparseArray<T> in,
            Function<T, IpConnectivityEvent> mapper) {
        for (int i = 0; i < in.size(); i++) {
            out.add(mapper.apply(in.valueAt(i)));
        }
        pw.println(connectStatsProto(mConnectStats).toString());
        in.clear();
    }

    private ConnectStats makeConnectStats() {
        return new ConnectStats(mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS);
    public static <T> void listEvents(
            PrintWriter pw, SparseArray<T> events, Function<T, Object> mapper) {
        for (int i = 0; i < events.size(); i++) {
            pw.println(mapper.apply(events.valueAt(i)).toString());
        }
    }

    private ConnectStats makeConnectStats(int netId) {
        long transports = getTransports(netId);
        return new ConnectStats(netId, transports, mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS);
    }

    private DnsEvent makeDnsEvent(int netId) {
+5 −2
Original line number Diff line number Diff line
@@ -45,7 +45,9 @@ import android.net.metrics.NetworkEvent;
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;

// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
@@ -483,8 +485,9 @@ public class IpConnectivityEventBuilderTest extends TestCase {

    static void verifySerialization(String want, ConnectivityMetricsEvent... input) {
        try {
            byte[] got = IpConnectivityEventBuilder.serialize(0,
                    IpConnectivityEventBuilder.toProto(Arrays.asList(input)));
            List<IpConnectivityEvent> proto =
                    IpConnectivityEventBuilder.toProto(Arrays.asList(input));
            byte[] got = IpConnectivityEventBuilder.serialize(0, proto);
            IpConnectivityLog log = IpConnectivityLog.parseFrom(got);
            assertEquals(want, log.toString());
        } catch (Exception e) {
Loading