--- /dev/null
+/*
+ * 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);
+ }
+}
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;
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<DnsEvent> mDnsEvents = new SparseArray<>();
@GuardedBy("this")
private final SparseArray<ConnectStats> 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")
@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<IpConnectivityEvent> events) {
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) {
in.clear();
}
- public static <T> void listEvents(
+ private static <T> void listEvents(
PrintWriter pw, SparseArray<T> events, Function<T, Object> mapper) {
for (int i = 0; i < events.size(); i++) {
pw.println(mapper.apply(events.valueAt(i)).toString());
}
}
+ private 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);
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;
}
@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);
}
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));
+ }
}