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 0000000000..679837a89a --- /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 cc675501be..46afd3135e 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 android.text.format.DateUtils.SECOND_IN_MILLIS; 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * 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 mOpenSessionCallsPerCaller = new HashMap<>(); @@ -513,20 +527,21 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { IBpfMap 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { | 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } // 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 class NetworkStatsService extends INetworkStatsService.Stub { 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 class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { */ @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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * {@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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { pw.decreaseIndent(); pw.println(); + if (mSupportEventLogger) { + mEventLogger.dump(pw); + } + pw.println("Stats Providers:"); pw.increaseIndent(); invokeForAllStatsProviderCallbacks((cb) -> { @@ -3215,6 +3260,22 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } + 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 { @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 0000000000..9f2d4d3def --- /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 { + 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 9453617160..e8d5c66816 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.HOUR_IN_MILLIS; 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 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { IBpfMap cookieTagMap, Handler handler) { return mSkDestroyListener; } + + @Override + public boolean supportEventLogger(@NonNull Context cts) { + return true; + } }; } @@ -2674,4 +2681,14 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { 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)); + } }