Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b9ad0b18 authored by Nucca Chen's avatar Nucca Chen
Browse files

[BOT.13] Make offload coordinator poll interval configurable

Bug: 150736748
Test: BpfCoordinatorTest

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1315576
Merged-In: I7f8dde3b57ee14eb33edbe2fd383df33cccc231c
Change-Id: I70a6e5c8e765daf40cb738feb7fd70cf3c8f052d
(cherry picked from commit bea90943)
parent 1c63ba39
Loading
Loading
Loading
Loading
+15 −11
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkStats.UID_TETHERING;
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;

import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;

import android.app.usage.NetworkStatsManager;
import android.net.INetd;
import android.net.MacAddress;
@@ -69,8 +71,6 @@ import java.util.Objects;
public class BpfCoordinator {
    private static final String TAG = BpfCoordinator.class.getSimpleName();
    private static final int DUMP_TIMEOUT_MS = 10_000;
    @VisibleForTesting
    static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000; // TODO: Make it customizable.

    @VisibleForTesting
    enum StatsType {
@@ -154,14 +154,6 @@ public class BpfCoordinator {

    @VisibleForTesting
    public abstract static class Dependencies {
        /**
         * Get polling Interval in milliseconds.
         */
        public int getPerformPollInterval() {
            // TODO: Consider make this configurable.
            return DEFAULT_PERFORM_POLL_INTERVAL_MS;
        }

        /** Get handler. */
        @NonNull public abstract Handler getHandler();

@@ -403,6 +395,7 @@ public class BpfCoordinator {
            pw.println("Stats provider " + (mStatsProvider != null
                    ? "registered" : "not registered"));
            pw.println("Upstream quota: " + mInterfaceQuotas.toString());
            pw.println("Polling interval: " + getPollingInterval() + " ms");

            pw.println("Forwarding stats:");
            pw.increaseIndent();
@@ -745,6 +738,17 @@ public class BpfCoordinator {
        updateQuotaAndStatsFromSnapshot(tetherStatsList);
    }

    @VisibleForTesting
    int getPollingInterval() {
        // The valid range of interval is DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS..max_long.
        // Ignore the config value is less than the minimum polling interval. Note that the
        // minimum interval definition is invoked as OffloadController#isPollingStatsNeeded does.
        // TODO: Perhaps define a minimum polling interval constant.
        final TetheringConfiguration config = mDeps.getTetherConfig();
        final int configInterval = (config != null) ? config.getOffloadPollInterval() : 0;
        return Math.max(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, configInterval);
    }

    private void maybeSchedulePollingStats() {
        if (!mPollingStarted) return;

@@ -752,7 +756,7 @@ public class BpfCoordinator {
            mHandler.removeCallbacks(mScheduledPollingTask);
        }

        mHandler.postDelayed(mScheduledPollingTask, mDeps.getPerformPollInterval());
        mHandler.postDelayed(mScheduledPollingTask, getPollingInterval());
    }

    // Return forwarding rule map. This is used for testing only.
+58 −13
Original line number Diff line number Diff line
@@ -25,11 +25,10 @@ import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkStats.UID_TETHERING;
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;

import static com.android.networkstack.tethering.BpfCoordinator
        .DEFAULT_PERFORM_POLL_INTERVAL_MS;
import static com.android.networkstack.tethering.BpfCoordinator.StatsType;
import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE;
import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID;
import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -104,11 +103,6 @@ public class BpfCoordinatorTest {
    private final TestLooper mTestLooper = new TestLooper();
    private BpfCoordinator.Dependencies mDeps =
            new BpfCoordinator.Dependencies() {
            @Override
            public int getPerformPollInterval() {
                return DEFAULT_PERFORM_POLL_INTERVAL_MS;
            }

            @NonNull
            public Handler getHandler() {
                return new Handler(mTestLooper.getLooper());
@@ -183,9 +177,11 @@ public class BpfCoordinatorTest {
        return parcel;
    }

    // Set up specific tether stats list and wait for the stats cache is updated by polling thread
    // in the coordinator. Beware of that it is only used for the default polling interval.
    private void setTetherOffloadStatsList(TetherStatsParcel[] tetherStatsList) throws Exception {
        when(mNetd.tetherOffloadGetStats()).thenReturn(tetherStatsList);
        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
        mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
        waitForIdle();
    }

@@ -254,7 +250,7 @@ public class BpfCoordinatorTest {
        clearInvocations(mNetd);

        // Verify the polling update thread stopped.
        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
        mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
        waitForIdle();
        verify(mNetd, never()).tetherOffloadGetStats();
    }
@@ -279,20 +275,20 @@ public class BpfCoordinatorTest {
        when(mNetd.tetherOffloadGetStats()).thenReturn(
                new TetherStatsParcel[] {buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0)});
        mTetherStatsProvider.onSetAlert(100);
        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
        mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
        waitForIdle();
        mTetherStatsProviderCb.assertNoCallback();

        // Verify that notifyAlertReached fired when quota is reached.
        when(mNetd.tetherOffloadGetStats()).thenReturn(
                new TetherStatsParcel[] {buildTestTetherStatsParcel(mobileIfIndex, 50, 0, 50, 0)});
        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
        mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
        waitForIdle();
        mTetherStatsProviderCb.expectNotifyAlertReached();

        // Verify that set quota with UNLIMITED won't trigger any callback.
        mTetherStatsProvider.onSetAlert(QUOTA_UNLIMITED);
        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
        mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
        waitForIdle();
        mTetherStatsProviderCb.assertNoCallback();
    }
@@ -512,7 +508,7 @@ public class BpfCoordinatorTest {
        coordinator.startPolling();

        // The tether stats polling task should not be scheduled.
        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
        mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
        waitForIdle();
        verify(mNetd, never()).tetherOffloadGetStats();

@@ -559,4 +555,53 @@ public class BpfCoordinatorTest {
        assertNotNull(rules);
        assertEquals(1, rules.size());
    }

    @Test
    public void testTetheringConfigSetPollingInterval() throws Exception {
        setupFunctioningNetdInterface();

        final BpfCoordinator coordinator = makeBpfCoordinator();

        // [1] The default polling interval.
        coordinator.startPolling();
        assertEquals(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, coordinator.getPollingInterval());
        coordinator.stopPolling();

        // [2] Expect the invalid polling interval isn't applied. The valid range of interval is
        // DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS..max_long.
        for (final int interval
                : new int[] {0, 100, DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS - 1}) {
            when(mTetherConfig.getOffloadPollInterval()).thenReturn(interval);
            coordinator.startPolling();
            assertEquals(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, coordinator.getPollingInterval());
            coordinator.stopPolling();
        }

        // [3] Set a specific polling interval which is larger than default value.
        // Use a large polling interval to avoid flaky test because the time forwarding
        // approximation is used to verify the scheduled time of the polling thread.
        final int pollingInterval = 100_000;
        when(mTetherConfig.getOffloadPollInterval()).thenReturn(pollingInterval);
        coordinator.startPolling();

        // Expect the specific polling interval to be applied.
        assertEquals(pollingInterval, coordinator.getPollingInterval());

        // Start on a new polling time slot.
        mTestLooper.moveTimeForward(pollingInterval);
        waitForIdle();
        clearInvocations(mNetd);

        // Move time forward to 90% polling interval time. Expect that the polling thread has not
        // scheduled yet.
        mTestLooper.moveTimeForward((long) (pollingInterval * 0.9));
        waitForIdle();
        verify(mNetd, never()).tetherOffloadGetStats();

        // Move time forward to the remaining 10% polling interval time. Expect that the polling
        // thread has scheduled.
        mTestLooper.moveTimeForward((long) (pollingInterval * 0.1));
        waitForIdle();
        verify(mNetd).tetherOffloadGetStats();
    }
}