Support NetworkStatsEventLogger
The performPoll method in the NetworkStatsService can be called
for many reasons, such as RAT changed events, LinkProperties
changed events, open session method calls, forceUpdate method
calls, etc.
However, the current logging is not sufficient to trace down
the caller of performPoll. This makes it difficult to debug
frequent poll issues or performance problems.
Adding debug log to logcat is not an option, given that
performPoll might run several times per second by system server
callers.
This change adds a reason code to the performPoll method and
a helper class to log performPoll events. This will allow us
to track down the caller of performPoll and better understand
why it is being called frequently or causing performance
problems.
This change also introduces a PollEvent object, which can be
used to put related information in subsequent changes when
logging.
Sample output:
Poll counts per reason:
DUMPSYS: 0
FORCE_UPDATE: 6
GLOBAL_ALERT: 0
NETWORK_STATUS_CHANGED: 3
OPEN_SESSION: 0
PERIODIC: 1
RAT_CHANGED: 1
REG_CALLBACK: 3
REMOVE_UIDS: 0
UPSTREAM_CHANGED: 1
Recent poll events:
2023-10-04T02:47:12.937370 - Poll(flags=3, PollEvent{reason=REG_CALLBACK})
2023-10-04T02:47:13.178559 - Poll(flags=3, PollEvent{reason=FORCE_UPDATE})
Test: manual
Test: atest FrameworksNetTests:android.net.connectivity.com.android.server.net.NetworkStatsEventLoggerTest
Test: atest FrameworksNetTests:android.net.connectivity.com.android.server.net.NetworkStatsServiceTest
Bug: 301141955
Change-Id: Ic6d0543ccdef12493385f45200199854b841a26b
diff --git a/service-t/src/com/android/server/net/NetworkStatsEventLogger.java b/service-t/src/com/android/server/net/NetworkStatsEventLogger.java
new file mode 100644
index 0000000..679837a
--- /dev/null
+++ b/service-t/src/com/android/server/net/NetworkStatsEventLogger.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 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.net;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Helper class for NetworkStatsService to log events.
+ *
+ * @hide
+ */
+public class NetworkStatsEventLogger {
+ static final int POLL_REASON_DUMPSYS = 0;
+ static final int POLL_REASON_FORCE_UPDATE = 1;
+ static final int POLL_REASON_GLOBAL_ALERT = 2;
+ static final int POLL_REASON_NETWORK_STATUS_CHANGED = 3;
+ static final int POLL_REASON_OPEN_SESSION = 4;
+ static final int POLL_REASON_PERIODIC = 5;
+ static final int POLL_REASON_RAT_CHANGED = 6;
+ static final int POLL_REASON_REG_CALLBACK = 7;
+ static final int POLL_REASON_REMOVE_UIDS = 8;
+ static final int POLL_REASON_UPSTREAM_CHANGED = 9;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "POLL_REASON_" }, value = {
+ POLL_REASON_DUMPSYS,
+ POLL_REASON_FORCE_UPDATE,
+ POLL_REASON_GLOBAL_ALERT,
+ POLL_REASON_NETWORK_STATUS_CHANGED,
+ POLL_REASON_OPEN_SESSION,
+ POLL_REASON_PERIODIC,
+ POLL_REASON_RAT_CHANGED,
+ POLL_REASON_REMOVE_UIDS,
+ POLL_REASON_REG_CALLBACK,
+ POLL_REASON_UPSTREAM_CHANGED
+ })
+ public @interface PollReason {
+ }
+ static final int MAX_POLL_REASON = POLL_REASON_UPSTREAM_CHANGED;
+
+ @VisibleForTesting(visibility = PRIVATE)
+ public static final int MAX_EVENTS_LOGS = 50;
+ private final LocalLog mEventChanges = new LocalLog(MAX_EVENTS_LOGS);
+ private final int[] mPollEventCounts = new int[MAX_POLL_REASON + 1];
+
+ /**
+ * Log a poll event.
+ *
+ * @param flags Flags used when polling. See NetworkStatsService#FLAG_PERSIST_*.
+ * @param event The event of polling to be logged.
+ */
+ public void logPollEvent(int flags, @NonNull PollEvent event) {
+ mEventChanges.log("Poll(flags=" + flags + ", " + event + ")");
+ mPollEventCounts[event.reason]++;
+ }
+
+ /**
+ * Print poll counts per reason into the given stream.
+ */
+ @VisibleForTesting(visibility = PRIVATE)
+ public void dumpPollCountsPerReason(@NonNull IndentingPrintWriter pw) {
+ pw.println("Poll counts per reason:");
+ pw.increaseIndent();
+ for (int i = 0; i <= MAX_POLL_REASON; i++) {
+ pw.println(PollEvent.pollReasonNameOf(i) + ": " + mPollEventCounts[i]);
+ }
+ pw.decreaseIndent();
+ pw.println();
+ }
+
+ /**
+ * Print recent poll events into the given stream.
+ */
+ @VisibleForTesting(visibility = PRIVATE)
+ public void dumpRecentPollEvents(@NonNull IndentingPrintWriter pw) {
+ pw.println("Recent poll events:");
+ pw.increaseIndent();
+ mEventChanges.reverseDump(pw);
+ pw.decreaseIndent();
+ pw.println();
+ }
+
+ /**
+ * Print the object's state into the given stream.
+ */
+ public void dump(@NonNull IndentingPrintWriter pw) {
+ dumpPollCountsPerReason(pw);
+ dumpRecentPollEvents(pw);
+ }
+
+ public static class PollEvent {
+ public final int reason;
+
+ public PollEvent(@PollReason int reason) {
+ if (reason < 0 || reason > MAX_POLL_REASON) {
+ throw new IllegalArgumentException("Unsupported poll reason: " + reason);
+ }
+ this.reason = reason;
+ }
+
+ @Override
+ public String toString() {
+ return "PollEvent{" + "reason=" + pollReasonNameOf(reason) + "}";
+ }
+
+ /**
+ * Get the name of the given reason.
+ *
+ * If the reason does not have a String representation, returns its integer representation.
+ */
+ @NonNull
+ public static String pollReasonNameOf(@PollReason int reason) {
+ switch (reason) {
+ case POLL_REASON_DUMPSYS: return "DUMPSYS";
+ case POLL_REASON_FORCE_UPDATE: return "FORCE_UPDATE";
+ case POLL_REASON_GLOBAL_ALERT: return "GLOBAL_ALERT";
+ case POLL_REASON_NETWORK_STATUS_CHANGED: return "NETWORK_STATUS_CHANGED";
+ case POLL_REASON_OPEN_SESSION: return "OPEN_SESSION";
+ case POLL_REASON_PERIODIC: return "PERIODIC";
+ case POLL_REASON_RAT_CHANGED: return "RAT_CHANGED";
+ case POLL_REASON_REMOVE_UIDS: return "REMOVE_UIDS";
+ case POLL_REASON_REG_CALLBACK: return "REG_CALLBACK";
+ case POLL_REASON_UPSTREAM_CHANGED: return "UPSTREAM_CHANGED";
+ default: return Integer.toString(reason);
+ }
+ }
+ }
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index cc67550..46afd31 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -66,6 +66,17 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport;
import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
+import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_PERIODIC;
+import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_DUMPSYS;
+import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_FORCE_UPDATE;
+import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_GLOBAL_ALERT;
+import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_NETWORK_STATUS_CHANGED;
+import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_OPEN_SESSION;
+import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_RAT_CHANGED;
+import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_REG_CALLBACK;
+import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_REMOVE_UIDS;
+import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_UPSTREAM_CHANGED;
+import static com.android.server.net.NetworkStatsEventLogger.PollEvent;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -281,6 +292,8 @@
static final String NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME = "import.attempts";
static final String NETSTATS_IMPORT_SUCCESSES_COUNTER_NAME = "import.successes";
static final String NETSTATS_IMPORT_FALLBACKS_COUNTER_NAME = "import.fallbacks";
+ static final String CONFIG_ENABLE_NETWORK_STATS_EVENT_LOGGER =
+ "enable_network_stats_event_logger";
private final Context mContext;
private final NetworkStatsFactory mStatsFactory;
@@ -441,6 +454,7 @@
* Map from key {@code OpenSessionKey} to count of opened sessions. This is for recording
* the caller of open session and it is only for debugging.
*/
+ // TODO: Move to NetworkStatsEventLogger to centralize event logging.
@GuardedBy("mOpenSessionCallsLock")
private final HashMap<OpenSessionKey, Integer> mOpenSessionCallsPerCaller = new HashMap<>();
@@ -513,20 +527,21 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_PERFORM_POLL: {
- performPoll(FLAG_PERSIST_ALL);
+ performPoll(FLAG_PERSIST_ALL, maybeCreatePollEvent((int) msg.obj));
break;
}
case MSG_NOTIFY_NETWORK_STATUS: {
synchronized (mStatsLock) {
// If no cached states, ignore.
if (mLastNetworkStateSnapshots == null) break;
- handleNotifyNetworkStatus(
- mDefaultNetworks, mLastNetworkStateSnapshots, mActiveIface);
+ handleNotifyNetworkStatus(mDefaultNetworks, mLastNetworkStateSnapshots,
+ mActiveIface, maybeCreatePollEvent((int) msg.obj));
}
break;
}
case MSG_PERFORM_POLL_REGISTER_ALERT: {
- performPoll(FLAG_PERSIST_NETWORK);
+ performPoll(FLAG_PERSIST_NETWORK,
+ maybeCreatePollEvent(POLL_REASON_GLOBAL_ALERT));
registerGlobalAlert();
break;
}
@@ -612,6 +627,13 @@
mStatsMapB = mDeps.getStatsMapB();
mAppUidStatsMap = mDeps.getAppUidStatsMap();
mIfaceStatsMap = mDeps.getIfaceStatsMap();
+ // To prevent any possible races, the flag is not allowed to change until rebooting.
+ mSupportEventLogger = mDeps.supportEventLogger(mContext);
+ if (mSupportEventLogger) {
+ mEventLogger = new NetworkStatsEventLogger();
+ } else {
+ mEventLogger = null;
+ }
// TODO: Remove bpfNetMaps creation and always start SkDestroyListener
// Following code is for the experiment to verify the SkDestroyListener refactoring. Based
@@ -840,6 +862,14 @@
IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap, Handler handler) {
return new SkDestroyListener(cookieTagMap, handler, new SharedLog(TAG));
}
+
+ /**
+ * Get whether event logger feature is supported.
+ */
+ public boolean supportEventLogger(Context ctx) {
+ return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(
+ ctx, CONFIG_ENABLE_NETWORK_STATS_EVENT_LOGGER);
+ }
}
/**
@@ -1432,7 +1462,7 @@
| NetworkStatsManager.FLAG_POLL_FORCE)) != 0) {
final long ident = Binder.clearCallingIdentity();
try {
- performPoll(FLAG_PERSIST_ALL);
+ performPoll(FLAG_PERSIST_ALL, maybeCreatePollEvent(POLL_REASON_OPEN_SESSION));
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1828,7 +1858,8 @@
final long token = Binder.clearCallingIdentity();
try {
- handleNotifyNetworkStatus(defaultNetworks, networkStates, activeIface);
+ handleNotifyNetworkStatus(defaultNetworks, networkStates, activeIface,
+ maybeCreatePollEvent(POLL_REASON_NETWORK_STATUS_CHANGED));
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1845,7 +1876,8 @@
final long token = Binder.clearCallingIdentity();
try {
- performPoll(FLAG_PERSIST_ALL);
+ // TODO: Log callstack for system server callers.
+ performPoll(FLAG_PERSIST_ALL, maybeCreatePollEvent(POLL_REASON_FORCE_UPDATE));
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1902,7 +1934,8 @@
}
// Create baseline stats
- mHandler.sendMessage(mHandler.obtainMessage(MSG_PERFORM_POLL));
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_PERFORM_POLL,
+ POLL_REASON_REG_CALLBACK));
return normalizedRequest;
}
@@ -1999,7 +2032,8 @@
new TetheringManager.TetheringEventCallback() {
@Override
public void onUpstreamChanged(@Nullable Network network) {
- performPoll(FLAG_PERSIST_NETWORK);
+ performPoll(FLAG_PERSIST_NETWORK,
+ maybeCreatePollEvent(POLL_REASON_UPSTREAM_CHANGED));
}
};
@@ -2008,7 +2042,7 @@
public void onReceive(Context context, Intent intent) {
// on background handler thread, and verified UPDATE_DEVICE_STATS
// permission above.
- performPoll(FLAG_PERSIST_ALL);
+ performPoll(FLAG_PERSIST_ALL, maybeCreatePollEvent(POLL_REASON_PERIODIC));
// verify that we're watching global alert
registerGlobalAlert();
@@ -2072,19 +2106,20 @@
public void handleOnCollapsedRatTypeChanged() {
// Protect service from frequently updating. Remove pending messages if any.
mHandler.removeMessages(MSG_NOTIFY_NETWORK_STATUS);
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(MSG_NOTIFY_NETWORK_STATUS), mSettings.getPollDelay());
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_NOTIFY_NETWORK_STATUS,
+ POLL_REASON_RAT_CHANGED), mSettings.getPollDelay());
}
private void handleNotifyNetworkStatus(
Network[] defaultNetworks,
NetworkStateSnapshot[] snapshots,
- String activeIface) {
+ String activeIface,
+ @Nullable PollEvent event) {
synchronized (mStatsLock) {
mWakeLock.acquire();
try {
mActiveIface = activeIface;
- handleNotifyNetworkStatusLocked(defaultNetworks, snapshots);
+ handleNotifyNetworkStatusLocked(defaultNetworks, snapshots, event);
} finally {
mWakeLock.release();
}
@@ -2098,7 +2133,7 @@
*/
@GuardedBy("mStatsLock")
private void handleNotifyNetworkStatusLocked(@NonNull Network[] defaultNetworks,
- @NonNull NetworkStateSnapshot[] snapshots) {
+ @NonNull NetworkStateSnapshot[] snapshots, @Nullable PollEvent event) {
if (!mSystemReady) return;
if (LOGV) Log.v(TAG, "handleNotifyNetworkStatusLocked()");
@@ -2108,7 +2143,7 @@
// poll, but only persist network stats to keep codepath fast. UID stats
// will be persisted during next alarm poll event.
- performPollLocked(FLAG_PERSIST_NETWORK);
+ performPollLocked(FLAG_PERSIST_NETWORK, event);
// Rebuild active interfaces based on connected networks
mActiveIfaces.clear();
@@ -2325,12 +2360,12 @@
}
}
- private void performPoll(int flags) {
+ private void performPoll(int flags, @Nullable PollEvent event) {
synchronized (mStatsLock) {
mWakeLock.acquire();
try {
- performPollLocked(flags);
+ performPollLocked(flags, event);
} finally {
mWakeLock.release();
}
@@ -2342,11 +2377,15 @@
* {@link NetworkStatsHistory}.
*/
@GuardedBy("mStatsLock")
- private void performPollLocked(int flags) {
+ private void performPollLocked(int flags, @Nullable PollEvent event) {
if (!mSystemReady) return;
if (LOGV) Log.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
Trace.traceBegin(TRACE_TAG_NETWORK, "performPollLocked");
+ if (mSupportEventLogger) {
+ mEventLogger.logPollEvent(flags, event);
+ }
+
final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0;
final boolean persistUid = (flags & FLAG_PERSIST_UID) != 0;
final boolean persistForce = (flags & FLAG_PERSIST_FORCE) != 0;
@@ -2546,7 +2585,7 @@
if (LOGV) Log.v(TAG, "removeUidsLocked() for UIDs " + Arrays.toString(uids));
// Perform one last poll before removing
- performPollLocked(FLAG_PERSIST_ALL);
+ performPollLocked(FLAG_PERSIST_ALL, maybeCreatePollEvent(POLL_REASON_REMOVE_UIDS));
mUidRecorder.removeUidsLocked(uids);
mUidTagRecorder.removeUidsLocked(uids);
@@ -2629,7 +2668,8 @@
}
if (poll) {
- performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE);
+ performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE,
+ maybeCreatePollEvent(POLL_REASON_DUMPSYS));
pw.println("Forced poll");
return;
}
@@ -2689,6 +2729,7 @@
pw.println("(failed to dump platform legacy stats import counters)");
}
}
+ pw.println(CONFIG_ENABLE_NETWORK_STATS_EVENT_LOGGER + ": " + mSupportEventLogger);
pw.decreaseIndent();
@@ -2746,6 +2787,10 @@
pw.decreaseIndent();
pw.println();
+ if (mSupportEventLogger) {
+ mEventLogger.dump(pw);
+ }
+
pw.println("Stats Providers:");
pw.increaseIndent();
invokeForAllStatsProviderCallbacks((cb) -> {
@@ -3215,6 +3260,22 @@
}
}
+ private final boolean mSupportEventLogger;
+ @GuardedBy("mStatsLock")
+ @Nullable
+ private final NetworkStatsEventLogger mEventLogger;
+
+ /**
+ * Create a PollEvent instance if the feature is enabled.
+ */
+ @Nullable
+ public PollEvent maybeCreatePollEvent(@NetworkStatsEventLogger.PollReason int reason) {
+ if (mSupportEventLogger) {
+ return new PollEvent(reason);
+ }
+ return null;
+ }
+
private class DropBoxNonMonotonicObserver implements NonMonotonicObserver<String> {
@Override
public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right,
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsEventLoggerTest.kt b/tests/unit/java/com/android/server/net/NetworkStatsEventLoggerTest.kt
new file mode 100644
index 0000000..9f2d4d3
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/NetworkStatsEventLoggerTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 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.net
+
+import android.util.IndentingPrintWriter
+import com.android.server.net.NetworkStatsEventLogger.MAX_POLL_REASON
+import com.android.server.net.NetworkStatsEventLogger.POLL_REASON_DUMPSYS
+import com.android.server.net.NetworkStatsEventLogger.POLL_REASON_FORCE_UPDATE
+import com.android.server.net.NetworkStatsEventLogger.POLL_REASON_GLOBAL_ALERT
+import com.android.server.net.NetworkStatsEventLogger.POLL_REASON_NETWORK_STATUS_CHANGED
+import com.android.server.net.NetworkStatsEventLogger.POLL_REASON_OPEN_SESSION
+import com.android.server.net.NetworkStatsEventLogger.POLL_REASON_PERIODIC
+import com.android.server.net.NetworkStatsEventLogger.POLL_REASON_RAT_CHANGED
+import com.android.server.net.NetworkStatsEventLogger.POLL_REASON_REG_CALLBACK
+import com.android.server.net.NetworkStatsEventLogger.PollEvent
+import com.android.server.net.NetworkStatsEventLogger.PollEvent.pollReasonNameOf
+import com.android.testutils.DevSdkIgnoreRunner
+import java.io.StringWriter
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val TEST_PERSIST_FLAG = 0x101
+
+@RunWith(DevSdkIgnoreRunner::class)
+class NetworkStatsEventLoggerTest {
+ val logger = NetworkStatsEventLogger()
+ val stringWriter = TestStringWriter()
+ val pw = IndentingPrintWriter(stringWriter)
+
+ @Test
+ fun testDump_invalid() {
+ // Verify it won't crash.
+ logger.dump(pw)
+ // Clear output buffer.
+ stringWriter.getOutputAndClear()
+
+ // Verify log invalid event throws. And nothing output in the dump.
+ val invalidReasons = listOf(-1, MAX_POLL_REASON + 1)
+ invalidReasons.forEach {
+ assertFailsWith<IllegalArgumentException> {
+ logger.logPollEvent(TEST_PERSIST_FLAG, PollEvent(it))
+ }
+ logger.dumpRecentPollEvents(pw)
+ val output = stringWriter.getOutputAndClear()
+ assertStringNotContains(output, pollReasonNameOf(it))
+ }
+ }
+
+ @Test
+ fun testDump_valid() {
+ // Choose arbitrary set of reasons for testing.
+ val loggedReasons = listOf(
+ POLL_REASON_GLOBAL_ALERT,
+ POLL_REASON_FORCE_UPDATE,
+ POLL_REASON_DUMPSYS,
+ POLL_REASON_PERIODIC,
+ POLL_REASON_RAT_CHANGED
+ )
+ val nonLoggedReasons = listOf(
+ POLL_REASON_NETWORK_STATUS_CHANGED,
+ POLL_REASON_OPEN_SESSION,
+ POLL_REASON_REG_CALLBACK)
+
+ // Add some valid records.
+ loggedReasons.forEach {
+ logger.logPollEvent(TEST_PERSIST_FLAG, PollEvent(it))
+ }
+
+ // Collect dumps.
+ logger.dumpRecentPollEvents(pw)
+ val outputRecentEvents = stringWriter.getOutputAndClear()
+ logger.dumpPollCountsPerReason(pw)
+ val outputCountsPerReason = stringWriter.getOutputAndClear()
+
+ // Verify the output contains at least necessary information.
+ loggedReasons.forEach {
+ // Verify all events are shown in the recent event dump.
+ val eventString = PollEvent(it).toString()
+ assertStringContains(outputRecentEvents, TEST_PERSIST_FLAG.toString())
+ assertStringContains(eventString, pollReasonNameOf(it))
+ assertStringContains(outputRecentEvents, eventString)
+ // Verify counts are 1 for each reason.
+ assertCountForReason(outputCountsPerReason, it, 1)
+ }
+
+ // Verify the output remains untouched for other reasons.
+ nonLoggedReasons.forEach {
+ assertStringNotContains(outputRecentEvents, PollEvent(it).toString())
+ assertCountForReason(outputCountsPerReason, it, 0)
+ }
+ }
+
+ @Test
+ fun testDump_maxEventLogs() {
+ // Choose arbitrary reason.
+ val reasonToBeTested = POLL_REASON_PERIODIC
+ val repeatCount = NetworkStatsEventLogger.MAX_EVENTS_LOGS * 2
+
+ // Collect baseline.
+ logger.dumpRecentPollEvents(pw)
+ val lineCountBaseLine = getLineCount(stringWriter.getOutputAndClear())
+
+ repeat(repeatCount) {
+ logger.logPollEvent(TEST_PERSIST_FLAG, PollEvent(reasonToBeTested))
+ }
+
+ // Collect dump.
+ logger.dumpRecentPollEvents(pw)
+ val lineCountAfterTest = getLineCount(stringWriter.getOutputAndClear())
+
+ // Verify line count increment is limited.
+ assertEquals(
+ NetworkStatsEventLogger.MAX_EVENTS_LOGS,
+ lineCountAfterTest - lineCountBaseLine
+ )
+
+ // Verify count per reason increased for the testing reason.
+ logger.dumpPollCountsPerReason(pw)
+ val outputCountsPerReason = stringWriter.getOutputAndClear()
+ for (reason in 0..MAX_POLL_REASON) {
+ assertCountForReason(
+ outputCountsPerReason,
+ reason,
+ if (reason == reasonToBeTested) repeatCount else 0
+ )
+ }
+ }
+
+ private fun getLineCount(multilineString: String) = multilineString.lines().size
+
+ private fun assertStringContains(got: String, want: String) {
+ assertTrue(got.contains(want), "Wanted: $want, but got: $got")
+ }
+
+ private fun assertStringNotContains(got: String, unwant: String) {
+ assertFalse(got.contains(unwant), "Unwanted: $unwant, but got: $got")
+ }
+
+ /**
+ * Assert the reason and the expected count are at the same line.
+ */
+ private fun assertCountForReason(dump: String, reason: Int, expectedCount: Int) {
+ // Matches strings like "GLOBAL_ALERT: 50" but not too strict since the format might change.
+ val regex = Regex(pollReasonNameOf(reason) + "[^0-9]+" + expectedCount)
+ assertEquals(
+ 1,
+ regex.findAll(dump).count(),
+ "Unexpected output: $dump " + " for reason: " + pollReasonNameOf(reason)
+ )
+ }
+
+ class TestStringWriter : StringWriter() {
+ fun getOutputAndClear() = toString().also { buffer.setLength(0) }
+ }
+}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 9453617..e8d5c66 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -64,6 +64,8 @@
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_RAT_CHANGED;
+import static com.android.server.net.NetworkStatsEventLogger.PollEvent.pollReasonNameOf;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
import static com.android.server.net.NetworkStatsService.NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME;
import static com.android.server.net.NetworkStatsService.NETSTATS_IMPORT_FALLBACKS_COUNTER_NAME;
@@ -525,6 +527,11 @@
IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap, Handler handler) {
return mSkDestroyListener;
}
+
+ @Override
+ public boolean supportEventLogger(@NonNull Context cts) {
+ return true;
+ }
};
}
@@ -2674,4 +2681,14 @@
doReturn(null).when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
doTestDumpIfaceStatsMap("unknown");
}
+
+ // Basic test to ensure event logger dump is called.
+ // Note that tests to ensure detailed correctness is done in the dedicated tests.
+ // See NetworkStatsEventLoggerTest.
+ @Test
+ public void testDumpEventLogger() {
+ setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_UMTS);
+ final String dump = getDump();
+ assertDumpContains(dump, pollReasonNameOf(POLL_REASON_RAT_CHANGED));
+ }
}