Merge changes Id0d536ff,I4fcd0ad7 into oc-dev
* changes:
Connectivity metrics: add transports to connect stats
Connectivity metrics: log DnsEvents in-band
diff --git a/core/java/android/net/metrics/ConnectStats.java b/core/java/android/net/metrics/ConnectStats.java
index 214edee..30b2656 100644
--- a/core/java/android/net/metrics/ConnectStats.java
+++ b/core/java/android/net/metrics/ConnectStats.java
@@ -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 @@
}
private void countConnect(int errno, String ipAddr) {
- mConnectCount++;
+ connectCount++;
if (!isNonBlocking(errno)) {
- mConnectBlockingCount++;
+ connectBlockingCount++;
}
if (isIPv6(ipAddr)) {
- mIpv6ConnectCount++;
+ ipv6ConnectCount++;
}
}
@@ -91,16 +85,16 @@
// 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 @@
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();
diff --git a/core/java/android/net/metrics/DnsEvent.java b/core/java/android/net/metrics/DnsEvent.java
index 89ae1c2..a4970e4 100644
--- a/core/java/android/net/metrics/DnsEvent.java
+++ b/core/java/android/net/metrics/DnsEvent.java
@@ -16,67 +16,70 @@
package android.net.metrics;
-import android.os.Parcel;
-import android.os.Parcelable;
+import android.net.NetworkCapabilities;
+import java.util.Arrays;
+import com.android.internal.util.BitUtils;
/**
* A DNS event recorded by NetdEventListenerService.
* {@hide}
*/
-final public class DnsEvent implements Parcelable {
- public final int netId;
+final public class DnsEvent {
- // The event type is currently only 1 or 2, so we store it as a byte.
- public final byte[] eventTypes;
+ private static final int SIZE_LIMIT = 20000;
+
+ // 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.
+ // It is the caller responsability to ensure the value of transports does not change between
+ // calls to addResult.
+ public final long transports;
+ // The number of DNS queries recorded. Queries are stored in the structure-of-array style where
+ // the eventTypes, returnCodes, and latenciesMs arrays have the same length and the i-th event
+ // is spread across the three array at position i.
+ public int eventCount;
+ // The types of DNS queries as defined in INetdEventListener.
+ public byte[] eventTypes;
// Current getaddrinfo codes go from 1 to EAI_MAX = 15. gethostbyname returns errno, but there
// are fewer than 255 errno values. So we store the result code in a byte as well.
- public final byte[] returnCodes;
- // The latency is an integer because a) short arrays aren't parcelable and b) a short can only
- // store a maximum latency of 32757 or 65535 ms, which is too short for pathologically slow
- // queries.
- public final int[] latenciesMs;
+ public byte[] returnCodes;
+ // Latencies in milliseconds of queries, stored as ints.
+ public int[] latenciesMs;
- public DnsEvent(int netId, byte[] eventTypes, byte[] returnCodes, int[] latenciesMs) {
+ public DnsEvent(int netId, long transports, int initialCapacity) {
this.netId = netId;
- this.eventTypes = eventTypes;
- this.returnCodes = returnCodes;
- this.latenciesMs = latenciesMs;
+ this.transports = transports;
+ eventTypes = new byte[initialCapacity];
+ returnCodes = new byte[initialCapacity];
+ latenciesMs = new int[initialCapacity];
}
- private DnsEvent(Parcel in) {
- this.netId = in.readInt();
- this.eventTypes = in.createByteArray();
- this.returnCodes = in.createByteArray();
- this.latenciesMs = in.createIntArray();
+ public void addResult(byte eventType, byte returnCode, int latencyMs) {
+ if (eventCount >= SIZE_LIMIT) {
+ // TODO: implement better rate limiting that does not biases metrics.
+ return;
+ }
+ if (eventCount == eventTypes.length) {
+ resize((int) (1.4 * eventCount));
+ }
+ eventTypes[eventCount] = eventType;
+ returnCodes[eventCount] = returnCode;
+ latenciesMs[eventCount] = latencyMs;
+ eventCount++;
}
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(netId);
- out.writeByteArray(eventTypes);
- out.writeByteArray(returnCodes);
- out.writeIntArray(latenciesMs);
- }
-
- @Override
- public int describeContents() {
- return 0;
+ public void resize(int newLength) {
+ eventTypes = Arrays.copyOf(eventTypes, newLength);
+ returnCodes = Arrays.copyOf(returnCodes, newLength);
+ latenciesMs = Arrays.copyOf(latenciesMs, newLength);
}
@Override
public String toString() {
- return String.format("DnsEvent(%d, %d events)", netId, eventTypes.length);
+ 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();
}
-
- public static final Parcelable.Creator<DnsEvent> CREATOR = new Parcelable.Creator<DnsEvent>() {
- @Override
- public DnsEvent createFromParcel(Parcel in) {
- return new DnsEvent(in);
- }
-
- @Override
- public DnsEvent[] newArray(int size) {
- return new DnsEvent[size];
- }
- };
}
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index ad66faa..5dee91d 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -23,9 +23,6 @@
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.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.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,20 +80,51 @@
}
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) {
+ 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);
+ 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);
@@ -102,11 +136,6 @@
return true;
}
- if (in instanceof DnsEvent) {
- setDnsEvent(out, (DnsEvent) in);
- return true;
- }
-
if (in instanceof IpManagerEvent) {
setIpManagerEvent(out, (IpManagerEvent) in);
return true;
@@ -163,16 +192,6 @@
out.setDhcpEvent(dhcpEvent);
}
- private static void setDnsEvent(IpConnectivityEvent out, DnsEvent in) {
- IpConnectivityLogClass.DNSLookupBatch dnsLookupBatch =
- new IpConnectivityLogClass.DNSLookupBatch();
- dnsLookupBatch.networkId = netIdOf(in.netId);
- dnsLookupBatch.eventTypes = bytesToInts(in.eventTypes);
- dnsLookupBatch.returnCodes = bytesToInts(in.returnCodes);
- dnsLookupBatch.latenciesMs = in.latenciesMs;
- out.setDnsLookupBatch(dnsLookupBatch);
- }
-
private static void setIpManagerEvent(IpConnectivityEvent out, IpManagerEvent in) {
IpConnectivityLogClass.IpProvisioningEvent ipProvisioningEvent =
new IpConnectivityLogClass.IpProvisioningEvent();
@@ -268,6 +287,18 @@
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;
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index da56a07..475d786 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -76,7 +76,8 @@
@VisibleForTesting
public final Impl impl = new Impl();
- private NetdEventListenerService mNetdListener;
+ @VisibleForTesting
+ NetdEventListenerService mNetdListener;
@GuardedBy("mLock")
private ArrayList<ConnectivityMetricsEvent> mBuffer;
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 7b9c60c..214cfce 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -18,10 +18,9 @@
import android.content.Context;
import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
import android.net.INetdEventCallback;
import android.net.Network;
-import android.net.NetworkRequest;
+import android.net.NetworkCapabilities;
import android.net.metrics.ConnectStats;
import android.net.metrics.DnsEvent;
import android.net.metrics.INetdEventListener;
@@ -29,17 +28,17 @@
import android.os.RemoteException;
import android.text.format.DateUtils;
import android.util.Log;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
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.Arrays;
import java.util.List;
-import java.util.SortedMap;
-import java.util.TreeMap;
+import java.util.function.Function;
+import java.util.function.IntFunction;
/**
* Implementation of the INetdEventListener interface.
@@ -52,8 +51,7 @@
private static final boolean DBG = false;
private static final boolean VDBG = false;
- // TODO: read this constant from system property
- private static final int MAX_LOOKUPS_PER_DNS_EVENT = 100;
+ private static final int INITIAL_DNS_BATCH_SIZE = 100;
// Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum
// bursts of 5000 measurements.
@@ -61,81 +59,17 @@
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.
- private class DnsEventBatch {
- private final int mNetId;
-
- private final byte[] mEventTypes = new byte[MAX_LOOKUPS_PER_DNS_EVENT];
- private final byte[] mReturnCodes = new byte[MAX_LOOKUPS_PER_DNS_EVENT];
- private final int[] mLatenciesMs = new int[MAX_LOOKUPS_PER_DNS_EVENT];
- private int mEventCount;
-
- public DnsEventBatch(int netId) {
- mNetId = netId;
- }
-
- public void addResult(byte eventType, byte returnCode, int latencyMs) {
- mEventTypes[mEventCount] = eventType;
- mReturnCodes[mEventCount] = returnCode;
- mLatenciesMs[mEventCount] = latencyMs;
- mEventCount++;
- if (mEventCount == MAX_LOOKUPS_PER_DNS_EVENT) {
- logAndClear();
- }
- }
-
- public void logAndClear() {
- // Did we lose a race with addResult?
- if (mEventCount == 0) {
- return;
- }
-
- // Only log as many events as we actually have.
- byte[] eventTypes = Arrays.copyOf(mEventTypes, mEventCount);
- byte[] returnCodes = Arrays.copyOf(mReturnCodes, mEventCount);
- int[] latenciesMs = Arrays.copyOf(mLatenciesMs, mEventCount);
- mMetricsLog.log(new DnsEvent(mNetId, eventTypes, returnCodes, latenciesMs));
- maybeLog("Logging %d results for netId %d", mEventCount, mNetId);
- mEventCount = 0;
- }
-
- // For debugging and unit tests only.
- public String toString() {
- return String.format("%s %d %d", getClass().getSimpleName(), mNetId, mEventCount);
- }
- }
-
- // Only sorted for ease of debugging. Because we only typically have a handful of networks up
- // at any given time, performance is not a concern.
+ // Sparse arrays of DNS and connect events, grouped by net id.
@GuardedBy("this")
- private final SortedMap<Integer, DnsEventBatch> mEventBatches = new TreeMap<>();
+ private final SparseArray<DnsEvent> mDnsEvents = new SparseArray<>();
+ @GuardedBy("this")
+ private final SparseArray<ConnectStats> mConnectEvents = new SparseArray<>();
- // We register a NetworkCallback to ensure that when a network disconnects, we flush the DNS
- // queries we've logged on that network. Because we do not do this periodically, we might lose
- // up to MAX_LOOKUPS_PER_DNS_EVENT lookup stats on each network when the system is shutting
- // down. We believe this to be sufficient for now.
private final ConnectivityManager mCm;
- private final IpConnectivityLog mMetricsLog;
- private final NetworkCallback mNetworkCallback = new NetworkCallback() {
- @Override
- public void onLost(Network network) {
- synchronized (NetdEventListenerService.this) {
- DnsEventBatch batch = mEventBatches.remove(network.netId);
- if (batch != null) {
- batch.logAndClear();
- }
- }
- }
- };
@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")
@@ -152,16 +86,13 @@
}
public NetdEventListenerService(Context context) {
- this(context.getSystemService(ConnectivityManager.class), new IpConnectivityLog());
+ this(context.getSystemService(ConnectivityManager.class));
}
@VisibleForTesting
- public NetdEventListenerService(ConnectivityManager cm, IpConnectivityLog log) {
+ public NetdEventListenerService(ConnectivityManager cm) {
// We are started when boot is complete, so ConnectivityService should already be running.
mCm = cm;
- mMetricsLog = log;
- final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
- mCm.registerNetworkCallback(request, mNetworkCallback);
}
@Override
@@ -172,16 +103,16 @@
throws RemoteException {
maybeVerboseLog("onDnsEvent(%d, %d, %d, %dms)", netId, eventType, returnCode, latencyMs);
- DnsEventBatch batch = mEventBatches.get(netId);
- if (batch == null) {
- batch = new DnsEventBatch(netId);
- mEventBatches.put(netId, batch);
+ DnsEvent dnsEvent = mDnsEvents.get(netId);
+ if (dnsEvent == null) {
+ dnsEvent = makeDnsEvent(netId);
+ mDnsEvents.put(netId, dnsEvent);
}
- batch.addResult((byte) eventType, (byte) returnCode, latencyMs);
+ dnsEvent.addResult((byte) eventType, (byte) returnCode, latencyMs);
if (mNetdEventCallback != null) {
- mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount,
- System.currentTimeMillis(), uid);
+ long timestamp = System.currentTimeMillis();
+ mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid);
}
}
@@ -192,7 +123,12 @@
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);
@@ -200,21 +136,8 @@
}
public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
- events.add(flushConnectStats());
- // TODO: migrate DnsEventBatch to IpConnectivityLogClass.DNSLatencies
- }
-
- private IpConnectivityEvent connectStatsProto() {
- // TODO: add transport information
- IpConnectivityEvent ev = new IpConnectivityEvent();
- ev.setConnectStatistics(mConnectStats.toProto());
- return ev;
- }
-
- private IpConnectivityEvent flushConnectStats() {
- IpConnectivityEvent ev = connectStatsProto();
- mConnectStats = makeConnectStats();
- return ev;
+ flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto);
+ flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto);
}
public synchronized void dump(PrintWriter writer) {
@@ -226,18 +149,47 @@
}
public synchronized void list(PrintWriter pw) {
- for (DnsEventBatch batch : mEventBatches.values()) {
- pw.println(batch.toString());
- }
- pw.println(mConnectStats.toString());
+ listEvents(pw, mConnectEvents, (x) -> x);
+ listEvents(pw, mDnsEvents, (x) -> x);
}
public synchronized void listAsProtos(PrintWriter pw) {
- pw.println(connectStatsProto().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) {
+ long transports = getTransports(netId);
+ return new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE);
+ }
+
+ private long getTransports(int netId) {
+ // TODO: directly query ConnectivityService instead of going through Binder interface.
+ NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId));
+ if (nc == null) {
+ return 0;
+ }
+ return BitUtils.packBits(nc.getTransportTypes());
}
private static void maybeLog(String s, Object... args) {
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index d819b96..d11565a 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -45,7 +45,9 @@
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.
@@ -57,7 +59,7 @@
aType(IpReachabilityEvent.class),
anInt(IpReachabilityEvent.NUD_FAILED));
- String want = joinLines(
+ String want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -70,13 +72,13 @@
" if_name: \"\"",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
ev.netId = 123;
ev.transports = 3; // transports have priority for inferrence of link layer
ev.ifname = "wlan0";
- want = joinLines(
+ want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -89,12 +91,12 @@
" if_name: \"\"",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
ev.transports = 1;
ev.ifname = null;
- want = joinLines(
+ want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -107,12 +109,12 @@
" if_name: \"\"",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
ev.transports = 0;
ev.ifname = "not_inferred";
- want = joinLines(
+ want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"not_inferred\"",
@@ -125,11 +127,11 @@
" if_name: \"\"",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
ev.ifname = "bt-pan";
- want = joinLines(
+ want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -142,11 +144,11 @@
" if_name: \"\"",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
ev.ifname = "rmnet_ipa0";
- want = joinLines(
+ want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -159,11 +161,11 @@
" if_name: \"\"",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
ev.ifname = "wlan0";
- want = joinLines(
+ want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -176,7 +178,7 @@
" if_name: \"\"",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
}
@@ -190,7 +192,7 @@
aBool(true),
aBool(false));
- String want = joinLines(
+ String want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -211,7 +213,7 @@
" transport_types: 3",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
}
@@ -223,7 +225,7 @@
aString("SomeState"),
anInt(192));
- String want = joinLines(
+ String want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -237,7 +239,7 @@
" state_transition: \"SomeState\"",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
}
@@ -248,7 +250,7 @@
aType(DhcpErrorEvent.class),
anInt(DhcpErrorEvent.L4_NOT_UDP));
- String want = joinLines(
+ String want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -262,59 +264,7 @@
" error_code: 50397184",
" >",
">",
- "version: 2");
-
- verifySerialization(want, ev);
- }
-
- @SmallTest
- public void testDnsEventSerialization() {
- ConnectivityMetricsEvent ev = describeIpEvent(
- aType(DnsEvent.class),
- anInt(101),
- aByteArray(b(1), b(1), b(2), b(1), b(1), b(1), b(2), b(2)),
- aByteArray(b(0), b(0), b(22), b(3), b(1), b(0), b(200), b(178)),
- anIntArray(3456, 267, 1230, 45, 2111, 450, 638, 1300));
-
- String want = joinLines(
- "dropped_events: 0",
- "events <",
- " if_name: \"\"",
- " link_layer: 0",
- " network_id: 0",
- " time_ms: 1",
- " transports: 0",
- " dns_lookup_batch <",
- " event_types: 1",
- " event_types: 1",
- " event_types: 2",
- " event_types: 1",
- " event_types: 1",
- " event_types: 1",
- " event_types: 2",
- " event_types: 2",
- " latencies_ms: 3456",
- " latencies_ms: 267",
- " latencies_ms: 1230",
- " latencies_ms: 45",
- " latencies_ms: 2111",
- " latencies_ms: 450",
- " latencies_ms: 638",
- " latencies_ms: 1300",
- " network_id <",
- " network_id: 101",
- " >",
- " return_codes: 0",
- " return_codes: 0",
- " return_codes: 22",
- " return_codes: 3",
- " return_codes: 1",
- " return_codes: 0",
- " return_codes: 200",
- " return_codes: 178",
- " >",
- ">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
}
@@ -326,7 +276,7 @@
anInt(IpManagerEvent.PROVISIONING_OK),
aLong(5678));
- String want = joinLines(
+ String want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -340,7 +290,7 @@
" latency_ms: 5678",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
}
@@ -351,7 +301,7 @@
aType(IpReachabilityEvent.class),
anInt(IpReachabilityEvent.NUD_FAILED));
- String want = joinLines(
+ String want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -364,7 +314,7 @@
" if_name: \"\"",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
}
@@ -377,7 +327,7 @@
anInt(5),
aLong(20410));
- String want = joinLines(
+ String want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -393,7 +343,7 @@
" >",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
}
@@ -406,7 +356,7 @@
anInt(ValidationProbeEvent.PROBE_HTTP),
anInt(204));
- String want = joinLines(
+ String want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -420,7 +370,7 @@
" probe_type: 1",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
}
@@ -436,7 +386,7 @@
anInt(2048),
anInt(3));
- String want = joinLines(
+ String want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -454,7 +404,7 @@
" program_length: 2048",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
}
@@ -474,7 +424,7 @@
anInt(3),
anInt(2048));
- String want = joinLines(
+ String want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -495,7 +445,7 @@
" zero_lifetime_ras: 1",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
}
@@ -511,7 +461,7 @@
aLong(1000),
aLong(-1));
- String want = joinLines(
+ String want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
@@ -528,28 +478,20 @@
" router_lifetime: 2000",
" >",
">",
- "version: 2");
+ "version: 2\n");
verifySerialization(want, ev);
}
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) {
fail(e.toString());
}
}
-
- static String joinLines(String ... elems) {
- StringBuilder b = new StringBuilder();
- for (String s : elems) {
- b.append(s);
- b.append("\n");
- }
- return b.toString();
- }
}
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 68786d0..e01469b 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -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.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.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 @@
assertEventsEqual(expectedEvent(3), got.get(2));
}
- @SmallTest
+ @Test
public void testLoggingEventsWithMultipleCallers() throws Exception {
IpConnectivityLog logger = new IpConnectivityLog(mMockService);
@@ -91,7 +115,7 @@
}.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 @@
}
}
- @SmallTest
+ @Test
public void testBufferFlushing() {
String output1 = getdump("flush");
assertEquals("", output1);
@@ -115,7 +139,7 @@
assertEquals("", output3);
}
- @SmallTest
+ @Test
public void testRateLimiting() {
final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
final ApfProgramEvent ev = new ApfProgramEvent();
@@ -137,11 +161,19 @@
assertEquals("", output2);
}
- @SmallTest
- 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;
@@ -177,7 +209,22 @@
logger.log(ev);
}
- String want = joinLines(
+ // 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 <",
" if_name: \"\"",
@@ -279,7 +326,71 @@
" router_lifetime: 2000",
" >",
">",
- "version: 2");
+ "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"));
}
@@ -291,6 +402,14 @@
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);
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 0ab4406..f98ab3d 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -16,190 +16,182 @@
package com.android.server.connectivity;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.Network;
-import android.net.metrics.ConnectStats;
-import android.net.metrics.DnsEvent;
-import android.net.metrics.INetdEventListener;
-import android.net.metrics.IpConnectivityLog;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.system.OsConstants;
-import android.test.suitebuilder.annotation.SmallTest;
-import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
-import java.io.FileOutputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-import java.util.OptionalInt;
-import java.util.stream.IntStream;
-import junit.framework.TestCase;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertTrue;
+import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
+import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
-public class NetdEventListenerServiceTest extends TestCase {
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.support.test.runner.AndroidJUnit4;
+import android.system.OsConstants;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.DNSLookupBatch;
+import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
- // TODO: read from NetdEventListenerService after this constant is read from system property
- static final int BATCH_SIZE = 100;
- static final int EVENT_TYPE = INetdEventListener.EVENT_GETADDRINFO;
- // TODO: read from INetdEventListener
- static final int RETURN_CODE = 1;
-
- static final byte[] EVENT_TYPES = new byte[BATCH_SIZE];
- static final byte[] RETURN_CODES = new byte[BATCH_SIZE];
- static final int[] LATENCIES = new int[BATCH_SIZE];
- static {
- for (int i = 0; i < BATCH_SIZE; i++) {
- EVENT_TYPES[i] = EVENT_TYPE;
- RETURN_CODES[i] = RETURN_CODE;
- LATENCIES[i] = i;
- }
- }
-
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetdEventListenerServiceTest {
private static final String EXAMPLE_IPV4 = "192.0.2.1";
private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
NetdEventListenerService mNetdEventListenerService;
+ ConnectivityManager mCm;
- @Mock ConnectivityManager mCm;
- @Mock IpConnectivityLog mLog;
- ArgumentCaptor<NetworkCallback> mCallbackCaptor;
- ArgumentCaptor<DnsEvent> mDnsEvCaptor;
-
+ @Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
- mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class);
- mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class);
- mNetdEventListenerService = new NetdEventListenerService(mCm, mLog);
+ NetworkCapabilities ncWifi = new NetworkCapabilities();
+ NetworkCapabilities ncCell = new NetworkCapabilities();
+ ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+ ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
- verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture());
+ mCm = mock(ConnectivityManager.class);
+ when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
+ when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
+
+ mNetdEventListenerService = new NetdEventListenerService(mCm);
}
- @SmallTest
- public void testOneDnsBatch() throws Exception {
- log(105, LATENCIES);
- log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event
+ @Test
+ public void testDnsLogging() throws Exception {
+ asyncDump(100);
- verifyLoggedDnsEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
+ dnsEvent(100, EVENT_GETADDRINFO, 0, 3456);
+ dnsEvent(100, EVENT_GETADDRINFO, 0, 267);
+ dnsEvent(100, EVENT_GETHOSTBYNAME, 22, 1230);
+ dnsEvent(100, EVENT_GETADDRINFO, 3, 45);
+ dnsEvent(100, EVENT_GETADDRINFO, 1, 2111);
+ dnsEvent(100, EVENT_GETADDRINFO, 0, 450);
+ dnsEvent(100, EVENT_GETHOSTBYNAME, 200, 638);
+ dnsEvent(100, EVENT_GETHOSTBYNAME, 178, 1300);
+ dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
+ dnsEvent(101, EVENT_GETADDRINFO, 0, 78);
+ dnsEvent(101, EVENT_GETADDRINFO, 0, 14);
+ dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 56);
+ dnsEvent(101, EVENT_GETADDRINFO, 0, 78);
+ dnsEvent(101, EVENT_GETADDRINFO, 0, 14);
- log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE));
-
- mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor
- verifyLoggedDnsEvents(
- new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
- new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES));
+ String got = flushStatistics();
+ String want = String.join("\n",
+ "dropped_events: 0",
+ "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",
+ " event_types: 1",
+ " event_types: 1",
+ " event_types: 1",
+ " event_types: 2",
+ " event_types: 2",
+ " latencies_ms: 3456",
+ " latencies_ms: 267",
+ " latencies_ms: 1230",
+ " latencies_ms: 45",
+ " latencies_ms: 2111",
+ " latencies_ms: 450",
+ " latencies_ms: 638",
+ " latencies_ms: 1300",
+ " return_codes: 0",
+ " return_codes: 0",
+ " return_codes: 22",
+ " return_codes: 3",
+ " return_codes: 1",
+ " return_codes: 0",
+ " return_codes: 200",
+ " return_codes: 178",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 2",
+ " network_id: 101",
+ " time_ms: 0",
+ " transports: 1",
+ " dns_lookup_batch <",
+ " event_types: 1",
+ " event_types: 1",
+ " event_types: 1",
+ " event_types: 2",
+ " event_types: 1",
+ " event_types: 1",
+ " latencies_ms: 56",
+ " latencies_ms: 78",
+ " latencies_ms: 14",
+ " latencies_ms: 56",
+ " latencies_ms: 78",
+ " latencies_ms: 14",
+ " return_codes: 0",
+ " return_codes: 0",
+ " return_codes: 0",
+ " return_codes: 0",
+ " return_codes: 0",
+ " return_codes: 0",
+ " >",
+ ">",
+ "version: 2\n");
+ assertEquals(want, got);
}
- @SmallTest
- public void testSeveralDmsBatches() throws Exception {
- log(105, LATENCIES);
- log(106, LATENCIES);
- log(105, LATENCIES);
- log(107, LATENCIES);
-
- verifyLoggedDnsEvents(
- new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
- new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
- new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
- new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
- }
-
- @SmallTest
- public void testDnsBatchAndNetworkLost() throws Exception {
- byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20);
- byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20);
- int[] latencies = Arrays.copyOf(LATENCIES, 20);
-
- log(105, LATENCIES);
- log(105, latencies);
- mCallbackCaptor.getValue().onLost(new Network(105));
- log(105, LATENCIES);
-
- verifyLoggedDnsEvents(
- new DnsEvent(105, eventTypes, returnCodes, latencies),
- new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
- new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
- }
-
- @SmallTest
- public void testConcurrentDnsBatchesAndDumps() throws Exception {
- final long stop = System.currentTimeMillis() + 100;
- final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
- new Thread() {
- public void run() {
- while (System.currentTimeMillis() < stop) {
- mNetdEventListenerService.dump(pw);
- }
- }
- }.start();
-
- logDnsAsync(105, LATENCIES);
- logDnsAsync(106, LATENCIES);
- logDnsAsync(107, LATENCIES);
-
- verifyLoggedDnsEvents(500,
- new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
- new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
- new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
- }
-
- @SmallTest
- public void testConcurrentDnsBatchesAndNetworkLoss() throws Exception {
- logDnsAsync(105, LATENCIES);
- Thread.sleep(10L);
- // call onLost() asynchronously to logDnsAsync's onDnsEvent() calls.
- mCallbackCaptor.getValue().onLost(new Network(105));
-
- // do not verify batch with unpredictable length
- verify(mLog, timeout(500).times(1)).log(any(Parcelable.class));
- }
-
- @SmallTest
+ @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,113 +201,124 @@
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());
}
});
}
- void log(int netId, int[] latencies) {
- try {
- for (int l : latencies) {
- mNetdEventListenerService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l, null, null,
- 0, 0);
+ void dnsEvent(int netId, int type, int result, int latency) throws Exception {
+ mNetdEventListenerService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
+ }
+
+ void asyncDump(long durationMs) throws Exception {
+ final long stop = System.currentTimeMillis() + durationMs;
+ final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
+ new Thread(() -> {
+ while (System.currentTimeMillis() < stop) {
+ mNetdEventListenerService.dump(pw);
}
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
+ }).start();
}
- void logDnsAsync(int netId, int[] latencies) {
- new Thread(() -> log(netId, latencies)).start();
- }
+ // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
+ String flushStatistics() throws Exception {
+ IpConnectivityMetrics metricsService =
+ new IpConnectivityMetrics(mock(Context.class), (ctx) -> 2000);
+ metricsService.mNetdListener = mNetdEventListenerService;
- void verifyLoggedDnsEvents(DnsEvent... expected) {
- verifyLoggedDnsEvents(0, expected);
- }
-
- void verifyLoggedDnsEvents(int wait, DnsEvent... expectedEvents) {
- verify(mLog, timeout(wait).times(expectedEvents.length)).log(mDnsEvCaptor.capture());
- for (DnsEvent got : mDnsEvCaptor.getAllValues()) {
- OptionalInt index = IntStream.range(0, expectedEvents.length)
- .filter(i -> dnsEventsEqual(expectedEvents[i], got))
- .findFirst();
- // Don't match same expected event more than once.
- index.ifPresent(i -> expectedEvents[i] = null);
- assertTrue(index.isPresent());
- }
- }
-
- /** equality function for DnsEvent to avoid overriding equals() and hashCode(). */
- static boolean dnsEventsEqual(DnsEvent expected, DnsEvent got) {
- return (expected == got) || ((expected != null) && (got != null)
- && (expected.netId == got.netId)
- && Arrays.equals(expected.eventTypes, got.eventTypes)
- && Arrays.equals(expected.returnCodes, got.returnCodes)
- && Arrays.equals(expected.latenciesMs, got.latenciesMs));
- }
-
- static void verifyConnectEvent(String expected, IpConnectivityEvent got) {
- try {
- Arrays.sort(got.getConnectStatistics().latenciesMs);
- Arrays.sort(got.getConnectStatistics().errnosCounters,
+ StringWriter buffer = new StringWriter();
+ PrintWriter writer = new PrintWriter(buffer);
+ metricsService.impl.dump(null, writer, new String[]{"flush"});
+ byte[] bytes = Base64.decode(buffer.toString(), Base64.DEFAULT);
+ 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));
- assertEquals(expected, got.toString());
- } catch (Exception e) {
- fail(e.toString());
}
+ return log.toString();
}
}