Loading services/core/java/com/android/server/connectivity/tethering/OffloadController.java +42 −25 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; /** /** Loading Loading @@ -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 Loading Loading @@ -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; } } Loading @@ -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) { Loading tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java +16 −0 Original line number Original line Diff line number Diff line Loading @@ -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()); Loading Loading
services/core/java/com/android/server/connectivity/tethering/OffloadController.java +42 −25 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; /** /** Loading Loading @@ -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 Loading Loading @@ -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; } } Loading @@ -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) { Loading
tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java +16 −0 Original line number Original line Diff line number Diff line Loading @@ -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()); Loading