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:
Lorenzo Colitti
2017-07-12 15:48:07 +09:00
parent ef7b2a13ce
commit 5a7dea1a8e
4 changed files with 190 additions and 17 deletions

View File

@@ -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);

View File

@@ -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) {

View File

@@ -178,6 +178,7 @@ public class TetheringTest {
mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager,
mLooper.getLooper(), mSystemProperties,
mTetheringDependencies);
verify(mNMService).registerTetheringStatsProvider(any(), anyString());
}
@After

View File

@@ -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);
}
}