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

Commit ff12ba93 authored by Hugo Benichi's avatar Hugo Benichi Committed by Gerrit Code Review
Browse files

Merge "Tethering offload stats updates are eventually consistent"

parents 3f6a95e8 752c1287
Loading
Loading
Loading
Loading
+42 −25
Original line number Original line Diff line number Diff line
@@ -32,11 +32,13 @@ import android.net.NetworkStats;
import android.net.RouteInfo;
import android.net.RouteInfo;
import android.net.util.SharedLog;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.Handler;
import android.os.Looper;
import android.os.INetworkManagementService;
import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemClock;
import android.provider.Settings;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils;
import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;


import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.IndentingPrintWriter;


@@ -46,8 +48,10 @@ import java.net.InetAddress;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Objects;
import java.util.Set;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeUnit;


/**
/**
@@ -79,8 +83,8 @@ public class OffloadController {
    // Maps upstream interface names to offloaded traffic statistics.
    // Maps upstream interface names to offloaded traffic statistics.
    // Always contains the latest value received from the hardware for each interface, regardless of
    // Always contains the latest value received from the hardware for each interface, regardless of
    // whether offload is currently running on that interface.
    // whether offload is currently running on that interface.
    private HashMap<String, OffloadHardwareInterface.ForwardedStats>
    private ConcurrentHashMap<String, ForwardedStats> mForwardedStats =
            mForwardedStats = new HashMap<>();
            new ConcurrentHashMap<>(16, 0.75F, 1);


    // Maps upstream interface names to interface quotas.
    // Maps upstream interface names to interface quotas.
    // Always contains the latest value received from the framework for each interface, regardless
    // Always contains the latest value received from the framework for each interface, regardless
@@ -205,27 +209,29 @@ public class OffloadController {
    private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
    private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
        @Override
        @Override
        public NetworkStats getTetherStats(int how) {
        public NetworkStats getTetherStats(int how) {
            NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
            // getTetherStats() is the only function in OffloadController that can be called from
            // a different thread. Do not attempt to update stats by querying the offload HAL
            // synchronously from a different thread than our Handler thread. http://b/64771555.
            Runnable updateStats = () -> { updateStatsForCurrentUpstream(); };
            if (Looper.myLooper() == mHandler.getLooper()) {
                updateStats.run();
            } else {
                mHandler.post(updateStats);
            }


            // We can't just post to mHandler because we are mostly (but not always) called by
            NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
            // NetworkStatsService#performPollLocked, which is (currently) on the same thread as us.
            mHandler.runWithScissors(() -> {
                // We have to report both per-interface and per-UID stats, because offloaded traffic
                // is not seen by kernel interface counters.
            NetworkStats.Entry entry = new NetworkStats.Entry();
            NetworkStats.Entry entry = new NetworkStats.Entry();
            entry.set = SET_DEFAULT;
            entry.set = SET_DEFAULT;
            entry.tag = TAG_NONE;
            entry.tag = TAG_NONE;
            entry.uid = (how == STATS_PER_UID) ? UID_TETHERING : UID_ALL;
            entry.uid = (how == STATS_PER_UID) ? UID_TETHERING : UID_ALL;


                updateStatsForCurrentUpstream();
            for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {

                ForwardedStats value = kv.getValue();
                for (String iface : mForwardedStats.keySet()) {
                entry.iface = kv.getKey();
                    entry.iface = iface;
                entry.rxBytes = value.rxBytes;
                    entry.rxBytes = mForwardedStats.get(iface).rxBytes;
                entry.txBytes = value.txBytes;
                    entry.txBytes = mForwardedStats.get(iface).txBytes;
                stats.addValues(entry);
                stats.addValues(entry);
            }
            }
            }, 0);


            return stats;
            return stats;
        }
        }
@@ -247,10 +253,21 @@ public class OffloadController {
            return;
            return;
        }
        }


        if (!mForwardedStats.containsKey(iface)) {
        // Always called on the handler thread.
            mForwardedStats.put(iface, new OffloadHardwareInterface.ForwardedStats());
        //
        }
        // Use get()/put() instead of updating ForwardedStats in place because we can be called
        mForwardedStats.get(iface).add(mHwInterface.getForwardedStats(iface));
        // concurrently with getTetherStats. In combination with the guarantees provided by
        // ConcurrentHashMap, this ensures that getTetherStats always gets the most recent copy of
        // the stats for each interface, and does not observe partial writes where rxBytes is
        // updated and txBytes is not.
        ForwardedStats diff = mHwInterface.getForwardedStats(iface);
        ForwardedStats base = mForwardedStats.get(iface);
        if (base != null) {
            diff.add(base);
        }
        mForwardedStats.put(iface, diff);
        // diff is a new object, just created by getForwardedStats(). Therefore, anyone reading from
        // mForwardedStats (i.e., any caller of getTetherStats) will see the new stats immediately.
    }
    }


    private boolean maybeUpdateDataLimit(String iface) {
    private boolean maybeUpdateDataLimit(String iface) {
+16 −0
Original line number Original line Diff line number Diff line
@@ -404,23 +404,39 @@ public class OffloadControllerTest {
        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
        when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats);
        when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats);


        InOrder inOrder = inOrder(mHardware);

        final LinkProperties lp = new LinkProperties();
        final LinkProperties lp = new LinkProperties();
        lp.setInterfaceName(ethernetIface);
        lp.setInterfaceName(ethernetIface);
        offload.setUpstreamLinkProperties(lp);
        offload.setUpstreamLinkProperties(lp);
        // Previous upstream was null, so no stats are fetched.
        inOrder.verify(mHardware, never()).getForwardedStats(any());


        lp.setInterfaceName(mobileIface);
        lp.setInterfaceName(mobileIface);
        offload.setUpstreamLinkProperties(lp);
        offload.setUpstreamLinkProperties(lp);
        // Expect that we fetch stats from the previous upstream.
        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface));


        lp.setInterfaceName(ethernetIface);
        lp.setInterfaceName(ethernetIface);
        offload.setUpstreamLinkProperties(lp);
        offload.setUpstreamLinkProperties(lp);
        // Expect that we fetch stats from the previous upstream.
        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(mobileIface));


        ethernetStats = new ForwardedStats();
        ethernetStats.rxBytes = 100000;
        ethernetStats.rxBytes = 100000;
        ethernetStats.txBytes = 100000;
        ethernetStats.txBytes = 100000;
        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
        offload.setUpstreamLinkProperties(null);
        offload.setUpstreamLinkProperties(null);
        // Expect that we fetch stats from the previous upstream.
        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface));


        ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
        ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
        NetworkStats stats = provider.getTetherStats(STATS_PER_IFACE);
        NetworkStats stats = provider.getTetherStats(STATS_PER_IFACE);
        NetworkStats perUidStats = provider.getTetherStats(STATS_PER_UID);
        NetworkStats perUidStats = provider.getTetherStats(STATS_PER_UID);
        waitForIdle();
        // There is no current upstream, so no stats are fetched.
        inOrder.verify(mHardware, never()).getForwardedStats(eq(ethernetIface));
        inOrder.verifyNoMoreInteractions();


        assertEquals(2, stats.size());
        assertEquals(2, stats.size());
        assertEquals(2, perUidStats.size());
        assertEquals(2, perUidStats.size());