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: Id0d536ff723ded5c26eafe0bb138ba75ba2856c5
Merged-In: I4769496383943e714a1d350c298e093c2ed57477
(cherry picked from commit dfc2cc5857)
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
inferLinkLayer(out);
|
||||
return 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,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());
|
||||
}
|
||||
pw.println(connectStatsProto(mConnectStats).toString());
|
||||
listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto);
|
||||
listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto);
|
||||
}
|
||||
|
||||
private ConnectStats makeConnectStats() {
|
||||
return new ConnectStats(mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS);
|
||||
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)));
|
||||
}
|
||||
in.clear();
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -16,12 +16,22 @@
|
||||
|
||||
package com.android.server.connectivity;
|
||||
|
||||
import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
|
||||
import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityMetricsEvent;
|
||||
import android.net.IIpConnectivityMetrics;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.metrics.ApfProgramEvent;
|
||||
import android.net.metrics.ApfStats;
|
||||
import android.net.metrics.DefaultNetworkEvent;
|
||||
@@ -31,7 +41,9 @@ import android.net.metrics.IpManagerEvent;
|
||||
import android.net.metrics.IpReachabilityEvent;
|
||||
import android.net.metrics.RaEvent;
|
||||
import android.net.metrics.ValidationProbeEvent;
|
||||
import android.system.OsConstants;
|
||||
import android.os.Parcelable;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.Base64;
|
||||
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
|
||||
@@ -41,26 +53,38 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import junit.framework.TestCase;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
public class IpConnectivityMetricsTest extends TestCase {
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class IpConnectivityMetricsTest {
|
||||
static final IpReachabilityEvent FAKE_EV =
|
||||
new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
|
||||
|
||||
private static final String EXAMPLE_IPV4 = "192.0.2.1";
|
||||
private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
|
||||
|
||||
@Mock Context mCtx;
|
||||
@Mock IIpConnectivityMetrics mMockService;
|
||||
@Mock ConnectivityManager mCm;
|
||||
|
||||
IpConnectivityMetrics mService;
|
||||
NetdEventListenerService mNetdListener;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000);
|
||||
mNetdListener = new NetdEventListenerService(mCm);
|
||||
mService.mNetdListener = mNetdListener;
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
@Test
|
||||
public void testLoggingEvents() throws Exception {
|
||||
IpConnectivityLog logger = new IpConnectivityLog(mMockService);
|
||||
|
||||
@@ -74,7 +98,7 @@ public class IpConnectivityMetricsTest extends TestCase {
|
||||
assertEventsEqual(expectedEvent(3), got.get(2));
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
@Test
|
||||
public void testLoggingEventsWithMultipleCallers() throws Exception {
|
||||
IpConnectivityLog logger = new IpConnectivityLog(mMockService);
|
||||
|
||||
@@ -91,7 +115,7 @@ public class IpConnectivityMetricsTest extends TestCase {
|
||||
}.start();
|
||||
}
|
||||
|
||||
List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 100);
|
||||
List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 200);
|
||||
Collections.sort(got, EVENT_COMPARATOR);
|
||||
Iterator<ConnectivityMetricsEvent> iter = got.iterator();
|
||||
for (int i = 0; i < nCallers; i++) {
|
||||
@@ -102,7 +126,7 @@ public class IpConnectivityMetricsTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
@Test
|
||||
public void testBufferFlushing() {
|
||||
String output1 = getdump("flush");
|
||||
assertEquals("", output1);
|
||||
@@ -115,7 +139,7 @@ public class IpConnectivityMetricsTest extends TestCase {
|
||||
assertEquals("", output3);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
@Test
|
||||
public void testRateLimiting() {
|
||||
final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
|
||||
final ApfProgramEvent ev = new ApfProgramEvent();
|
||||
@@ -137,12 +161,19 @@ public class IpConnectivityMetricsTest extends TestCase {
|
||||
assertEquals("", output2);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
// TODO: add NetdEventListenerService coverage too
|
||||
public void testEndToEndLogging() {
|
||||
@Test
|
||||
public void testEndToEndLogging() throws Exception {
|
||||
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
|
||||
IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
|
||||
|
||||
NetworkCapabilities ncWifi = new NetworkCapabilities();
|
||||
NetworkCapabilities ncCell = new NetworkCapabilities();
|
||||
ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
|
||||
ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
|
||||
|
||||
when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
|
||||
when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
|
||||
|
||||
ApfStats apfStats = new ApfStats();
|
||||
apfStats.durationMs = 45000;
|
||||
apfStats.receivedRas = 10;
|
||||
@@ -178,6 +209,21 @@ public class IpConnectivityMetricsTest extends TestCase {
|
||||
logger.log(ev);
|
||||
}
|
||||
|
||||
// netId, errno, latency, destination
|
||||
connectEvent(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4);
|
||||
connectEvent(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6);
|
||||
connectEvent(100, 0, 110, EXAMPLE_IPV4);
|
||||
connectEvent(101, 0, 23, EXAMPLE_IPV4);
|
||||
connectEvent(101, 0, 45, EXAMPLE_IPV6);
|
||||
connectEvent(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4);
|
||||
|
||||
// netId, type, return code, latency
|
||||
dnsEvent(100, EVENT_GETADDRINFO, 0, 3456);
|
||||
dnsEvent(100, EVENT_GETADDRINFO, 3, 45);
|
||||
dnsEvent(100, EVENT_GETHOSTBYNAME, 0, 638);
|
||||
dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
|
||||
dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 34);
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
@@ -280,6 +326,70 @@ public class IpConnectivityMetricsTest extends TestCase {
|
||||
" router_lifetime: 2000",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 100",
|
||||
" time_ms: 0",
|
||||
" transports: 2",
|
||||
" connect_statistics <",
|
||||
" connect_blocking_count: 1",
|
||||
" connect_count: 3",
|
||||
" errnos_counters <",
|
||||
" key: 11",
|
||||
" value: 1",
|
||||
" >",
|
||||
" ipv6_addr_count: 1",
|
||||
" latencies_ms: 110",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 2",
|
||||
" network_id: 101",
|
||||
" time_ms: 0",
|
||||
" transports: 1",
|
||||
" connect_statistics <",
|
||||
" connect_blocking_count: 2",
|
||||
" connect_count: 2",
|
||||
" ipv6_addr_count: 1",
|
||||
" latencies_ms: 23",
|
||||
" latencies_ms: 45",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 100",
|
||||
" time_ms: 0",
|
||||
" transports: 2",
|
||||
" dns_lookup_batch <",
|
||||
" event_types: 1",
|
||||
" event_types: 1",
|
||||
" event_types: 2",
|
||||
" latencies_ms: 3456",
|
||||
" latencies_ms: 45",
|
||||
" latencies_ms: 638",
|
||||
" return_codes: 0",
|
||||
" return_codes: 3",
|
||||
" return_codes: 0",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 2",
|
||||
" network_id: 101",
|
||||
" time_ms: 0",
|
||||
" transports: 1",
|
||||
" dns_lookup_batch <",
|
||||
" event_types: 1",
|
||||
" event_types: 2",
|
||||
" latencies_ms: 56",
|
||||
" latencies_ms: 34",
|
||||
" return_codes: 0",
|
||||
" return_codes: 0",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, getdump("flush"));
|
||||
@@ -292,6 +402,14 @@ public class IpConnectivityMetricsTest extends TestCase {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
void connectEvent(int netid, int error, int latencyMs, String ipAddr) throws Exception {
|
||||
mNetdListener.onConnectEvent(netid, error, latencyMs, ipAddr, 80, 1);
|
||||
}
|
||||
|
||||
void dnsEvent(int netId, int type, int result, int latency) throws Exception {
|
||||
mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
|
||||
}
|
||||
|
||||
List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
|
||||
ArgumentCaptor<ConnectivityMetricsEvent> captor =
|
||||
ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
|
||||
|
||||
@@ -62,20 +62,22 @@ public class NetdEventListenerServiceTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mCm = mock(ConnectivityManager.class);
|
||||
mNetdEventListenerService = new NetdEventListenerService(mCm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDnsLogging() throws Exception {
|
||||
NetworkCapabilities ncWifi = new NetworkCapabilities();
|
||||
NetworkCapabilities ncCell = new NetworkCapabilities();
|
||||
ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
|
||||
ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
|
||||
|
||||
mCm = mock(ConnectivityManager.class);
|
||||
when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
|
||||
when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
|
||||
|
||||
mNetdEventListenerService = new NetdEventListenerService(mCm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDnsLogging() throws Exception {
|
||||
asyncDump(100);
|
||||
|
||||
dnsEvent(100, EVENT_GETADDRINFO, 0, 3456);
|
||||
dnsEvent(100, EVENT_GETADDRINFO, 0, 267);
|
||||
dnsEvent(100, EVENT_GETHOSTBYNAME, 22, 1230);
|
||||
@@ -96,18 +98,6 @@ public class NetdEventListenerServiceTest {
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 0",
|
||||
" network_id: 0",
|
||||
" time_ms: 0",
|
||||
" transports: 0",
|
||||
" connect_statistics <",
|
||||
" connect_blocking_count: 0",
|
||||
" connect_count: 0",
|
||||
" ipv6_addr_count: 0",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 100",
|
||||
" time_ms: 0",
|
||||
@@ -172,34 +162,36 @@ public class NetdEventListenerServiceTest {
|
||||
|
||||
@Test
|
||||
public void testConnectLogging() throws Exception {
|
||||
asyncDump(100);
|
||||
|
||||
final int OK = 0;
|
||||
Thread[] logActions = {
|
||||
// ignored
|
||||
connectEventAction(OsConstants.EALREADY, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(OsConstants.EALREADY, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
|
||||
// valid latencies
|
||||
connectEventAction(OK, 110, EXAMPLE_IPV4),
|
||||
connectEventAction(OK, 23, EXAMPLE_IPV4),
|
||||
connectEventAction(OK, 45, EXAMPLE_IPV4),
|
||||
connectEventAction(OK, 56, EXAMPLE_IPV4),
|
||||
connectEventAction(OK, 523, EXAMPLE_IPV6),
|
||||
connectEventAction(OK, 214, EXAMPLE_IPV6),
|
||||
connectEventAction(OK, 67, EXAMPLE_IPV6),
|
||||
connectEventAction(100, OK, 110, EXAMPLE_IPV4),
|
||||
connectEventAction(100, OK, 23, EXAMPLE_IPV4),
|
||||
connectEventAction(100, OK, 45, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OK, 56, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OK, 523, EXAMPLE_IPV6),
|
||||
connectEventAction(101, OK, 214, EXAMPLE_IPV6),
|
||||
connectEventAction(101, OK, 67, EXAMPLE_IPV6),
|
||||
// errors
|
||||
connectEventAction(OsConstants.EPERM, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(OsConstants.EPERM, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(OsConstants.EAGAIN, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(100, OsConstants.EPERM, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OsConstants.EPERM, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(100, OsConstants.EACCES, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(100, OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(101, OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4),
|
||||
};
|
||||
|
||||
for (Thread t : logActions) {
|
||||
@@ -209,59 +201,84 @@ public class NetdEventListenerServiceTest {
|
||||
t.join();
|
||||
}
|
||||
|
||||
List<IpConnectivityEvent> events = new ArrayList<>();
|
||||
mNetdEventListenerService.flushStatistics(events);
|
||||
|
||||
IpConnectivityEvent got = events.get(0);
|
||||
String got = flushStatistics();
|
||||
String want = String.join("\n",
|
||||
"if_name: \"\"",
|
||||
"link_layer: 0",
|
||||
"network_id: 0",
|
||||
"time_ms: 0",
|
||||
"transports: 0",
|
||||
"connect_statistics <",
|
||||
" connect_blocking_count: 7",
|
||||
" connect_count: 12",
|
||||
" errnos_counters <",
|
||||
" key: 1",
|
||||
" value: 2",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 100",
|
||||
" time_ms: 0",
|
||||
" transports: 2",
|
||||
" connect_statistics <",
|
||||
" connect_blocking_count: 3",
|
||||
" connect_count: 6",
|
||||
" errnos_counters <",
|
||||
" key: 1",
|
||||
" value: 1",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 11",
|
||||
" value: 1",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 13",
|
||||
" value: 1",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 98",
|
||||
" value: 1",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 110",
|
||||
" value: 2",
|
||||
" >",
|
||||
" ipv6_addr_count: 1",
|
||||
" latencies_ms: 23",
|
||||
" latencies_ms: 45",
|
||||
" latencies_ms: 110",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 11",
|
||||
" value: 1",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 2",
|
||||
" network_id: 101",
|
||||
" time_ms: 0",
|
||||
" transports: 1",
|
||||
" connect_statistics <",
|
||||
" connect_blocking_count: 4",
|
||||
" connect_count: 6",
|
||||
" errnos_counters <",
|
||||
" key: 1",
|
||||
" value: 1",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 13",
|
||||
" value: 2",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 110",
|
||||
" value: 1",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 111",
|
||||
" value: 1",
|
||||
" >",
|
||||
" ipv6_addr_count: 5",
|
||||
" latencies_ms: 56",
|
||||
" latencies_ms: 67",
|
||||
" latencies_ms: 214",
|
||||
" latencies_ms: 523",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 13",
|
||||
" value: 3",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 98",
|
||||
" value: 1",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 110",
|
||||
" value: 3",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 111",
|
||||
" value: 1",
|
||||
" >",
|
||||
" ipv6_addr_count: 6",
|
||||
" latencies_ms: 23",
|
||||
" latencies_ms: 45",
|
||||
" latencies_ms: 56",
|
||||
" latencies_ms: 67",
|
||||
" latencies_ms: 110",
|
||||
" latencies_ms: 214",
|
||||
" latencies_ms: 523",
|
||||
">\n");
|
||||
verifyConnectEvent(want, got);
|
||||
">",
|
||||
"version: 2\n");
|
||||
assertEquals(want, got);
|
||||
}
|
||||
|
||||
Thread connectEventAction(int error, int latencyMs, String ipAddr) {
|
||||
Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) {
|
||||
return new Thread(() -> {
|
||||
try {
|
||||
mNetdEventListenerService.onConnectEvent(100, error, latencyMs, ipAddr, 80, 1);
|
||||
mNetdEventListenerService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
|
||||
} catch (Exception e) {
|
||||
fail(e.toString());
|
||||
}
|
||||
@@ -272,25 +289,14 @@ public class NetdEventListenerServiceTest {
|
||||
mNetdEventListenerService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
|
||||
}
|
||||
|
||||
Thread dumpAction(long durationMs) throws Exception {
|
||||
void asyncDump(long durationMs) throws Exception {
|
||||
final long stop = System.currentTimeMillis() + durationMs;
|
||||
final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
|
||||
return new Thread(() -> {
|
||||
new Thread(() -> {
|
||||
while (System.currentTimeMillis() < stop) {
|
||||
mNetdEventListenerService.dump(pw);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void verifyConnectEvent(String expected, IpConnectivityEvent got) {
|
||||
try {
|
||||
Arrays.sort(got.getConnectStatistics().latenciesMs);
|
||||
Arrays.sort(got.getConnectStatistics().errnosCounters,
|
||||
Comparator.comparingInt((p) -> p.key));
|
||||
assertEquals(expected, got.toString());
|
||||
} catch (Exception e) {
|
||||
fail(e.toString());
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
|
||||
@@ -303,6 +309,16 @@ public class NetdEventListenerServiceTest {
|
||||
PrintWriter writer = new PrintWriter(buffer);
|
||||
metricsService.impl.dump(null, writer, new String[]{"flush"});
|
||||
byte[] bytes = Base64.decode(buffer.toString(), Base64.DEFAULT);
|
||||
return IpConnectivityLog.parseFrom(bytes).toString();
|
||||
IpConnectivityLog log = IpConnectivityLog.parseFrom(bytes);
|
||||
for (IpConnectivityEvent ev : log.events) {
|
||||
if (ev.getConnectStatistics() == null) {
|
||||
continue;
|
||||
}
|
||||
// Sort repeated fields of connect() events arriving in non-deterministic order.
|
||||
Arrays.sort(ev.getConnectStatistics().latenciesMs);
|
||||
Arrays.sort(ev.getConnectStatistics().errnosCounters,
|
||||
Comparator.comparingInt((p) -> p.key));
|
||||
}
|
||||
return log.toString();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user