Merge changes I1450c126,I8926a922,I54403377,Idf216797 into nyc-mr2-dev

* changes:
  DO NOT MERGE IP Connectivity metrics: add connect() statistics
  DO NOT MERGE Fix IP connectivity metrics tests
  DO NOT MERGE IP connectivity metrics: introduce ConnectStatistics and DNS statistics
  DO NOT MERGE IP connectivity metrics: add transport field.
This commit is contained in:
Hugo Benichi
2016-12-19 08:31:10 +00:00
committed by Android (Google) Code Review
9 changed files with 500 additions and 108 deletions

View File

@@ -33,6 +33,8 @@ import static com.android.internal.util.Preconditions.checkArgumentPositive;
* The available amount of tokens is computed lazily when the bucket state is inspected. * The available amount of tokens is computed lazily when the bucket state is inspected.
* Therefore it is purely synchronous and does not involve any asynchronous activity. * Therefore it is purely synchronous and does not involve any asynchronous activity.
* It is not synchronized in any way and not a thread-safe object. * It is not synchronized in any way and not a thread-safe object.
*
* {@hide}
*/ */
public class TokenBucket { public class TokenBucket {

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.connectivity;
import android.system.OsConstants;
import android.util.IntArray;
import android.util.SparseIntArray;
import com.android.internal.util.TokenBucket;
import com.android.server.connectivity.metrics.IpConnectivityLogClass.ConnectStatistics;
import com.android.server.connectivity.metrics.IpConnectivityLogClass.Pair;
/**
* A class that aggregates connect() statistics and helps build
* IpConnectivityLogClass.ConnectStatistics instances.
*
* {@hide}
*/
public class ConnectStats {
private final static int EALREADY = OsConstants.EALREADY;
private final static int EINPROGRESS = OsConstants.EINPROGRESS;
/** How many events resulted in a given errno. */
private final SparseIntArray mErrnos = new SparseIntArray();
/** Latencies of blocking connects. TODO: add non-blocking connects latencies. */
private final IntArray mLatencies = new IntArray();
/** TokenBucket for rate limiting latency recording. */
private final TokenBucket mLatencyTb;
/** Maximum number of latency values recorded. */
private final int mMaxLatencyRecords;
/** Total count of successful connects. */
private int mConnectCount = 0;
/** Total count of successful connects with IPv6 socket address. */
private int mIpv6ConnectCount = 0;
public ConnectStats(TokenBucket tb, int maxLatencyRecords) {
mLatencyTb = tb;
mMaxLatencyRecords = maxLatencyRecords;
}
public ConnectStatistics toProto() {
ConnectStatistics stats = new ConnectStatistics();
stats.connectCount = mConnectCount;
stats.ipv6AddrCount = mIpv6ConnectCount;
stats.latenciesMs = mLatencies.toArray();
stats.errnosCounters = toPairArrays(mErrnos);
return stats;
}
public void addEvent(int errno, int latencyMs, String ipAddr) {
if (isSuccess(errno)) {
countConnect(ipAddr);
countLatency(errno, latencyMs);
} else {
countError(errno);
}
}
private void countConnect(String ipAddr) {
mConnectCount++;
if (isIPv6(ipAddr)) mIpv6ConnectCount++;
}
private void countLatency(int errno, int ms) {
if (isNonBlocking(errno)) {
// Ignore connect() on non-blocking sockets
return;
}
if (!mLatencyTb.get()) {
// Rate limited
return;
}
if (mLatencies.size() >= mMaxLatencyRecords) {
// Hard limit the total number of latency measurements.
return;
}
mLatencies.add(ms);
}
private void countError(int errno) {
final int newcount = mErrnos.get(errno, 0) + 1;
mErrnos.put(errno, newcount);
}
private static boolean isSuccess(int errno) {
return (errno == 0) || isNonBlocking(errno);
}
private static boolean isNonBlocking(int errno) {
// On non-blocking TCP sockets, connect() immediately returns EINPROGRESS.
// On non-blocking TCP sockets that are connecting, connect() immediately returns EALREADY.
return (errno == EINPROGRESS) || (errno == EALREADY);
}
private static boolean isIPv6(String ipAddr) {
return ipAddr.contains(":");
}
private static Pair[] toPairArrays(SparseIntArray counts) {
final int s = counts.size();
Pair[] pairs = new Pair[s];
for (int i = 0; i < s; i++) {
Pair p = new Pair();
p.key = counts.keyAt(i);
p.value = counts.valueAt(i);
pairs[i] = p;
}
return pairs;
}
}

View File

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

View File

@@ -36,14 +36,14 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.TokenBucket; import com.android.internal.util.TokenBucket;
import com.android.server.SystemService; import com.android.server.SystemService;
import com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.function.ToIntFunction; import java.util.function.ToIntFunction;
import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent;
/** {@hide} */ /** {@hide} */
final public class IpConnectivityMetrics extends SystemService { final public class IpConnectivityMetrics extends SystemService {
private static final String TAG = IpConnectivityMetrics.class.getSimpleName(); private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
@@ -63,6 +63,8 @@ final public class IpConnectivityMetrics extends SystemService {
// Maximum size of the event buffer. // Maximum size of the event buffer.
private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10; private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;
private static final int MAXIMUM_CONNECT_LATENCY_RECORDS = 20000;
private static final int ERROR_RATE_LIMITED = -1; private static final int ERROR_RATE_LIMITED = -1;
// Lock ensuring that concurrent manipulations of the event buffer are correct. // Lock ensuring that concurrent manipulations of the event buffer are correct.
@@ -160,9 +162,15 @@ final public class IpConnectivityMetrics extends SystemService {
initBuffer(); initBuffer();
} }
final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events);
if (mNetdListener != null) {
mNetdListener.flushStatistics(protoEvents);
}
final byte[] data; final byte[] data;
try { try {
data = IpConnectivityEventBuilder.serialize(dropped, events); data = IpConnectivityEventBuilder.serialize(dropped, protoEvents);
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "could not serialize events", e); Log.e(TAG, "could not serialize events", e);
return ""; return "";

View File

@@ -19,25 +19,27 @@ package com.android.server.connectivity;
import android.content.Context; import android.content.Context;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.INetdEventCallback; import android.net.INetdEventCallback;
import android.net.Network;
import android.net.NetworkRequest; import android.net.NetworkRequest;
import android.net.metrics.DnsEvent; import android.net.metrics.DnsEvent;
import android.net.metrics.INetdEventListener; import android.net.metrics.INetdEventListener;
import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpConnectivityLog;
import android.os.RemoteException; import android.os.RemoteException;
import android.text.format.DateUtils;
import android.util.Log; import android.util.Log;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.TokenBucket;
import com.android.server.connectivity.metrics.IpConnectivityLogClass.ConnectStatistics;
import com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
/** /**
* Implementation of the INetdEventListener interface. * Implementation of the INetdEventListener interface.
*/ */
@@ -52,6 +54,12 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
// TODO: read this constant from system property // TODO: read this constant from system property
private static final int MAX_LOOKUPS_PER_DNS_EVENT = 100; private static final int MAX_LOOKUPS_PER_DNS_EVENT = 100;
// Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum
// bursts of 5000 measurements.
private static final int CONNECT_LATENCY_BURST_LIMIT = 5000;
private static final int CONNECT_LATENCY_FILL_RATE = 15 * (int) DateUtils.SECOND_IN_MILLIS;
private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
// Stores the results of a number of consecutive DNS lookups on the same network. // 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 // This class is not thread-safe and it is the responsibility of the service to call its methods
// on one thread at a time. // on one thread at a time.
@@ -121,6 +129,12 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
} }
}; };
@GuardedBy("this")
private final TokenBucket mConnectTb =
new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT);
@GuardedBy("this")
private ConnectStats mConnectStats = makeConnectStats();
// Callback should only be registered/unregistered when logging is being enabled/disabled in DPM // 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. // by the device owner. It's DevicePolicyManager's responsibility to ensure that.
@GuardedBy("this") @GuardedBy("this")
@@ -175,13 +189,28 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
// This method must not block or perform long-running operations. // This method must not block or perform long-running operations.
public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port,
int uid) throws RemoteException { int uid) throws RemoteException {
maybeVerboseLog("onConnectEvent(%d, %d, %dms)", netId, error, latencyMs); maybeVerboseLog("onConnectEvent(%d, %d)", netId, latencyMs);
mConnectStats.addEvent(error, latencyMs, ipAddr);
if (mNetdEventCallback != null) { if (mNetdEventCallback != null) {
mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid); mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid);
} }
} }
public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
events.add(flushConnectStats());
// TODO: migrate DnsEventBatch to IpConnectivityLogClass.DNSLatencies
}
private IpConnectivityEvent flushConnectStats() {
IpConnectivityEvent ev = new IpConnectivityEvent();
ev.connectStatistics = mConnectStats.toProto();
// TODO: add transport information
mConnectStats = makeConnectStats();
return ev;
}
public synchronized void dump(PrintWriter writer) { public synchronized void dump(PrintWriter writer) {
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
pw.println(TAG + ":"); pw.println(TAG + ":");
@@ -189,9 +218,14 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
for (DnsEventBatch batch : mEventBatches.values()) { for (DnsEventBatch batch : mEventBatches.values()) {
pw.println(batch.toString()); pw.println(batch.toString());
} }
// TODO: also dump ConnectStats
pw.decreaseIndent(); pw.decreaseIndent();
} }
private ConnectStats makeConnectStats() {
return new ConnectStats(mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS);
}
private static void maybeLog(String s, Object... args) { private static void maybeLog(String s, Object... args) {
if (DBG) Log.d(TAG, String.format(s, args)); if (DBG) Log.d(TAG, String.format(s, args));
} }

View File

@@ -17,6 +17,22 @@ message NetworkId {
optional int32 network_id = 1; optional int32 network_id = 1;
}; };
// Transport describes a physical technology used by a network. It is a subset
// of the TRANSPORT_* constants defined in android.net.NetworkCapabilities.
enum Transport {
UNKNOWN = 0;
BLUETOOTH = 1;
CELLULAR = 2;
ETHERNET = 3;
WIFI = 4;
};
// A pair of (key, value) integers for describing histogram-like statistics.
message Pair {
optional int32 key = 1;
optional int32 value = 2;
};
// Logs changes in the system default network. Changes can be 1) acquiring a // Logs changes in the system default network. Changes can be 1) acquiring a
// default network with no previous default, 2) a switch of the system default // default network with no previous default, 2) a switch of the system default
// network to a new default network, 3) a loss of the system default network. // network to a new default network, 3) a loss of the system default network.
@@ -49,7 +65,8 @@ message DefaultNetworkEvent {
// This message is associated to android.net.metrics.IpReachabilityEvent. // This message is associated to android.net.metrics.IpReachabilityEvent.
message IpReachabilityEvent { message IpReachabilityEvent {
// The interface name (wlan, rmnet, lo, ...) on which the probe was sent. // The interface name (wlan, rmnet, lo, ...) on which the probe was sent.
optional string if_name = 1; // Deprecated since version 2, replaced by transport field.
optional string if_name = 1 [deprecated = true];
// The event type code of the probe, represented by constants defined in // The event type code of the probe, represented by constants defined in
// android.net.metrics.IpReachabilityEvent. // android.net.metrics.IpReachabilityEvent.
@@ -93,6 +110,7 @@ message ValidationProbeEvent {
// Logs DNS lookup latencies. Repeated fields must have the same length. // Logs DNS lookup latencies. Repeated fields must have the same length.
// This message is associated to android.net.metrics.DnsEvent. // This message is associated to android.net.metrics.DnsEvent.
// Deprecated since version 2.
message DNSLookupBatch { message DNSLookupBatch {
// The id of the network on which the DNS lookups took place. // The id of the network on which the DNS lookups took place.
optional NetworkId network_id = 1; optional NetworkId network_id = 1;
@@ -107,13 +125,62 @@ message DNSLookupBatch {
repeated int32 latencies_ms = 4; repeated int32 latencies_ms = 4;
}; };
// Represents a collections of DNS lookup latencies and counters for a
// particular combination of DNS query type and return code.
// Since version 2.
message DNSLatencies {
// The type of the DNS lookups, as defined in android.net.metrics.DnsEvent.
// Acts as a key for a set of DNS query results.
// Possible values are: 0 for getaddrinfo, 1 for gethostbyname.
optional int32 type = 1;
// The return value of the DNS resolver for the DNS lookups.
// Acts as a key for a set of DNS query results.
// Possible values are: 0 for success, or errno code for failures.
optional int32 return_code = 2;
// The number of query operations recorded.
optional int32 query_count = 3;
// The number of query operations returning A IPv4 records.
optional int32 a_count = 4;
// The number of query operations returning AAAA IPv6 records.
optional int32 aaaa_count = 5;
// The time it took for each DNS lookup to complete. The number of repeated
// values can be less than query_count in case of event rate-limiting.
repeated int32 latencies_ms = 6;
};
// Represents latency and errno statistics of the connect() system call.
// Since version 2.
message ConnectStatistics {
// The number of connect() operations recorded.
optional int32 connect_count = 1;
// The number of connect() operations with IPv6 socket address.
optional int32 ipv6_addr_count = 2;
// The time it took for each successful connect() operation to complete.
// The number of repeated values can be less than connect_count in case of
// event rate-limiting.
repeated int32 latencies_ms = 3;
// Counts of all error values returned by failed connect() operations.
// The Pair key field is the errno code. The Pair value field is the count
// for that errno code.
repeated Pair errnos_counters = 4;
};
// Represents a DHCP event on a single interface, which can be a DHCPClient // Represents a DHCP event on a single interface, which can be a DHCPClient
// state transition or a response packet parsing error. // state transition or a response packet parsing error.
// This message is associated to android.net.metrics.DhcpClientEvent and // This message is associated to android.net.metrics.DhcpClientEvent and
// android.net.metrics.DhcpErrorEvent. // android.net.metrics.DhcpErrorEvent.
message DHCPEvent { message DHCPEvent {
// The interface name (wlan, rmnet, lo, ...) on which the event happened. // The interface name (wlan, rmnet, lo, ...) on which the event happened.
optional string if_name = 1; // Deprecated since version 2, replaced by transport field.
optional string if_name = 1 [deprecated = true];
oneof value { oneof value {
// The name of a state in the DhcpClient state machine, represented by // The name of a state in the DhcpClient state machine, represented by
@@ -217,7 +284,8 @@ message RaEvent {
// This message is associated to android.net.metrics.IpManagerEvent. // This message is associated to android.net.metrics.IpManagerEvent.
message IpProvisioningEvent { message IpProvisioningEvent {
// The interface name (wlan, rmnet, lo, ...) on which the probe was sent. // The interface name (wlan, rmnet, lo, ...) on which the probe was sent.
optional string if_name = 1; // Deprecated since version 2, replaced by transport field.
optional string if_name = 1 [deprecated = true];
// The code of the IP provisioning event, represented by constants defined in // The code of the IP provisioning event, represented by constants defined in
// android.net.metrics.IpManagerEvent. // android.net.metrics.IpManagerEvent.
@@ -228,11 +296,15 @@ message IpProvisioningEvent {
} }
// Represents one of the IP connectivity event defined in this file. // Represents one of the IP connectivity event defined in this file.
// Next tag: 12 // Next tag: 15
message IpConnectivityEvent { message IpConnectivityEvent {
// Time in ms when the event was recorded. // Time in ms when the event was recorded.
optional int64 time_ms = 1; optional int64 time_ms = 1;
// Physical transport of the network on which the event happened.
// Since version 2.
optional Transport transport = 12;
// Event type. // Event type.
oneof event { oneof event {
@@ -246,7 +318,14 @@ message IpConnectivityEvent {
NetworkEvent network_event = 4; NetworkEvent network_event = 4;
// A batch of DNS lookups. // A batch of DNS lookups.
DNSLookupBatch dns_lookup_batch = 5; // Deprecated in the nyc-mr2 release since version 2, and replaced by dns_latencies.
DNSLookupBatch dns_lookup_batch = 5 [deprecated = true];
// DNS lookup latency statistics.
DNSLatencies dns_latencies = 13;
// Connect latency and errno statistics.
ConnectStatistics connect_statistics = 14;
// A DHCP client event or DHCP receive error. // A DHCP client event or DHCP receive error.
DHCPEvent dhcp_event = 6; DHCPEvent dhcp_event = 6;
@@ -277,9 +356,9 @@ message IpConnectivityLog {
optional int32 dropped_events = 2; optional int32 dropped_events = 2;
// The version number of the metrics events being collected. // The version number of the metrics events being collected.
// nyc-dev: not populated, implicitly 0 // nyc-dev: not populated, implicitly 0.
// nyc-dr1: not populated, implicitly 1 (sailfish and marlin only) // nyc-dr1: not populated, implicitly 1 (sailfish and marlin only).
// nyc-mr1: not populated, implicitly 1 // nyc-mr1: not populated, implicitly 1.
// nyc-mr2: 2 // nyc-mr2: 2.
optional int32 version = 3; optional int32 version = 3;
}; };

View File

@@ -16,6 +16,17 @@
package com.android.server.connectivity; package com.android.server.connectivity;
import static com.android.server.connectivity.MetricsTestUtil.aBool;
import static com.android.server.connectivity.MetricsTestUtil.aByteArray;
import static com.android.server.connectivity.MetricsTestUtil.aLong;
import static com.android.server.connectivity.MetricsTestUtil.aString;
import static com.android.server.connectivity.MetricsTestUtil.aType;
import static com.android.server.connectivity.MetricsTestUtil.anInt;
import static com.android.server.connectivity.MetricsTestUtil.anIntArray;
import static com.android.server.connectivity.MetricsTestUtil.b;
import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent;
import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityLog;
import android.net.ConnectivityMetricsEvent; import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats; import android.net.metrics.ApfStats;
@@ -28,24 +39,13 @@ import android.net.metrics.IpReachabilityEvent;
import android.net.metrics.NetworkEvent; import android.net.metrics.NetworkEvent;
import android.net.metrics.RaEvent; import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent; import android.net.metrics.ValidationProbeEvent;
import com.google.protobuf.nano.MessageNano; import android.test.suitebuilder.annotation.SmallTest;
import java.util.Arrays; import java.util.Arrays;
import junit.framework.TestCase; import junit.framework.TestCase;
import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityLog;
import static com.android.server.connectivity.MetricsTestUtil.aBool;
import static com.android.server.connectivity.MetricsTestUtil.aByteArray;
import static com.android.server.connectivity.MetricsTestUtil.aLong;
import static com.android.server.connectivity.MetricsTestUtil.aString;
import static com.android.server.connectivity.MetricsTestUtil.aType;
import static com.android.server.connectivity.MetricsTestUtil.anInt;
import static com.android.server.connectivity.MetricsTestUtil.anIntArray;
import static com.android.server.connectivity.MetricsTestUtil.b;
import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent;
import static com.android.server.connectivity.MetricsTestUtil.ipEv;
public class IpConnectivityEventBuilderTest extends TestCase { public class IpConnectivityEventBuilderTest extends TestCase {
@SmallTest
public void testDefaultNetworkEventSerialization() { public void testDefaultNetworkEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent( ConnectivityMetricsEvent ev = describeIpEvent(
aType(DefaultNetworkEvent.class), aType(DefaultNetworkEvent.class),
@@ -58,6 +58,8 @@ public class IpConnectivityEventBuilderTest extends TestCase {
String want = joinLines( String want = joinLines(
"dropped_events: 0", "dropped_events: 0",
"events <", "events <",
" time_ms: 1",
" transport: 0",
" default_network_event <", " default_network_event <",
" network_id <", " network_id <",
" network_id: 102", " network_id: 102",
@@ -70,13 +72,13 @@ public class IpConnectivityEventBuilderTest extends TestCase {
" transport_types: 2", " transport_types: 2",
" transport_types: 3", " transport_types: 3",
" >", " >",
" time_ms: 1",
">", ">",
"version: 2"); "version: 2");
verifySerialization(want, ev); verifySerialization(want, ev);
} }
@SmallTest
public void testDhcpClientEventSerialization() { public void testDhcpClientEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent( ConnectivityMetricsEvent ev = describeIpEvent(
aType(DhcpClientEvent.class), aType(DhcpClientEvent.class),
@@ -87,19 +89,20 @@ public class IpConnectivityEventBuilderTest extends TestCase {
String want = joinLines( String want = joinLines(
"dropped_events: 0", "dropped_events: 0",
"events <", "events <",
" time_ms: 1",
" transport: 0",
" dhcp_event <", " dhcp_event <",
" duration_ms: 192", " duration_ms: 192",
" error_code: 0",
" if_name: \"wlan0\"", " if_name: \"wlan0\"",
" state_transition: \"SomeState\"", " state_transition: \"SomeState\"",
" >", " >",
" time_ms: 1",
">", ">",
"version: 2"); "version: 2");
verifySerialization(want, ev); verifySerialization(want, ev);
} }
@SmallTest
public void testDhcpErrorEventSerialization() { public void testDhcpErrorEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent( ConnectivityMetricsEvent ev = describeIpEvent(
aType(DhcpErrorEvent.class), aType(DhcpErrorEvent.class),
@@ -109,19 +112,20 @@ public class IpConnectivityEventBuilderTest extends TestCase {
String want = joinLines( String want = joinLines(
"dropped_events: 0", "dropped_events: 0",
"events <", "events <",
" time_ms: 1",
" transport: 0",
" dhcp_event <", " dhcp_event <",
" duration_ms: 0", " duration_ms: 0",
" error_code: 50397184",
" if_name: \"wlan0\"", " if_name: \"wlan0\"",
" state_transition: \"\"", " error_code: 50397184",
" >", " >",
" time_ms: 1",
">", ">",
"version: 2"); "version: 2");
verifySerialization(want, ev); verifySerialization(want, ev);
} }
@SmallTest
public void testDnsEventSerialization() { public void testDnsEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent( ConnectivityMetricsEvent ev = describeIpEvent(
aType(DnsEvent.class), aType(DnsEvent.class),
@@ -133,6 +137,8 @@ public class IpConnectivityEventBuilderTest extends TestCase {
String want = joinLines( String want = joinLines(
"dropped_events: 0", "dropped_events: 0",
"events <", "events <",
" time_ms: 1",
" transport: 0",
" dns_lookup_batch <", " dns_lookup_batch <",
" event_types: 1", " event_types: 1",
" event_types: 1", " event_types: 1",
@@ -162,13 +168,13 @@ public class IpConnectivityEventBuilderTest extends TestCase {
" return_codes: 200", " return_codes: 200",
" return_codes: 178", " return_codes: 178",
" >", " >",
" time_ms: 1",
">", ">",
"version: 2"); "version: 2");
verifySerialization(want, ev); verifySerialization(want, ev);
} }
@SmallTest
public void testIpManagerEventSerialization() { public void testIpManagerEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent( ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpManagerEvent.class), aType(IpManagerEvent.class),
@@ -179,18 +185,20 @@ public class IpConnectivityEventBuilderTest extends TestCase {
String want = joinLines( String want = joinLines(
"dropped_events: 0", "dropped_events: 0",
"events <", "events <",
" time_ms: 1",
" transport: 0",
" ip_provisioning_event <", " ip_provisioning_event <",
" event_type: 1", " event_type: 1",
" if_name: \"wlan0\"", " if_name: \"wlan0\"",
" latency_ms: 5678", " latency_ms: 5678",
" >", " >",
" time_ms: 1",
">", ">",
"version: 2"); "version: 2");
verifySerialization(want, ev); verifySerialization(want, ev);
} }
@SmallTest
public void testIpReachabilityEventSerialization() { public void testIpReachabilityEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent( ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpReachabilityEvent.class), aType(IpReachabilityEvent.class),
@@ -200,17 +208,19 @@ public class IpConnectivityEventBuilderTest extends TestCase {
String want = joinLines( String want = joinLines(
"dropped_events: 0", "dropped_events: 0",
"events <", "events <",
" time_ms: 1",
" transport: 0",
" ip_reachability_event <", " ip_reachability_event <",
" event_type: 512", " event_type: 512",
" if_name: \"wlan0\"", " if_name: \"wlan0\"",
" >", " >",
" time_ms: 1",
">", ">",
"version: 2"); "version: 2");
verifySerialization(want, ev); verifySerialization(want, ev);
} }
@SmallTest
public void testNetworkEventSerialization() { public void testNetworkEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent( ConnectivityMetricsEvent ev = describeIpEvent(
aType(NetworkEvent.class), aType(NetworkEvent.class),
@@ -221,6 +231,8 @@ public class IpConnectivityEventBuilderTest extends TestCase {
String want = joinLines( String want = joinLines(
"dropped_events: 0", "dropped_events: 0",
"events <", "events <",
" time_ms: 1",
" transport: 0",
" network_event <", " network_event <",
" event_type: 5", " event_type: 5",
" latency_ms: 20410", " latency_ms: 20410",
@@ -228,13 +240,13 @@ public class IpConnectivityEventBuilderTest extends TestCase {
" network_id: 100", " network_id: 100",
" >", " >",
" >", " >",
" time_ms: 1",
">", ">",
"version: 2"); "version: 2");
verifySerialization(want, ev); verifySerialization(want, ev);
} }
@SmallTest
public void testValidationProbeEventSerialization() { public void testValidationProbeEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent( ConnectivityMetricsEvent ev = describeIpEvent(
aType(ValidationProbeEvent.class), aType(ValidationProbeEvent.class),
@@ -247,6 +259,7 @@ public class IpConnectivityEventBuilderTest extends TestCase {
"dropped_events: 0", "dropped_events: 0",
"events <", "events <",
" time_ms: 1", " time_ms: 1",
" transport: 0",
" validation_probe_event <", " validation_probe_event <",
" latency_ms: 40730", " latency_ms: 40730",
" network_id <", " network_id <",
@@ -261,6 +274,7 @@ public class IpConnectivityEventBuilderTest extends TestCase {
verifySerialization(want, ev); verifySerialization(want, ev);
} }
@SmallTest
public void testApfProgramEventSerialization() { public void testApfProgramEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent( ConnectivityMetricsEvent ev = describeIpEvent(
aType(ApfProgramEvent.class), aType(ApfProgramEvent.class),
@@ -273,6 +287,8 @@ public class IpConnectivityEventBuilderTest extends TestCase {
String want = joinLines( String want = joinLines(
"dropped_events: 0", "dropped_events: 0",
"events <", "events <",
" time_ms: 1",
" transport: 0",
" apf_program_event <", " apf_program_event <",
" current_ras: 9", " current_ras: 9",
" drop_multicast: true", " drop_multicast: true",
@@ -281,13 +297,13 @@ public class IpConnectivityEventBuilderTest extends TestCase {
" lifetime: 200", " lifetime: 200",
" program_length: 2048", " program_length: 2048",
" >", " >",
" time_ms: 1",
">", ">",
"version: 2"); "version: 2");
verifySerialization(want, ev); verifySerialization(want, ev);
} }
@SmallTest
public void testApfStatsSerialization() { public void testApfStatsSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent( ConnectivityMetricsEvent ev = describeIpEvent(
aType(ApfStats.class), aType(ApfStats.class),
@@ -303,6 +319,8 @@ public class IpConnectivityEventBuilderTest extends TestCase {
String want = joinLines( String want = joinLines(
"dropped_events: 0", "dropped_events: 0",
"events <", "events <",
" time_ms: 1",
" transport: 0",
" apf_statistics <", " apf_statistics <",
" dropped_ras: 2", " dropped_ras: 2",
" duration_ms: 45000", " duration_ms: 45000",
@@ -313,13 +331,13 @@ public class IpConnectivityEventBuilderTest extends TestCase {
" received_ras: 10", " received_ras: 10",
" zero_lifetime_ras: 1", " zero_lifetime_ras: 1",
" >", " >",
" time_ms: 1",
">", ">",
"version: 2"); "version: 2");
verifySerialization(want, ev); verifySerialization(want, ev);
} }
@SmallTest
public void testRaEventSerialization() { public void testRaEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent( ConnectivityMetricsEvent ev = describeIpEvent(
aType(RaEvent.class), aType(RaEvent.class),
@@ -333,6 +351,8 @@ public class IpConnectivityEventBuilderTest extends TestCase {
String want = joinLines( String want = joinLines(
"dropped_events: 0", "dropped_events: 0",
"events <", "events <",
" time_ms: 1",
" transport: 0",
" ra_event <", " ra_event <",
" dnssl_lifetime: -1", " dnssl_lifetime: -1",
" prefix_preferred_lifetime: 300", " prefix_preferred_lifetime: 300",
@@ -341,7 +361,6 @@ public class IpConnectivityEventBuilderTest extends TestCase {
" route_info_lifetime: -1", " route_info_lifetime: -1",
" router_lifetime: 2000", " router_lifetime: 2000",
" >", " >",
" time_ms: 1",
">", ">",
"version: 2"); "version: 2");
@@ -350,9 +369,9 @@ public class IpConnectivityEventBuilderTest extends TestCase {
static void verifySerialization(String want, ConnectivityMetricsEvent... input) { static void verifySerialization(String want, ConnectivityMetricsEvent... input) {
try { try {
byte[] got = IpConnectivityEventBuilder.serialize(0, Arrays.asList(input)); byte[] got = IpConnectivityEventBuilder.serialize(0,
IpConnectivityLog log = new IpConnectivityLog(); IpConnectivityEventBuilder.toProto(Arrays.asList(input)));
MessageNano.mergeFrom(log, got); IpConnectivityLog log = IpConnectivityLog.parseFrom(got);
assertEquals(want, log.toString()); assertEquals(want, log.toString());
} catch (Exception e) { } catch (Exception e) {
fail(e.toString()); fail(e.toString());

View File

@@ -16,6 +16,9 @@
package com.android.server.connectivity; package com.android.server.connectivity;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.content.Context; import android.content.Context;
import android.net.ConnectivityMetricsEvent; import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics; import android.net.IIpConnectivityMetrics;
@@ -29,9 +32,9 @@ import android.net.metrics.IpReachabilityEvent;
import android.net.metrics.RaEvent; import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent; import android.net.metrics.ValidationProbeEvent;
import android.os.Parcelable; import android.os.Parcelable;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Base64; import android.util.Base64;
import com.android.server.connectivity.metrics.IpConnectivityLogClass; import com.android.server.connectivity.metrics.IpConnectivityLogClass;
import com.google.protobuf.nano.MessageNano;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Collections; import java.util.Collections;
@@ -43,10 +46,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class IpConnectivityMetricsTest extends TestCase { public class IpConnectivityMetricsTest extends TestCase {
static final IpReachabilityEvent FAKE_EV = static final IpReachabilityEvent FAKE_EV =
new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED); new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED);
@@ -61,6 +60,7 @@ public class IpConnectivityMetricsTest extends TestCase {
mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000); mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000);
} }
@SmallTest
public void testLoggingEvents() throws Exception { public void testLoggingEvents() throws Exception {
IpConnectivityLog logger = new IpConnectivityLog(mMockService); IpConnectivityLog logger = new IpConnectivityLog(mMockService);
@@ -74,6 +74,7 @@ public class IpConnectivityMetricsTest extends TestCase {
assertEventsEqual(expectedEvent(3), got.get(2)); assertEventsEqual(expectedEvent(3), got.get(2));
} }
@SmallTest
public void testLoggingEventsWithMultipleCallers() throws Exception { public void testLoggingEventsWithMultipleCallers() throws Exception {
IpConnectivityLog logger = new IpConnectivityLog(mMockService); IpConnectivityLog logger = new IpConnectivityLog(mMockService);
@@ -101,6 +102,7 @@ public class IpConnectivityMetricsTest extends TestCase {
} }
} }
@SmallTest
public void testBufferFlushing() { public void testBufferFlushing() {
String output1 = getdump("flush"); String output1 = getdump("flush");
assertEquals("", output1); assertEquals("", output1);
@@ -113,6 +115,7 @@ public class IpConnectivityMetricsTest extends TestCase {
assertEquals("", output3); assertEquals("", output3);
} }
@SmallTest
public void testRateLimiting() { public void testRateLimiting() {
final IpConnectivityLog logger = new IpConnectivityLog(mService.impl); final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
final ApfProgramEvent ev = new ApfProgramEvent(0, 0, 0, 0, 0); final ApfProgramEvent ev = new ApfProgramEvent(0, 0, 0, 0, 0);
@@ -134,6 +137,7 @@ public class IpConnectivityMetricsTest extends TestCase {
assertEquals("", output2); assertEquals("", output2);
} }
@SmallTest
public void testEndToEndLogging() { public void testEndToEndLogging() {
IpConnectivityLog logger = new IpConnectivityLog(mService.impl); IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
@@ -154,22 +158,25 @@ public class IpConnectivityMetricsTest extends TestCase {
String want = joinLines( String want = joinLines(
"dropped_events: 0", "dropped_events: 0",
"events <", "events <",
" time_ms: 100",
" transport: 0",
" ip_reachability_event <", " ip_reachability_event <",
" event_type: 512", " event_type: 512",
" if_name: \"wlan0\"", " if_name: \"wlan0\"",
" >", " >",
" time_ms: 100",
">", ">",
"events <", "events <",
" time_ms: 200",
" transport: 0",
" dhcp_event <", " dhcp_event <",
" duration_ms: 192", " duration_ms: 192",
" error_code: 0",
" if_name: \"wlan0\"", " if_name: \"wlan0\"",
" state_transition: \"SomeState\"", " state_transition: \"SomeState\"",
" >", " >",
" time_ms: 200",
">", ">",
"events <", "events <",
" time_ms: 300",
" transport: 0",
" default_network_event <", " default_network_event <",
" network_id <", " network_id <",
" network_id: 102", " network_id: 102",
@@ -182,18 +189,19 @@ public class IpConnectivityMetricsTest extends TestCase {
" transport_types: 2", " transport_types: 2",
" transport_types: 3", " transport_types: 3",
" >", " >",
" time_ms: 300",
">", ">",
"events <", "events <",
" time_ms: 400",
" transport: 0",
" ip_provisioning_event <", " ip_provisioning_event <",
" event_type: 1", " event_type: 1",
" if_name: \"wlan0\"", " if_name: \"wlan0\"",
" latency_ms: 5678", " latency_ms: 5678",
" >", " >",
" time_ms: 400",
">", ">",
"events <", "events <",
" time_ms: 500", " time_ms: 500",
" transport: 0",
" validation_probe_event <", " validation_probe_event <",
" latency_ms: 40730", " latency_ms: 40730",
" network_id <", " network_id <",
@@ -204,6 +212,8 @@ public class IpConnectivityMetricsTest extends TestCase {
" >", " >",
">", ">",
"events <", "events <",
" time_ms: 600",
" transport: 0",
" apf_statistics <", " apf_statistics <",
" dropped_ras: 2", " dropped_ras: 2",
" duration_ms: 45000", " duration_ms: 45000",
@@ -214,9 +224,10 @@ public class IpConnectivityMetricsTest extends TestCase {
" received_ras: 10", " received_ras: 10",
" zero_lifetime_ras: 1", " zero_lifetime_ras: 1",
" >", " >",
" time_ms: 600",
">", ">",
"events <", "events <",
" time_ms: 700",
" transport: 0",
" ra_event <", " ra_event <",
" dnssl_lifetime: -1", " dnssl_lifetime: -1",
" prefix_preferred_lifetime: 300", " prefix_preferred_lifetime: 300",
@@ -225,7 +236,6 @@ public class IpConnectivityMetricsTest extends TestCase {
" route_info_lifetime: -1", " route_info_lifetime: -1",
" router_lifetime: 2000", " router_lifetime: 2000",
" >", " >",
" time_ms: 700",
">", ">",
"version: 2"); "version: 2");
@@ -254,8 +264,7 @@ public class IpConnectivityMetricsTest extends TestCase {
try { try {
byte[] got = Base64.decode(output, Base64.DEFAULT); byte[] got = Base64.decode(output, Base64.DEFAULT);
IpConnectivityLogClass.IpConnectivityLog log = IpConnectivityLogClass.IpConnectivityLog log =
new IpConnectivityLogClass.IpConnectivityLog(); IpConnectivityLogClass.IpConnectivityLog.parseFrom(got);
MessageNano.mergeFrom(log, got);
assertEquals(want, log.toString()); assertEquals(want, log.toString());
} catch (Exception e) { } catch (Exception e) {
fail(e.toString()); fail(e.toString());
@@ -283,10 +292,5 @@ public class IpConnectivityMetricsTest extends TestCase {
} }
static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR = static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR =
new Comparator<ConnectivityMetricsEvent>() { Comparator.comparingLong((ev) -> ev.timestamp);
@Override
public int compare(ConnectivityMetricsEvent ev1, ConnectivityMetricsEvent ev2) {
return (int) (ev1.timestamp - ev2.timestamp);
}
};
} }

View File

@@ -16,24 +16,34 @@
package com.android.server.connectivity; package com.android.server.connectivity;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network; import android.net.Network;
import android.net.metrics.DnsEvent; import android.net.metrics.DnsEvent;
import android.net.metrics.INetdEventListener; import android.net.metrics.INetdEventListener;
import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpConnectivityLog;
import android.os.RemoteException; import android.os.RemoteException;
import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.server.connectivity.metrics.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 junit.framework.TestCase;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq; import static org.mockito.Mockito.eq;
@@ -41,13 +51,6 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import java.util.OptionalInt;
import java.util.stream.IntStream;
public class NetdEventListenerServiceTest extends TestCase { public class NetdEventListenerServiceTest extends TestCase {
// TODO: read from NetdEventListenerService after this constant is read from system property // TODO: read from NetdEventListenerService after this constant is read from system property
@@ -67,50 +70,56 @@ public class NetdEventListenerServiceTest extends TestCase {
} }
} }
private static final String EXAMPLE_IPV4 = "192.0.2.1";
private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
NetdEventListenerService mNetdEventListenerService; NetdEventListenerService mNetdEventListenerService;
@Mock ConnectivityManager mCm; @Mock ConnectivityManager mCm;
@Mock IpConnectivityLog mLog; @Mock IpConnectivityLog mLog;
ArgumentCaptor<NetworkCallback> mCallbackCaptor; ArgumentCaptor<NetworkCallback> mCallbackCaptor;
ArgumentCaptor<DnsEvent> mEvCaptor; ArgumentCaptor<DnsEvent> mDnsEvCaptor;
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class); mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class);
mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class);
mNetdEventListenerService = new NetdEventListenerService(mCm, mLog); mNetdEventListenerService = new NetdEventListenerService(mCm, mLog);
verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture()); verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture());
} }
public void testOneBatch() throws Exception { @SmallTest
public void testOneDnsBatch() throws Exception {
log(105, LATENCIES); log(105, LATENCIES);
log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event
verifyLoggedEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); verifyLoggedDnsEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE)); log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE));
mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor
verifyLoggedEvents( verifyLoggedDnsEvents(
new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES)); new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES));
} }
public void testSeveralBatches() throws Exception { @SmallTest
public void testSeveralDmsBatches() throws Exception {
log(105, LATENCIES); log(105, LATENCIES);
log(106, LATENCIES); log(106, LATENCIES);
log(105, LATENCIES); log(105, LATENCIES);
log(107, LATENCIES); log(107, LATENCIES);
verifyLoggedEvents( verifyLoggedDnsEvents(
new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES)); new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
} }
public void testBatchAndNetworkLost() throws Exception { @SmallTest
public void testDnsBatchAndNetworkLost() throws Exception {
byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20); byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20);
byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20); byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20);
int[] latencies = Arrays.copyOf(LATENCIES, 20); int[] latencies = Arrays.copyOf(LATENCIES, 20);
@@ -120,13 +129,14 @@ public class NetdEventListenerServiceTest extends TestCase {
mCallbackCaptor.getValue().onLost(new Network(105)); mCallbackCaptor.getValue().onLost(new Network(105));
log(105, LATENCIES); log(105, LATENCIES);
verifyLoggedEvents( verifyLoggedDnsEvents(
new DnsEvent(105, eventTypes, returnCodes, latencies), new DnsEvent(105, eventTypes, returnCodes, latencies),
new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
} }
public void testConcurrentBatchesAndDumps() throws Exception { @SmallTest
public void testConcurrentDnsBatchesAndDumps() throws Exception {
final long stop = System.currentTimeMillis() + 100; final long stop = System.currentTimeMillis() + 100;
final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null")); final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
new Thread() { new Thread() {
@@ -137,26 +147,120 @@ public class NetdEventListenerServiceTest extends TestCase {
} }
}.start(); }.start();
logAsync(105, LATENCIES); logDnsAsync(105, LATENCIES);
logAsync(106, LATENCIES); logDnsAsync(106, LATENCIES);
logAsync(107, LATENCIES); logDnsAsync(107, LATENCIES);
verifyLoggedEvents(500, verifyLoggedDnsEvents(500,
new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES)); new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
} }
public void testConcurrentBatchesAndNetworkLoss() throws Exception { @SmallTest
logAsync(105, LATENCIES); public void testConcurrentDnsBatchesAndNetworkLoss() throws Exception {
logDnsAsync(105, LATENCIES);
Thread.sleep(10L); Thread.sleep(10L);
// call onLost() asynchronously to logAsync's onDnsEvent() calls. // call onLost() asynchronously to logDnsAsync's onDnsEvent() calls.
mCallbackCaptor.getValue().onLost(new Network(105)); mCallbackCaptor.getValue().onLost(new Network(105));
// do not verify unpredictable batch // do not verify unpredictable batch
verify(mLog, timeout(500).times(1)).log(any()); verify(mLog, timeout(500).times(1)).log(any());
} }
@SmallTest
public void testConnectLogging() throws Exception {
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),
// 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),
// 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),
};
for (Thread t : logActions) {
t.start();
}
for (Thread t : logActions) {
t.join();
}
List<IpConnectivityEvent> events = new ArrayList<>();
mNetdEventListenerService.flushStatistics(events);
IpConnectivityEvent got = events.get(0);
String want = joinLines(
"time_ms: 0",
"transport: 0",
"connect_statistics <",
" connect_count: 12",
" errnos_counters <",
" key: 1",
" value: 2",
" >",
" errnos_counters <",
" key: 11",
" value: 1",
" >",
" 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");
verifyConnectEvent(want, got);
}
Thread connectEventAction(int error, int latencyMs, String ipAddr) {
return new Thread(() -> {
try {
mNetdEventListenerService.onConnectEvent(100, error, latencyMs, ipAddr, 80, 1);
} catch (Exception e) {
fail(e.toString());
}
});
}
void log(int netId, int[] latencies) { void log(int netId, int[] latencies) {
try { try {
for (int l : latencies) { for (int l : latencies) {
@@ -168,7 +272,7 @@ public class NetdEventListenerServiceTest extends TestCase {
} }
} }
void logAsync(int netId, int[] latencies) { void logDnsAsync(int netId, int[] latencies) {
new Thread() { new Thread() {
public void run() { public void run() {
log(netId, latencies); log(netId, latencies);
@@ -176,15 +280,15 @@ public class NetdEventListenerServiceTest extends TestCase {
}.start(); }.start();
} }
void verifyLoggedEvents(DnsEvent... expected) { void verifyLoggedDnsEvents(DnsEvent... expected) {
verifyLoggedEvents(0, expected); verifyLoggedDnsEvents(0, expected);
} }
void verifyLoggedEvents(int wait, DnsEvent... expectedEvents) { void verifyLoggedDnsEvents(int wait, DnsEvent... expectedEvents) {
verify(mLog, timeout(wait).times(expectedEvents.length)).log(mEvCaptor.capture()); verify(mLog, timeout(wait).times(expectedEvents.length)).log(mDnsEvCaptor.capture());
for (DnsEvent got : mEvCaptor.getAllValues()) { for (DnsEvent got : mDnsEvCaptor.getAllValues()) {
OptionalInt index = IntStream.range(0, expectedEvents.length) OptionalInt index = IntStream.range(0, expectedEvents.length)
.filter(i -> eventsEqual(expectedEvents[i], got)) .filter(i -> dnsEventsEqual(expectedEvents[i], got))
.findFirst(); .findFirst();
// Don't match same expected event more than once. // Don't match same expected event more than once.
index.ifPresent(i -> expectedEvents[i] = null); index.ifPresent(i -> expectedEvents[i] = null);
@@ -193,11 +297,30 @@ public class NetdEventListenerServiceTest extends TestCase {
} }
/** equality function for DnsEvent to avoid overriding equals() and hashCode(). */ /** equality function for DnsEvent to avoid overriding equals() and hashCode(). */
static boolean eventsEqual(DnsEvent expected, DnsEvent got) { static boolean dnsEventsEqual(DnsEvent expected, DnsEvent got) {
return (expected == got) || ((expected != null) && (got != null) return (expected == got) || ((expected != null) && (got != null)
&& (expected.netId == got.netId) && (expected.netId == got.netId)
&& Arrays.equals(expected.eventTypes, got.eventTypes) && Arrays.equals(expected.eventTypes, got.eventTypes)
&& Arrays.equals(expected.returnCodes, got.returnCodes) && Arrays.equals(expected.returnCodes, got.returnCodes)
&& Arrays.equals(expected.latenciesMs, got.latenciesMs)); && Arrays.equals(expected.latenciesMs, got.latenciesMs));
} }
static String joinLines(String ... elems) {
StringBuilder b = new StringBuilder();
for (String s : elems) {
b.append(s).append("\n");
}
return b.toString();
}
static void verifyConnectEvent(String expected, IpConnectivityEvent got) {
try {
Arrays.sort(got.connectStatistics.latenciesMs);
Arrays.sort(got.connectStatistics.errnosCounters,
Comparator.comparingInt((p) -> p.key));
assertEquals(expected, got.toString());
} catch (Exception e) {
fail(e.toString());
}
}
} }