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

Commit e55546a1 authored by Hugo Benichi's avatar Hugo Benichi Committed by android-build-merger
Browse files

DO NOT MERGE IP Connectivity metrics: add connect() statistics

am: 2299a1c4

Change-Id: I7ad93b1b3a3446ffd6dce7c0799ddb9a2b43955f
parents 6b480eca 2299a1c4
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ import static com.android.internal.util.Preconditions.checkArgumentPositive;
 * The available amount of tokens is computed lazily when the bucket state is inspected.
 * Therefore it is purely synchronous and does not involve any asynchronous activity.
 * It is not synchronized in any way and not a thread-safe object.
 *
 * {@hide}
 */
public class TokenBucket {

+123 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.server.connectivity;

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

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

    /** 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();
    /** TokenBucket for rate limiting latency recording. */
    private final TokenBucket mLatencyTb;
    /** Maximum number of latency values recorded. */
    private final int mMaxLatencyRecords;
    /** Total count of successful connects. */
    private int mConnectCount = 0;
    /** Total count of successful connects with IPv6 socket address. */
    private int mIpv6ConnectCount = 0;

    public ConnectStats(TokenBucket tb, int maxLatencyRecords) {
        mLatencyTb = tb;
        mMaxLatencyRecords = maxLatencyRecords;
    }

    public ConnectStatistics toProto() {
        ConnectStatistics stats = new ConnectStatistics();
        stats.connectCount = mConnectCount;
        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(ipAddr);
            countLatency(errno, latencyMs);
        } else {
            countError(errno);
        }
    }

    private void countConnect(String ipAddr) {
        mConnectCount++;
        if (isIPv6(ipAddr)) mIpv6ConnectCount++;
    }

    private void countLatency(int errno, int ms) {
        if (isNonBlocking(errno)) {
            // Ignore connect() on non-blocking sockets
            return;
        }
        if (!mLatencyTb.get()) {
            // Rate limited
            return;
        }
        if (mLatencies.size() >= mMaxLatencyRecords) {
            // Hard limit the total number of latency measurements.
            return;
        }
        mLatencies.add(ms);
    }

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

    private static boolean isSuccess(int errno) {
        return (errno == 0) || isNonBlocking(errno);
    }

    private static boolean isNonBlocking(int errno) {
        // On non-blocking TCP sockets, connect() immediately returns EINPROGRESS.
        // On non-blocking TCP sockets that are connecting, connect() immediately returns EALREADY.
        return (errno == EINPROGRESS) || (errno == EALREADY);
    }

    private static boolean isIPv6(String ipAddr) {
        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;
    }
}
+4 −4
Original line number Diff line number Diff line
@@ -43,10 +43,10 @@ final public class IpConnectivityEventBuilder {
    private IpConnectivityEventBuilder() {
    }

    public static byte[] serialize(int dropped, List<ConnectivityMetricsEvent> events)
    public static byte[] serialize(int dropped, List<IpConnectivityEvent> events)
            throws IOException {
        final IpConnectivityLog log = new IpConnectivityLog();
        log.events = toProto(events);
        log.events = events.toArray(new IpConnectivityEvent[events.size()]);
        log.droppedEvents = dropped;
        if ((log.events.length > 0) || (dropped > 0)) {
            // Only write version number if log has some information at all.
@@ -55,7 +55,7 @@ final public class IpConnectivityEventBuilder {
        return IpConnectivityLog.toByteArray(log);
    }

    public static IpConnectivityEvent[] toProto(List<ConnectivityMetricsEvent> eventsIn) {
    public static List<IpConnectivityEvent> toProto(List<ConnectivityMetricsEvent> eventsIn) {
        final ArrayList<IpConnectivityEvent> eventsOut = new ArrayList<>(eventsIn.size());
        for (ConnectivityMetricsEvent in : eventsIn) {
            final IpConnectivityEvent out = toProto(in);
@@ -64,7 +64,7 @@ final public class IpConnectivityEventBuilder {
            }
            eventsOut.add(out);
        }
        return eventsOut.toArray(new IpConnectivityEvent[eventsOut.size()]);
        return eventsOut;
    }

    public static IpConnectivityEvent toProto(ConnectivityMetricsEvent ev) {
+11 −3
Original line number Diff line number Diff line
@@ -36,14 +36,14 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.TokenBucket;
import com.android.server.SystemService;
import com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.ToIntFunction;

import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent;

/** {@hide} */
final public class IpConnectivityMetrics extends SystemService {
    private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
@@ -63,6 +63,8 @@ final public class IpConnectivityMetrics extends SystemService {
    // Maximum size of the event buffer.
    private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;

    private static final int MAXIMUM_CONNECT_LATENCY_RECORDS = 20000;

    private static final int ERROR_RATE_LIMITED = -1;

    // Lock ensuring that concurrent manipulations of the event buffer are correct.
@@ -160,9 +162,15 @@ final public class IpConnectivityMetrics extends SystemService {
            initBuffer();
        }

        final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events);

        if (mNetdListener != null) {
            mNetdListener.flushStatistics(protoEvents);
        }

        final byte[] data;
        try {
            data = IpConnectivityEventBuilder.serialize(dropped, events);
            data = IpConnectivityEventBuilder.serialize(dropped, protoEvents);
        } catch (IOException e) {
            Log.e(TAG, "could not serialize events", e);
            return "";
+39 −5
Original line number Diff line number Diff line
@@ -19,25 +19,27 @@ package com.android.server.connectivity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.INetdEventCallback;
import android.net.Network;
import android.net.NetworkRequest;
import android.net.metrics.DnsEvent;
import android.net.metrics.INetdEventListener;
import android.net.metrics.IpConnectivityLog;
import android.os.RemoteException;
import android.text.format.DateUtils;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;

import com.android.internal.util.TokenBucket;
import com.android.server.connectivity.metrics.IpConnectivityLogClass.ConnectStatistics;
import com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;


/**
 * Implementation of the INetdEventListener interface.
 */
@@ -52,6 +54,12 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
    // TODO: read this constant from system property
    private static final int MAX_LOOKUPS_PER_DNS_EVENT = 100;

    // Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum
    // bursts of 5000 measurements.
    private static final int CONNECT_LATENCY_BURST_LIMIT  = 5000;
    private static final int CONNECT_LATENCY_FILL_RATE    = 15 * (int) DateUtils.SECOND_IN_MILLIS;
    private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;

    // Stores the results of a number of consecutive DNS lookups on the same network.
    // This class is not thread-safe and it is the responsibility of the service to call its methods
    // on one thread at a time.
@@ -121,6 +129,12 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
        }
    };

    @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")
@@ -175,13 +189,28 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
    // This method must not block or perform long-running operations.
    public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port,
            int uid) throws RemoteException {
        maybeVerboseLog("onConnectEvent(%d, %d, %dms)", netId, error, latencyMs);
        maybeVerboseLog("onConnectEvent(%d, %d)", netId, latencyMs);

        mConnectStats.addEvent(error, latencyMs, ipAddr);

        if (mNetdEventCallback != null) {
            mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid);
        }
    }

    public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
        events.add(flushConnectStats());
        // TODO: migrate DnsEventBatch to IpConnectivityLogClass.DNSLatencies
    }

    private IpConnectivityEvent flushConnectStats() {
        IpConnectivityEvent ev = new IpConnectivityEvent();
        ev.connectStatistics = mConnectStats.toProto();
        // TODO: add transport information
        mConnectStats = makeConnectStats();
        return ev;
    }

    public synchronized void dump(PrintWriter writer) {
        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
        pw.println(TAG + ":");
@@ -189,9 +218,14 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
        for (DnsEventBatch batch : mEventBatches.values()) {
            pw.println(batch.toString());
        }
        // TODO: also dump ConnectStats
        pw.decreaseIndent();
    }

    private ConnectStats makeConnectStats() {
        return new ConnectStats(mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS);
    }

    private static void maybeLog(String s, Object... args) {
        if (DBG) Log.d(TAG, String.format(s, args));
    }
Loading