From f562ac34a51da55e4d15e34f0cd1cb597e7d926c Mon Sep 17 00:00:00 2001 From: Hugo Benichi Date: Mon, 4 Sep 2017 13:24:43 +0900 Subject: [PATCH 1/2] Connectivity metrics: collect NFLOG wakeup events This patch stores NFLOG packet wakeup events sent by Netd to the system server into a ring buffer inside NetdEventListenerService. The content of this buffer is accessible by $ dumpsys connmetrics or $ dumpsys connmetrics list, and is added to bug reports. The wakeup event buffer stores currently uid and timestamps. Bug: 34901696 Bug: 62179647 Test: runtest frameworks-net, new unit tests Change-Id: Ie8db6f8572b1a929a20398d8dc03e189bc488382 --- .../java/android/net/metrics/WakeupEvent.java | 34 +++++++++++ .../NetdEventListenerService.java | 59 ++++++++++++++++++- .../NetdEventListenerServiceTest.java | 58 ++++++++++++++++++ 3 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 core/java/android/net/metrics/WakeupEvent.java diff --git a/core/java/android/net/metrics/WakeupEvent.java b/core/java/android/net/metrics/WakeupEvent.java new file mode 100644 index 0000000000000..cbf3fc8c81da1 --- /dev/null +++ b/core/java/android/net/metrics/WakeupEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 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 android.net.metrics; + +/** + * An event logged when NFLOG notifies userspace of a wakeup packet for + * watched interfaces. + * {@hide} + */ +public class WakeupEvent { + public String iface; + public long timestampMs; + public int uid; + + @Override + public String toString() { + return String.format("WakeupEvent(%tT.%tL, %s, uid: %d)", + timestampMs, timestampMs, iface, uid); + } +} diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java index 4094083f138c5..833457cd618b0 100644 --- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java +++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java @@ -25,6 +25,7 @@ import android.net.metrics.ConnectStats; import android.net.metrics.DnsEvent; import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; +import android.net.metrics.WakeupEvent; import android.os.RemoteException; import android.text.format.DateUtils; import android.util.Log; @@ -59,12 +60,23 @@ public class NetdEventListenerService extends INetdEventListener.Stub { private static final int CONNECT_LATENCY_FILL_RATE = 15 * (int) DateUtils.SECOND_IN_MILLIS; private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000; + @VisibleForTesting + static final int WAKEUP_EVENT_BUFFER_LENGTH = 1024; + + private static final String WAKEUP_EVENT_IFACE_PREFIX = "iface:"; + // Sparse arrays of DNS and connect events, grouped by net id. @GuardedBy("this") private final SparseArray mDnsEvents = new SparseArray<>(); @GuardedBy("this") private final SparseArray mConnectEvents = new SparseArray<>(); + // Ring buffer array for storing packet wake up events sent by Netd. + @GuardedBy("this") + private final WakeupEvent[] mWakeupEvents = new WakeupEvent[WAKEUP_EVENT_BUFFER_LENGTH]; + @GuardedBy("this") + private long mWakeupEventCursor = 0; + private final ConnectivityManager mCm; @GuardedBy("this") @@ -137,6 +149,44 @@ public class NetdEventListenerService extends INetdEventListener.Stub { @Override public synchronized void onWakeupEvent(String prefix, int uid, int gid, long timestampNs) { + maybeVerboseLog("onWakeupEvent(%s, %d, %d, %sns)", prefix, uid, gid, timestampNs); + + // TODO: add ip protocol and port + + String iface = prefix.replaceFirst(WAKEUP_EVENT_IFACE_PREFIX, ""); + long timestampMs = timestampNs / 1000000; + // FIXME: Netd timestampNs is always 0. + timestampMs = System.currentTimeMillis(); + + addWakupEvent(iface, timestampMs, uid); + } + + @GuardedBy("this") + private void addWakupEvent(String iface, long timestampMs, int uid) { + int index = wakeupEventIndex(mWakeupEventCursor); + mWakeupEventCursor++; + WakeupEvent event = new WakeupEvent(); + event.iface = iface; + event.timestampMs = timestampMs; + event.uid = uid; + mWakeupEvents[index] = event; + } + + @GuardedBy("this") + private WakeupEvent[] getWakeupEvents() { + int length = (int) Math.min(mWakeupEventCursor, (long) mWakeupEvents.length); + WakeupEvent[] out = new WakeupEvent[length]; + // Reverse iteration from youngest event to oldest event. + long inCursor = mWakeupEventCursor - 1; + int outIdx = out.length - 1; + while (outIdx >= 0) { + out[outIdx--] = mWakeupEvents[wakeupEventIndex(inCursor--)]; + } + return out; + } + + private static int wakeupEventIndex(long cursor) { + return (int) Math.abs(cursor % WAKEUP_EVENT_BUFFER_LENGTH); } public synchronized void flushStatistics(List events) { @@ -155,6 +205,7 @@ public class NetdEventListenerService extends INetdEventListener.Stub { public synchronized void list(PrintWriter pw) { listEvents(pw, mConnectEvents, (x) -> x); listEvents(pw, mDnsEvents, (x) -> x); + listWakeupEvents(pw, getWakeupEvents()); } public synchronized void listAsProtos(PrintWriter pw) { @@ -170,13 +221,19 @@ public class NetdEventListenerService extends INetdEventListener.Stub { in.clear(); } - public static void listEvents( + private static void listEvents( PrintWriter pw, SparseArray events, Function mapper) { for (int i = 0; i < events.size(); i++) { pw.println(mapper.apply(events.valueAt(i)).toString()); } } + private static void listWakeupEvents(PrintWriter pw, WakeupEvent[] events) { + for (WakeupEvent wakeup : events) { + pw.println(wakeup); + } + } + private ConnectStats makeConnectStats(int netId) { long transports = getTransports(netId); return new ConnectStats(netId, transports, mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS); diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java index 46f395eae214c..3a93b9432ca24 100644 --- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java +++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java @@ -19,6 +19,7 @@ package com.android.server.connectivity; 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.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; @@ -74,6 +75,52 @@ public class NetdEventListenerServiceTest { mNetdEventListenerService = new NetdEventListenerService(mCm); } + @Test + public void testWakeupEventLogging() throws Exception { + final int BUFFER_LENGTH = NetdEventListenerService.WAKEUP_EVENT_BUFFER_LENGTH; + + // Assert no events + String[] events1 = listNetdEvent(); + assertEquals(new String[]{""}, events1); + + long now = System.currentTimeMillis(); + String prefix = "iface:wlan0"; + int[] uids = { 10001, 10002, 10004, 1000, 10052, 10023, 10002, 10123, 10004 }; + for (int uid : uids) { + mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now); + } + + String[] events2 = listNetdEvent(); + assertEquals(uids.length, events2.length); + for (int i = 0; i < uids.length; i++) { + String got = events2[i]; + assertContains(got, "wlan0"); + assertContains(got, "uid: " + uids[i]); + } + + int uid = 20000; + for (int i = 0; i < BUFFER_LENGTH * 2; i++) { + long ts = now + 10; + mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, ts); + } + + // Assert there are BUFFER_LENGTH events all with uid 20000 + String[] events3 = listNetdEvent(); + assertEquals(BUFFER_LENGTH, events3.length); + for (String got : events3) { + assertContains(got, "wlan0"); + assertContains(got, "uid: " + uid); + } + + uid = 45678; + mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now); + + String[] events4 = listNetdEvent(); + String lastEvent = events4[events4.length - 1]; + assertContains(lastEvent, "wlan0"); + assertContains(lastEvent, "uid: " + uid); + } + @Test public void testDnsLogging() throws Exception { asyncDump(100); @@ -329,4 +376,15 @@ public class NetdEventListenerServiceTest { } return log.toString(); } + + String[] listNetdEvent() throws Exception { + StringWriter buffer = new StringWriter(); + PrintWriter writer = new PrintWriter(buffer); + mNetdEventListenerService.list(writer); + return buffer.toString().split("\\n"); + } + + static void assertContains(String got, String want) { + assertTrue(got + " did not contain \"" + want + "\"", got.contains(want)); + } } From 60c9f63b66926745603978e1bd6372b3a44561d1 Mon Sep 17 00:00:00 2001 From: Hugo Benichi Date: Tue, 5 Sep 2017 13:34:48 +0900 Subject: [PATCH 2/2] Connectivity metrics: add WakeupStats events This patch defines a new WakeupStats event in ipconnectivity.proto and populates these events from the NFLOG wakeup events stored in NetdEventListenerService. There is one WakeupStats object per known interface on which ingress packets arrive and may wake the system up. Example from $ adb shell dumpsys connmetrics list: UPDATEME ... WakeupStats(wlan0, total: 58, root: 0, system: 3, apps: 38, non-apps: 0, unrouted: 17, 6111s) WakeupEvent(13:36:31.686, iface wlan0, uid -1) WakeupEvent(13:38:50.846, iface wlan0, uid -1) WakeupEvent(13:39:16.676, iface wlan0, uid 10065) WakeupEvent(13:40:32.144, iface wlan0, uid 1000) WakeupEvent(13:40:35.827, iface wlan0, uid 1000) WakeupEvent(13:40:47.913, iface wlan0, uid 10004) WakeupEvent(13:40:52.622, iface wlan0, uid 10014) WakeupEvent(13:41:06.036, iface wlan0, uid 10004) ... Bug: 34901696 Bug: 62179647 Test: runtest frameworks-net Change-Id: Ie2676b20bfb411a1902f4942643df0c20e268d99 --- .../java/android/net/metrics/WakeupStats.java | 87 +++++++++++++++++++ proto/src/ipconnectivity.proto | 35 ++++++++ .../IpConnectivityEventBuilder.java | 17 ++++ .../NetdEventListenerService.java | 63 ++++++++++---- .../IpConnectivityEventBuilderTest.java | 84 ++++++++++++++---- .../IpConnectivityMetricsTest.java | 46 ++++++++++ .../NetdEventListenerServiceTest.java | 84 ++++++++++++++++-- 7 files changed, 377 insertions(+), 39 deletions(-) create mode 100644 core/java/android/net/metrics/WakeupStats.java diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java new file mode 100644 index 0000000000000..d520b9745918c --- /dev/null +++ b/core/java/android/net/metrics/WakeupStats.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 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 android.net.metrics; + +import android.os.Process; +import android.os.SystemClock; + +/** + * An event logged per interface and that aggregates WakeupEvents for that interface. + * {@hide} + */ +public class WakeupStats { + + private static final int NO_UID = -1; + + public final long creationTimeMs = SystemClock.elapsedRealtime(); + public final String iface; + + public long totalWakeups = 0; + public long rootWakeups = 0; + public long systemWakeups = 0; + public long nonApplicationWakeups = 0; + public long applicationWakeups = 0; + public long unroutedWakeups = 0; + public long durationSec = 0; + + public WakeupStats(String iface) { + this.iface = iface; + } + + /** Update durationSec with current time. */ + public void updateDuration() { + durationSec = (SystemClock.elapsedRealtime() - creationTimeMs) / 1000; + } + + /** Update wakeup counters for the given WakeupEvent. */ + public void countEvent(WakeupEvent ev) { + totalWakeups++; + switch (ev.uid) { + case Process.ROOT_UID: + rootWakeups++; + break; + case Process.SYSTEM_UID: + systemWakeups++; + break; + case NO_UID: + unroutedWakeups++; + break; + default: + if (ev.uid >= Process.FIRST_APPLICATION_UID) { + applicationWakeups++; + } else { + nonApplicationWakeups++; + } + break; + } + } + + @Override + public String toString() { + updateDuration(); + return new StringBuilder() + .append("WakeupStats(").append(iface) + .append(", total: ").append(totalWakeups) + .append(", root: ").append(rootWakeups) + .append(", system: ").append(systemWakeups) + .append(", apps: ").append(applicationWakeups) + .append(", non-apps: ").append(nonApplicationWakeups) + .append(", unrouted: ").append(unroutedWakeups) + .append(", ").append(durationSec).append("s)") + .toString(); + } +} diff --git a/proto/src/ipconnectivity.proto b/proto/src/ipconnectivity.proto index 01fb860e92d60..d997a80cda830 100644 --- a/proto/src/ipconnectivity.proto +++ b/proto/src/ipconnectivity.proto @@ -473,6 +473,38 @@ message NetworkStats { repeated Pair validation_states = 8; } +// Represents statistics from NFLOG wakeup events due to ingress packets. +// Since oc-mr1. +// Next tag: 8. +message WakeupStats { + // The time duration in seconds covered by these stats, for deriving + // exact wakeup rates. + optional int64 duration_sec = 1; + + // The total number of ingress packets waking up the device. + optional int64 total_wakeups = 2; + + // The total number of wakeup packets routed to a socket belonging to + // the root uid (uid 0). + optional int64 root_wakeups = 3; + + // The total number of wakeup packets routed to a socket belonging to + // the system server (uid 1000). + optional int64 system_wakeups = 4; + + // The total number of wakeup packets routed to a socket belonging to + // an application (uid > 9999). + optional int64 application_wakeups = 5; + + // The total number of wakeup packets routed to a socket belonging to another + // uid than the root uid, system uid, or an application uid (any uid in + // between [1001, 9999]. See android.os.Process for possible uids. + optional int64 non_application_wakeups = 6; + + // The total number of wakeup packets with no associated sockets. + optional int64 unrouted_wakeups = 7; +} + // Represents one of the IP connectivity event defined in this file. // Next tag: 20 message IpConnectivityEvent { @@ -547,6 +579,9 @@ message IpConnectivityEvent { // Network statistics. NetworkStats network_stats = 19; + + // Ingress packet wakeup statistics. + WakeupStats wakeup_stats = 20; }; }; diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java index ee382199438b5..22330e66e1269 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java @@ -38,6 +38,7 @@ import android.net.metrics.IpReachabilityEvent; import android.net.metrics.NetworkEvent; import android.net.metrics.RaEvent; import android.net.metrics.ValidationProbeEvent; +import android.net.metrics.WakeupStats; import android.os.Parcelable; import android.util.SparseArray; import android.util.SparseIntArray; @@ -115,6 +116,22 @@ final public class IpConnectivityEventBuilder { return out; } + public static IpConnectivityEvent toProto(WakeupStats in) { + IpConnectivityLogClass.WakeupStats wakeupStats = + new IpConnectivityLogClass.WakeupStats(); + in.updateDuration(); + wakeupStats.durationSec = in.durationSec; + wakeupStats.totalWakeups = in.totalWakeups; + wakeupStats.rootWakeups = in.rootWakeups; + wakeupStats.systemWakeups = in.systemWakeups; + wakeupStats.nonApplicationWakeups = in.nonApplicationWakeups; + wakeupStats.applicationWakeups = in.applicationWakeups; + wakeupStats.unroutedWakeups = in.unroutedWakeups; + final IpConnectivityEvent out = buildEvent(0, 0, in.iface); + out.setWakeupStats(wakeupStats); + return out; + } + private static IpConnectivityEvent buildEvent(int netId, long transports, String ifname) { final IpConnectivityEvent ev = new IpConnectivityEvent(); ev.networkId = netId; diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java index 833457cd618b0..6f7ace2f6527d 100644 --- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java +++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java @@ -16,6 +16,8 @@ package com.android.server.connectivity; +import static android.util.TimeUtils.NANOS_PER_MS; + import android.content.Context; import android.net.ConnectivityManager; import android.net.INetdEventCallback; @@ -26,9 +28,11 @@ import android.net.metrics.DnsEvent; import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; import android.net.metrics.WakeupEvent; +import android.net.metrics.WakeupStats; import android.os.RemoteException; import android.text.format.DateUtils; import android.util.Log; +import android.util.ArrayMap; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -62,8 +66,10 @@ public class NetdEventListenerService extends INetdEventListener.Stub { @VisibleForTesting static final int WAKEUP_EVENT_BUFFER_LENGTH = 1024; - - private static final String WAKEUP_EVENT_IFACE_PREFIX = "iface:"; + // TODO: dedup this String constant with the one used in + // ConnectivityService#wakeupModifyInterface(). + @VisibleForTesting + static final String WAKEUP_EVENT_IFACE_PREFIX = "iface:"; // Sparse arrays of DNS and connect events, grouped by net id. @GuardedBy("this") @@ -71,6 +77,9 @@ public class NetdEventListenerService extends INetdEventListener.Stub { @GuardedBy("this") private final SparseArray mConnectEvents = new SparseArray<>(); + // Array of aggregated wakeup event stats, grouped by interface name. + @GuardedBy("this") + private final ArrayMap mWakeupStats = new ArrayMap<>(); // Ring buffer array for storing packet wake up events sent by Netd. @GuardedBy("this") private final WakeupEvent[] mWakeupEvents = new WakeupEvent[WAKEUP_EVENT_BUFFER_LENGTH]; @@ -154,9 +163,12 @@ public class NetdEventListenerService extends INetdEventListener.Stub { // TODO: add ip protocol and port String iface = prefix.replaceFirst(WAKEUP_EVENT_IFACE_PREFIX, ""); - long timestampMs = timestampNs / 1000000; - // FIXME: Netd timestampNs is always 0. - timestampMs = System.currentTimeMillis(); + final long timestampMs; + if (timestampNs > 0) { + timestampMs = timestampNs / NANOS_PER_MS; + } else { + timestampMs = System.currentTimeMillis(); + } addWakupEvent(iface, timestampMs, uid); } @@ -170,6 +182,12 @@ public class NetdEventListenerService extends INetdEventListener.Stub { event.timestampMs = timestampMs; event.uid = uid; mWakeupEvents[index] = event; + WakeupStats stats = mWakeupStats.get(iface); + if (stats == null) { + stats = new WakeupStats(iface); + mWakeupStats.put(iface, stats); + } + stats.countEvent(event); } @GuardedBy("this") @@ -192,6 +210,10 @@ public class NetdEventListenerService extends INetdEventListener.Stub { public synchronized void flushStatistics(List events) { flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto); flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto); + for (int i = 0; i < mWakeupStats.size(); i++) { + events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i))); + } + mWakeupStats.clear(); } public synchronized void dump(PrintWriter writer) { @@ -203,14 +225,22 @@ public class NetdEventListenerService extends INetdEventListener.Stub { } public synchronized void list(PrintWriter pw) { - listEvents(pw, mConnectEvents, (x) -> x); - listEvents(pw, mDnsEvents, (x) -> x); - listWakeupEvents(pw, getWakeupEvents()); + listEvents(pw, mConnectEvents, (x) -> x, "\n"); + listEvents(pw, mDnsEvents, (x) -> x, "\n"); + for (int i = 0; i < mWakeupStats.size(); i++) { + pw.println(mWakeupStats.valueAt(i)); + } + for (WakeupEvent wakeup : getWakeupEvents()) { + pw.println(wakeup); + } } public synchronized void listAsProtos(PrintWriter pw) { - listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto); - listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto); + listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto, ""); + listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto, ""); + for (int i = 0; i < mWakeupStats.size(); i++) { + pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i))); + } } private static void flushProtos(List out, SparseArray in, @@ -222,15 +252,12 @@ public class NetdEventListenerService extends INetdEventListener.Stub { } private static void listEvents( - PrintWriter pw, SparseArray events, Function mapper) { + PrintWriter pw, SparseArray events, Function mapper, String separator) { + // Proto derived Classes have toString method that adds a \n at the end. + // Let the caller control that by passing in the line separator explicitly. for (int i = 0; i < events.size(); i++) { - pw.println(mapper.apply(events.valueAt(i)).toString()); - } - } - - private static void listWakeupEvents(PrintWriter pw, WakeupEvent[] events) { - for (WakeupEvent wakeup : events) { - pw.println(wakeup); + pw.print(mapper.apply(events.valueAt(i))); + pw.print(separator); } } diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java index eff04ab5adddf..f72a1c638ed51 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java @@ -16,6 +16,8 @@ package com.android.server.connectivity; +import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO; +import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME; import static com.android.server.connectivity.MetricsTestUtil.aBool; import static com.android.server.connectivity.MetricsTestUtil.aByteArray; import static com.android.server.connectivity.MetricsTestUtil.aLong; @@ -31,29 +33,41 @@ import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClas import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.ETHERNET; import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.MULTIPLE; import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.WIFI; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import android.net.ConnectivityMetricsEvent; import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfStats; +import android.net.metrics.ConnectStats; import android.net.metrics.DefaultNetworkEvent; import android.net.metrics.DhcpClientEvent; import android.net.metrics.DhcpErrorEvent; import android.net.metrics.DnsEvent; +import android.net.metrics.DnsEvent; import android.net.metrics.IpManagerEvent; import android.net.metrics.IpReachabilityEvent; import android.net.metrics.NetworkEvent; import android.net.metrics.RaEvent; import android.net.metrics.ValidationProbeEvent; +import android.net.metrics.WakeupStats; +import android.support.test.runner.AndroidJUnit4; 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; + +import org.junit.runner.RunWith; +import org.junit.Test; // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto. -public class IpConnectivityEventBuilderTest extends TestCase { +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IpConnectivityEventBuilderTest { - @SmallTest + @Test public void testLinkLayerInferrence() { ConnectivityMetricsEvent ev = describeIpEvent( aType(IpReachabilityEvent.class), @@ -182,7 +196,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testDefaultNetworkEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DefaultNetworkEvent.class), @@ -223,7 +237,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testDhcpClientEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DhcpClientEvent.class), @@ -249,7 +263,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testDhcpErrorEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DhcpErrorEvent.class), @@ -274,7 +288,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testIpManagerEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(IpManagerEvent.class), @@ -300,7 +314,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testIpReachabilityEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(IpReachabilityEvent.class), @@ -324,7 +338,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testNetworkEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(NetworkEvent.class), @@ -353,7 +367,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testValidationProbeEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(ValidationProbeEvent.class), @@ -380,7 +394,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testApfProgramEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(ApfProgramEvent.class), @@ -414,7 +428,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testApfStatsSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(ApfStats.class), @@ -457,7 +471,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testRaEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(RaEvent.class), @@ -490,11 +504,49 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } + @Test + public void testWakeupStatsSerialization() { + WakeupStats stats = new WakeupStats("wlan0"); + stats.totalWakeups = 14; + stats.applicationWakeups = 5; + stats.nonApplicationWakeups = 1; + stats.rootWakeups = 2; + stats.systemWakeups = 3; + stats.unroutedWakeups = 3; + + IpConnectivityEvent got = IpConnectivityEventBuilder.toProto(stats); + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " wakeup_stats <", + " application_wakeups: 5", + " duration_sec: 0", + " non_application_wakeups: 1", + " root_wakeups: 2", + " system_wakeups: 3", + " total_wakeups: 14", + " unrouted_wakeups: 3", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, got); + } + static void verifySerialization(String want, ConnectivityMetricsEvent... input) { + List protoInput = + IpConnectivityEventBuilder.toProto(Arrays.asList(input)); + verifySerialization(want, protoInput.toArray(new IpConnectivityEvent[0])); + } + + static void verifySerialization(String want, IpConnectivityEvent... input) { try { - List proto = - IpConnectivityEventBuilder.toProto(Arrays.asList(input)); - byte[] got = IpConnectivityEventBuilder.serialize(0, proto); + byte[] got = IpConnectivityEventBuilder.serialize(0, Arrays.asList(input)); IpConnectivityLog log = IpConnectivityLog.parseFrom(got); assertEquals(want, log.toString()); } catch (Exception e) { diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java index cc18b7f322082..ede5988cdc6da 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -224,6 +224,15 @@ public class IpConnectivityMetricsTest { dnsEvent(101, EVENT_GETADDRINFO, 0, 56); dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 34); + // iface, uid + wakeupEvent("wlan0", 1000); + wakeupEvent("rmnet0", 10123); + wakeupEvent("wlan0", 1000); + wakeupEvent("rmnet0", 10008); + wakeupEvent("wlan0", -1); + wakeupEvent("wlan0", 10008); + wakeupEvent("rmnet0", 1000); + String want = String.join("\n", "dropped_events: 0", "events <", @@ -405,6 +414,38 @@ public class IpConnectivityMetricsTest { " return_codes: 0", " >", ">", + "events <", + " if_name: \"\"", + " link_layer: 2", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " wakeup_stats <", + " application_wakeups: 2", + " duration_sec: 0", + " non_application_wakeups: 0", + " root_wakeups: 0", + " system_wakeups: 1", + " total_wakeups: 3", + " unrouted_wakeups: 0", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " wakeup_stats <", + " application_wakeups: 1", + " duration_sec: 0", + " non_application_wakeups: 0", + " root_wakeups: 0", + " system_wakeups: 2", + " total_wakeups: 4", + " unrouted_wakeups: 1", + " >", + ">", "version: 2\n"); verifySerialization(want, getdump("flush")); @@ -425,6 +466,11 @@ public class IpConnectivityMetricsTest { mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0); } + void wakeupEvent(String iface, int uid) throws Exception { + String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface; + mNetdListener.onWakeupEvent(prefix, uid, uid, 0); + } + List verifyEvents(int n, int timeoutMs) throws Exception { ArgumentCaptor 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 3a93b9432ca24..2b105e5c92eec 100644 --- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java +++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java @@ -38,9 +38,11 @@ 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; @@ -48,6 +50,7 @@ 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; @@ -91,9 +94,13 @@ public class NetdEventListenerServiceTest { } String[] events2 = listNetdEvent(); - assertEquals(uids.length, events2.length); + int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line + assertEquals(expectedLength2, events2.length); + assertContains(events2[0], "WakeupStats"); + assertContains(events2[0], "wlan0"); for (int i = 0; i < uids.length; i++) { - String got = events2[i]; + String got = events2[i+1]; + assertContains(got, "WakeupEvent"); assertContains(got, "wlan0"); assertContains(got, "uid: " + uids[i]); } @@ -104,10 +111,14 @@ public class NetdEventListenerServiceTest { mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, ts); } - // Assert there are BUFFER_LENGTH events all with uid 20000 String[] events3 = listNetdEvent(); - assertEquals(BUFFER_LENGTH, events3.length); - for (String got : events3) { + int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line + assertEquals(expectedLength3, events3.length); + assertContains(events2[0], "WakeupStats"); + assertContains(events2[0], "wlan0"); + for (int i = 1; i < expectedLength3; i++) { + String got = events3[i]; + assertContains(got, "WakeupEvent"); assertContains(got, "wlan0"); assertContains(got, "uid: " + uid); } @@ -117,10 +128,68 @@ public class NetdEventListenerServiceTest { String[] events4 = listNetdEvent(); String lastEvent = events4[events4.length - 1]; + assertContains(lastEvent, "WakeupEvent"); assertContains(lastEvent, "wlan0"); assertContains(lastEvent, "uid: " + uid); } + @Test + public void testWakeupStatsLogging() throws Exception { + wakeupEvent("wlan0", 1000); + wakeupEvent("rmnet0", 10123); + wakeupEvent("wlan0", 1000); + wakeupEvent("rmnet0", 10008); + wakeupEvent("wlan0", -1); + wakeupEvent("wlan0", 10008); + wakeupEvent("rmnet0", 1000); + wakeupEvent("wlan0", 10004); + wakeupEvent("wlan0", 1000); + wakeupEvent("wlan0", 0); + wakeupEvent("wlan0", -1); + wakeupEvent("rmnet0", 10052); + wakeupEvent("wlan0", 0); + wakeupEvent("rmnet0", 1000); + wakeupEvent("wlan0", 1010); + + String got = flushStatistics(); + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 2", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " wakeup_stats <", + " application_wakeups: 3", + " duration_sec: 0", + " non_application_wakeups: 0", + " root_wakeups: 0", + " system_wakeups: 2", + " total_wakeups: 5", + " unrouted_wakeups: 0", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " wakeup_stats <", + " application_wakeups: 2", + " duration_sec: 0", + " non_application_wakeups: 1", + " root_wakeups: 2", + " system_wakeups: 3", + " total_wakeups: 10", + " unrouted_wakeups: 2", + " >", + ">", + "version: 2\n"); + assertEquals(want, got); + } + @Test public void testDnsLogging() throws Exception { asyncDump(100); @@ -344,6 +413,11 @@ public class NetdEventListenerServiceTest { mNetdEventListenerService.onDnsEvent(netId, type, result, latency, "", null, 0, 0); } + void wakeupEvent(String iface, int uid) throws Exception { + String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface; + mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, 0); + } + void asyncDump(long durationMs) throws Exception { final long stop = System.currentTimeMillis() + durationMs; final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));