Fetch tethering offload stats.
Make tethering offload register an ITetheringStatsProvider and fetch tethering stats from the hardware. Currently we fetch stats in the following cases: 1. Just after changing upstreams, we fetch stats from the previous upstream. 2. When we are polled by NetworkStatsService. Bug: 29337859 Bug: 32163131 Test: builds, boots Test: stats appear in tethering logs Change-Id: If744f2e06cb6a3095a40199936b9afb76eff7b56
This commit is contained in:
@@ -214,7 +214,7 @@ public class Tethering extends BaseNetworkObserver {
|
||||
final Handler smHandler = mTetherMasterSM.getHandler();
|
||||
mOffloadController = new OffloadController(smHandler,
|
||||
deps.getOffloadHardwareInterface(smHandler, mLog),
|
||||
mContext.getContentResolver(),
|
||||
mContext.getContentResolver(), mNMService,
|
||||
mLog);
|
||||
mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
|
||||
mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
|
||||
|
||||
@@ -16,24 +16,36 @@
|
||||
|
||||
package com.android.server.connectivity.tethering;
|
||||
|
||||
import static android.net.NetworkStats.SET_DEFAULT;
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
import static android.net.TrafficStats.UID_TETHERING;
|
||||
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.net.ITetheringStatsProvider;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.RouteInfo;
|
||||
import android.net.util.SharedLog;
|
||||
import android.os.Handler;
|
||||
import android.os.INetworkManagementService;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A class to encapsulate the business logic of programming the tethering
|
||||
@@ -44,6 +56,8 @@ import java.util.Set;
|
||||
public class OffloadController {
|
||||
private static final String TAG = OffloadController.class.getSimpleName();
|
||||
|
||||
private static final int STATS_FETCH_TIMEOUT_MS = 1000;
|
||||
|
||||
private final Handler mHandler;
|
||||
private final OffloadHardwareInterface mHwInterface;
|
||||
private final ContentResolver mContentResolver;
|
||||
@@ -59,14 +73,25 @@ public class OffloadController {
|
||||
// prefixes representing only the locally-assigned IP addresses.
|
||||
private Set<String> mLastLocalPrefixStrs;
|
||||
|
||||
// Maps upstream interface names to offloaded traffic statistics.
|
||||
private HashMap<String, OffloadHardwareInterface.ForwardedStats>
|
||||
mForwardedStats = new HashMap<>();
|
||||
|
||||
public OffloadController(Handler h, OffloadHardwareInterface hwi,
|
||||
ContentResolver contentResolver, SharedLog log) {
|
||||
ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
|
||||
mHandler = h;
|
||||
mHwInterface = hwi;
|
||||
mContentResolver = contentResolver;
|
||||
mLog = log.forSubComponent(TAG);
|
||||
mExemptPrefixes = new HashSet<>();
|
||||
mLastLocalPrefixStrs = new HashSet<>();
|
||||
|
||||
try {
|
||||
nms.registerTetheringStatsProvider(
|
||||
new OffloadTetheringStatsProvider(), getClass().getSimpleName());
|
||||
} catch (RemoteException e) {
|
||||
mLog.e("Cannot register offload stats provider: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@@ -138,6 +163,7 @@ public class OffloadController {
|
||||
|
||||
public void stop() {
|
||||
final boolean wasStarted = started();
|
||||
updateStatsForCurrentUpstream();
|
||||
mUpstreamLinkProperties = null;
|
||||
mHwInterface.stopOffloadControl();
|
||||
mControlInitialized = false;
|
||||
@@ -145,16 +171,76 @@ public class OffloadController {
|
||||
if (wasStarted) mLog.log("tethering offload stopped");
|
||||
}
|
||||
|
||||
private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
|
||||
@Override
|
||||
public NetworkStats getTetherStats() {
|
||||
NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
mHandler.post(() -> {
|
||||
try {
|
||||
NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||
entry.set = SET_DEFAULT;
|
||||
entry.tag = TAG_NONE;
|
||||
entry.uid = UID_TETHERING;
|
||||
|
||||
updateStatsForCurrentUpstream();
|
||||
|
||||
for (String iface : mForwardedStats.keySet()) {
|
||||
entry.iface = iface;
|
||||
entry.rxBytes = mForwardedStats.get(iface).rxBytes;
|
||||
entry.txBytes = mForwardedStats.get(iface).txBytes;
|
||||
stats.addValues(entry);
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
latch.await(STATS_FETCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
mLog.e("Tethering stats fetch timed out after " + STATS_FETCH_TIMEOUT_MS + "ms");
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUpdateStats(String iface) {
|
||||
if (TextUtils.isEmpty(iface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mForwardedStats.containsKey(iface)) {
|
||||
mForwardedStats.put(iface, new OffloadHardwareInterface.ForwardedStats());
|
||||
}
|
||||
mForwardedStats.get(iface).add(mHwInterface.getForwardedStats(iface));
|
||||
}
|
||||
|
||||
private void updateStatsForCurrentUpstream() {
|
||||
if (mUpstreamLinkProperties != null) {
|
||||
maybeUpdateStats(mUpstreamLinkProperties.getInterfaceName());
|
||||
}
|
||||
}
|
||||
|
||||
public void setUpstreamLinkProperties(LinkProperties lp) {
|
||||
if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return;
|
||||
|
||||
String prevUpstream = (mUpstreamLinkProperties != null) ?
|
||||
mUpstreamLinkProperties.getInterfaceName() : null;
|
||||
|
||||
mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null;
|
||||
|
||||
// TODO: examine return code and decide what to do if programming
|
||||
// upstream parameters fails (probably just wait for a subsequent
|
||||
// onOffloadEvent() callback to tell us offload is available again and
|
||||
// then reapply all state).
|
||||
computeAndPushLocalPrefixes();
|
||||
pushUpstreamParameters();
|
||||
|
||||
// Update stats after we've told the hardware to change routing so we don't miss packets.
|
||||
maybeUpdateStats(prevUpstream);
|
||||
}
|
||||
|
||||
public void setLocalPrefixes(Set<IpPrefix> localPrefixes) {
|
||||
|
||||
@@ -178,6 +178,7 @@ public class TetheringTest {
|
||||
mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager,
|
||||
mLooper.getLooper(), mSystemProperties,
|
||||
mTetheringDependencies);
|
||||
verify(mNMService).registerTetheringStatsProvider(any(), anyString());
|
||||
}
|
||||
|
||||
@After
|
||||
|
||||
@@ -16,26 +16,38 @@
|
||||
|
||||
package com.android.server.connectivity.tethering;
|
||||
|
||||
import static android.net.NetworkStats.SET_DEFAULT;
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
import static android.net.TrafficStats.UID_TETHERING;
|
||||
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
|
||||
import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyObject;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.net.ITetheringStatsProvider;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.RouteInfo;
|
||||
import android.net.util.SharedLog;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.INetworkManagementService;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.SettingNotFoundException;
|
||||
|
||||
@@ -66,11 +78,14 @@ public class OffloadControllerTest {
|
||||
@Mock private OffloadHardwareInterface mHardware;
|
||||
@Mock private ApplicationInfo mApplicationInfo;
|
||||
@Mock private Context mContext;
|
||||
@Mock private INetworkManagementService mNMService;
|
||||
private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
|
||||
ArgumentCaptor.forClass(ArrayList.class);
|
||||
private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor =
|
||||
ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class);
|
||||
private MockContentResolver mContentResolver;
|
||||
|
||||
@Before public void setUp() throws Exception {
|
||||
@Before public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
|
||||
when(mContext.getPackageName()).thenReturn("OffloadControllerTest");
|
||||
@@ -88,14 +103,23 @@ public class OffloadControllerTest {
|
||||
when(mHardware.initOffloadConfig()).thenReturn(true);
|
||||
when(mHardware.initOffloadControl(any(OffloadHardwareInterface.ControlCallback.class)))
|
||||
.thenReturn(true);
|
||||
when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats());
|
||||
}
|
||||
|
||||
private void enableOffload() {
|
||||
Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
|
||||
}
|
||||
|
||||
private OffloadController makeOffloadController() throws Exception {
|
||||
OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()),
|
||||
mHardware, mContentResolver, mNMService, new SharedLog("test"));
|
||||
verify(mNMService).registerTetheringStatsProvider(
|
||||
mTetherStatsProviderCaptor.capture(), anyString());
|
||||
return offload;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoSettingsValueDefaultDisabledDoesNotStart() {
|
||||
public void testNoSettingsValueDefaultDisabledDoesNotStart() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(1);
|
||||
try {
|
||||
@@ -103,8 +127,7 @@ public class OffloadControllerTest {
|
||||
fail();
|
||||
} catch (SettingNotFoundException expected) {}
|
||||
|
||||
final OffloadController offload =
|
||||
new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final InOrder inOrder = inOrder(mHardware);
|
||||
@@ -116,7 +139,7 @@ public class OffloadControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoSettingsValueDefaultEnabledDoesStart() {
|
||||
public void testNoSettingsValueDefaultEnabledDoesStart() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(0);
|
||||
try {
|
||||
@@ -124,8 +147,7 @@ public class OffloadControllerTest {
|
||||
fail();
|
||||
} catch (SettingNotFoundException expected) {}
|
||||
|
||||
final OffloadController offload =
|
||||
new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final InOrder inOrder = inOrder(mHardware);
|
||||
@@ -137,12 +159,11 @@ public class OffloadControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSettingsAllowsStart() {
|
||||
public void testSettingsAllowsStart() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
|
||||
|
||||
final OffloadController offload =
|
||||
new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final InOrder inOrder = inOrder(mHardware);
|
||||
@@ -154,12 +175,11 @@ public class OffloadControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSettingsDisablesStart() {
|
||||
public void testSettingsDisablesStart() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 1);
|
||||
|
||||
final OffloadController offload =
|
||||
new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final InOrder inOrder = inOrder(mHardware);
|
||||
@@ -174,8 +194,7 @@ public class OffloadControllerTest {
|
||||
setupFunctioningHardwareInterface();
|
||||
enableOffload();
|
||||
|
||||
final OffloadController offload =
|
||||
new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final InOrder inOrder = inOrder(mHardware);
|
||||
@@ -240,6 +259,7 @@ public class OffloadControllerTest {
|
||||
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
|
||||
eq(testIfName), eq(ipv4Addr), eq(null), eq(null));
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
final String ipv4Gateway = "192.0.2.1";
|
||||
@@ -249,6 +269,7 @@ public class OffloadControllerTest {
|
||||
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
|
||||
eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), eq(null));
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
final String ipv6Gw1 = "fe80::cafe";
|
||||
@@ -258,6 +279,7 @@ public class OffloadControllerTest {
|
||||
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
|
||||
eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
|
||||
ArrayList<String> v6gws = mStringArrayCaptor.getValue();
|
||||
assertEquals(1, v6gws.size());
|
||||
assertTrue(v6gws.contains(ipv6Gw1));
|
||||
@@ -270,6 +292,7 @@ public class OffloadControllerTest {
|
||||
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
|
||||
eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
|
||||
v6gws = mStringArrayCaptor.getValue();
|
||||
assertEquals(2, v6gws.size());
|
||||
assertTrue(v6gws.contains(ipv6Gw1));
|
||||
@@ -287,6 +310,7 @@ public class OffloadControllerTest {
|
||||
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
|
||||
eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
|
||||
v6gws = mStringArrayCaptor.getValue();
|
||||
assertEquals(2, v6gws.size());
|
||||
assertTrue(v6gws.contains(ipv6Gw1));
|
||||
@@ -321,6 +345,7 @@ public class OffloadControllerTest {
|
||||
assertEquals(2, v6gws.size());
|
||||
assertTrue(v6gws.contains(ipv6Gw1));
|
||||
assertTrue(v6gws.contains(ipv6Gw2));
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
// Completely identical LinkProperties updates are de-duped.
|
||||
@@ -331,4 +356,65 @@ public class OffloadControllerTest {
|
||||
anyObject(), anyObject(), anyObject(), anyObject());
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
private void assertNetworkStats(String iface, ForwardedStats stats, NetworkStats.Entry entry) {
|
||||
assertEquals(iface, entry.iface);
|
||||
assertEquals(stats.rxBytes, entry.rxBytes);
|
||||
assertEquals(stats.txBytes, entry.txBytes);
|
||||
assertEquals(SET_DEFAULT, entry.set);
|
||||
assertEquals(TAG_NONE, entry.tag);
|
||||
assertEquals(UID_TETHERING, entry.uid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetForwardedStats() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
enableOffload();
|
||||
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final String ethernetIface = "eth1";
|
||||
final String mobileIface = "rmnet_data0";
|
||||
|
||||
ForwardedStats ethernetStats = new ForwardedStats();
|
||||
ethernetStats.rxBytes = 12345;
|
||||
ethernetStats.txBytes = 54321;
|
||||
|
||||
ForwardedStats mobileStats = new ForwardedStats();
|
||||
mobileStats.rxBytes = 999;
|
||||
mobileStats.txBytes = 99999;
|
||||
|
||||
when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
|
||||
when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats);
|
||||
|
||||
final LinkProperties lp = new LinkProperties();
|
||||
lp.setInterfaceName(ethernetIface);
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
|
||||
lp.setInterfaceName(mobileIface);
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
|
||||
lp.setInterfaceName(ethernetIface);
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
|
||||
ethernetStats.rxBytes = 100000;
|
||||
ethernetStats.txBytes = 100000;
|
||||
offload.setUpstreamLinkProperties(null);
|
||||
|
||||
NetworkStats stats = mTetherStatsProviderCaptor.getValue().getTetherStats();
|
||||
assertEquals(2, stats.size());
|
||||
|
||||
NetworkStats.Entry entry = null;
|
||||
int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1;
|
||||
int mobilePosition = 1 - ethernetPosition;
|
||||
|
||||
entry = stats.getValues(mobilePosition, entry);
|
||||
assertNetworkStats(mobileIface, mobileStats, entry);
|
||||
|
||||
ethernetStats.rxBytes = 12345 + 100000;
|
||||
ethernetStats.txBytes = 54321 + 100000;
|
||||
entry = stats.getValues(ethernetPosition, entry);
|
||||
assertNetworkStats(ethernetIface, ethernetStats, entry);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user