Support active session metrics sampling
This commit introduces a feature to monitor and upload active
tethering connection metrics. This will help us understand
the usage of tethering and improve the user experience.
The feature is disabled by default and will be gradually
rolled out.
Test: atest TetheringTests:com.android.networkstack.tethering.BpfCoordinatorTest
Test: manual test with:
adb shell device_config put tethering tether_active_sessions_metrics 3
m statsd_testdrive && statsd_testdrive -e 925
Bug: 354619988
Change-Id: I3b3f5ae068b127192c35541c6d9439b7367d54bd
This commit is contained in:
parent
0ea1136fcb
commit
d2581d64fb
2 changed files with 121 additions and 7 deletions
|
|
@ -87,6 +87,7 @@ import com.android.net.module.util.netlink.NetlinkConstants;
|
|||
import com.android.net.module.util.netlink.NetlinkUtils;
|
||||
import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim;
|
||||
import com.android.networkstack.tethering.util.TetheringUtils.ForwardedStats;
|
||||
import com.android.server.ConnectivityStatsLog;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
|
|
@ -151,6 +152,13 @@ public class BpfCoordinator {
|
|||
|
||||
@VisibleForTesting
|
||||
static final int CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS = 60_000;
|
||||
// The interval is set to 5 minutes to strike a balance between minimizing
|
||||
// the amount of metrics data uploaded and providing sufficient resolution
|
||||
// to track changes in forwarding rules. This choice considers the minimum
|
||||
// push metrics sampling interval of 5 minutes and the 3-minute timeout
|
||||
// for forwarding rules.
|
||||
@VisibleForTesting
|
||||
static final int CONNTRACK_METRICS_UPDATE_INTERVAL_MS = 300_000;
|
||||
@VisibleForTesting
|
||||
static final int NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED = 432_000;
|
||||
@VisibleForTesting
|
||||
|
|
@ -319,6 +327,12 @@ public class BpfCoordinator {
|
|||
|
||||
private final boolean mSupportActiveSessionsMetrics;
|
||||
|
||||
// Runnable that used by scheduling next refreshing of conntrack metrics sampling.
|
||||
private final Runnable mScheduledConntrackMetricsSampling = () -> {
|
||||
uploadConntrackMetricsSample();
|
||||
scheduleConntrackMetricsSampling();
|
||||
};
|
||||
|
||||
// TODO: add BpfMap<TetherDownstream64Key, TetherDownstream64Value> retrieving function.
|
||||
@VisibleForTesting
|
||||
public abstract static class Dependencies {
|
||||
|
|
@ -481,6 +495,12 @@ public class BpfCoordinator {
|
|||
}
|
||||
}
|
||||
|
||||
/** Send a TetheringActiveSessionsReported event. */
|
||||
public void sendTetheringActiveSessionsReported(int lastMaxSessionCount) {
|
||||
ConnectivityStatsLog.write(ConnectivityStatsLog.TETHERING_ACTIVE_SESSIONS_REPORTED,
|
||||
lastMaxSessionCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DeviceConfigUtils#isTetheringFeatureEnabled
|
||||
*/
|
||||
|
|
@ -530,38 +550,48 @@ public class BpfCoordinator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Start BPF tethering offload stats and conntrack timeout polling.
|
||||
* Start BPF tethering offload stats and conntrack polling.
|
||||
* Note that this can be only called on handler thread.
|
||||
*/
|
||||
private void startStatsAndConntrackTimeoutPolling() {
|
||||
private void startStatsAndConntrackPolling() {
|
||||
schedulePollingStats();
|
||||
scheduleConntrackTimeoutUpdate();
|
||||
if (mSupportActiveSessionsMetrics) {
|
||||
scheduleConntrackMetricsSampling();
|
||||
}
|
||||
|
||||
mLog.i("Polling started.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop BPF tethering offload stats and conntrack timeout polling.
|
||||
* Stop BPF tethering offload stats and conntrack polling.
|
||||
* The data limit cleanup and the tether stats maps cleanup are not implemented here.
|
||||
* These cleanups rely on all IpServers calling #removeIpv6DownstreamRule. After the
|
||||
* last rule is removed from the upstream, #removeIpv6DownstreamRule does the cleanup
|
||||
* functionality.
|
||||
* Note that this can be only called on handler thread.
|
||||
*/
|
||||
private void stopStatsAndConntrackTimeoutPolling() {
|
||||
private void stopStatsAndConntrackPolling() {
|
||||
// Stop scheduled polling conntrack timeout.
|
||||
if (mHandler.hasCallbacks(mScheduledConntrackTimeoutUpdate)) {
|
||||
mHandler.removeCallbacks(mScheduledConntrackTimeoutUpdate);
|
||||
}
|
||||
// Clear counters in case there is any counter unsync problem
|
||||
// Stop scheduled polling conntrack metrics sampling and
|
||||
// clear counters in case there is any counter unsync problem
|
||||
// previously due to possible bpf failures.
|
||||
// Normally this won't happen because all clients are cleared before
|
||||
// reaching here. See IpServer.BaseServingState#exit().
|
||||
if (mSupportActiveSessionsMetrics) {
|
||||
if (mHandler.hasCallbacks(mScheduledConntrackMetricsSampling)) {
|
||||
mHandler.removeCallbacks(mScheduledConntrackMetricsSampling);
|
||||
}
|
||||
final int currentCount = mBpfConntrackEventConsumer.getCurrentConnectionCount();
|
||||
if (currentCount != 0) {
|
||||
Log.wtf(TAG, "Unexpected CurrentConnectionCount: " + currentCount);
|
||||
}
|
||||
// Avoid sending metrics when tethering is about to close.
|
||||
// This leads to a missing final sample before disconnect
|
||||
// but avoids possibly duplicating the last metric in the upload.
|
||||
mBpfConntrackEventConsumer.clearConnectionCounters();
|
||||
}
|
||||
// Stop scheduled polling stats and poll the latest stats from BPF maps.
|
||||
|
|
@ -897,7 +927,7 @@ public class BpfCoordinator {
|
|||
|
||||
// Start monitoring and polling when the first IpServer is added.
|
||||
if (mServedIpServers.isEmpty()) {
|
||||
startStatsAndConntrackTimeoutPolling();
|
||||
startStatsAndConntrackPolling();
|
||||
startConntrackMonitoring();
|
||||
mIpNeighborMonitor.start();
|
||||
mLog.i("Neighbor monitoring started.");
|
||||
|
|
@ -920,7 +950,7 @@ public class BpfCoordinator {
|
|||
|
||||
// Stop monitoring and polling when the last IpServer is removed.
|
||||
if (mServedIpServers.isEmpty()) {
|
||||
stopStatsAndConntrackTimeoutPolling();
|
||||
stopStatsAndConntrackPolling();
|
||||
stopConntrackMonitoring();
|
||||
mIpNeighborMonitor.stop();
|
||||
mLog.i("Neighbor monitoring stopped.");
|
||||
|
|
@ -2579,6 +2609,11 @@ public class BpfCoordinator {
|
|||
});
|
||||
}
|
||||
|
||||
private void uploadConntrackMetricsSample() {
|
||||
mDeps.sendTetheringActiveSessionsReported(
|
||||
mBpfConntrackEventConsumer.getLastMaxConnectionAndResetToCurrent());
|
||||
}
|
||||
|
||||
private void schedulePollingStats() {
|
||||
if (mHandler.hasCallbacks(mScheduledPollingStats)) {
|
||||
mHandler.removeCallbacks(mScheduledPollingStats);
|
||||
|
|
@ -2596,6 +2631,15 @@ public class BpfCoordinator {
|
|||
CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS);
|
||||
}
|
||||
|
||||
private void scheduleConntrackMetricsSampling() {
|
||||
if (mHandler.hasCallbacks(mScheduledConntrackMetricsSampling)) {
|
||||
mHandler.removeCallbacks(mScheduledConntrackMetricsSampling);
|
||||
}
|
||||
|
||||
mHandler.postDelayed(mScheduledConntrackMetricsSampling,
|
||||
CONNTRACK_METRICS_UPDATE_INTERVAL_MS);
|
||||
}
|
||||
|
||||
// Return IPv6 downstream forwarding rule map. This is used for testing only.
|
||||
// Note that this can be only called on handler thread.
|
||||
@NonNull
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH;
|
|||
import static com.android.net.module.util.netlink.StructNdMsg.NUD_FAILED;
|
||||
import static com.android.net.module.util.netlink.StructNdMsg.NUD_REACHABLE;
|
||||
import static com.android.net.module.util.netlink.StructNdMsg.NUD_STALE;
|
||||
import static com.android.networkstack.tethering.BpfCoordinator.CONNTRACK_METRICS_UPDATE_INTERVAL_MS;
|
||||
import static com.android.networkstack.tethering.BpfCoordinator.CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS;
|
||||
import static com.android.networkstack.tethering.BpfCoordinator.INVALID_MTU;
|
||||
import static com.android.networkstack.tethering.BpfCoordinator.NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED;
|
||||
|
|
@ -572,6 +573,11 @@ public class BpfCoordinatorTest {
|
|||
return mBpfErrorMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendTetheringActiveSessionsReported(int lastMaxSessionCount) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFeatureEnabled(Context context, String name) {
|
||||
return mFeatureFlags.getOrDefault(name, false);
|
||||
|
|
@ -2166,6 +2172,70 @@ public class BpfCoordinatorTest {
|
|||
assertConsumerCountersEquals(0);
|
||||
}
|
||||
|
||||
@FeatureFlag(name = TETHER_ACTIVE_SESSIONS_METRICS)
|
||||
// BPF IPv4 forwarding only supports on S+.
|
||||
@IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
@Test
|
||||
public void testSendActiveSessionsReported_metricsEnabled() throws Exception {
|
||||
doTestSendActiveSessionsReported(true);
|
||||
}
|
||||
|
||||
@FeatureFlag(name = TETHER_ACTIVE_SESSIONS_METRICS, enabled = false)
|
||||
@Test
|
||||
public void testSendActiveSessionsReported_metricsDisabled() throws Exception {
|
||||
doTestSendActiveSessionsReported(false);
|
||||
}
|
||||
|
||||
private void doTestSendActiveSessionsReported(final boolean supportActiveSessionsMetrics)
|
||||
throws Exception {
|
||||
final BpfCoordinator coordinator = makeBpfCoordinator();
|
||||
initBpfCoordinatorForRule4(coordinator);
|
||||
resetNetdAndBpfMaps();
|
||||
assertConsumerCountersEquals(0);
|
||||
|
||||
// Prepare the counter value.
|
||||
for (int i = 0; i < 5; i++) {
|
||||
mConsumer.accept(new TestConntrackEvent.Builder().setMsgType(
|
||||
IPCTNL_MSG_CT_NEW).setProto(IPPROTO_TCP).setRemotePort(i).build());
|
||||
}
|
||||
|
||||
// Then delete some 3 rules, 2 rules remaining.
|
||||
// The max count is 5 while current rules count is 2.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
mConsumer.accept(new TestConntrackEvent.Builder().setMsgType(
|
||||
IPCTNL_MSG_CT_DELETE).setProto(IPPROTO_TCP).setRemotePort(i).build());
|
||||
}
|
||||
|
||||
// Verify the method is not invoked when timer is not expired.
|
||||
waitForIdle();
|
||||
verify(mDeps, never()).sendTetheringActiveSessionsReported(anyInt());
|
||||
|
||||
// Verify metrics will be sent upon timer expiry.
|
||||
mTestLooper.moveTimeForward(CONNTRACK_METRICS_UPDATE_INTERVAL_MS);
|
||||
waitForIdle();
|
||||
if (supportActiveSessionsMetrics) {
|
||||
verify(mDeps).sendTetheringActiveSessionsReported(5);
|
||||
} else {
|
||||
verify(mDeps, never()).sendTetheringActiveSessionsReported(anyInt());
|
||||
}
|
||||
|
||||
// Verify next uploaded metrics will reflect the decreased rules count.
|
||||
mTestLooper.moveTimeForward(CONNTRACK_METRICS_UPDATE_INTERVAL_MS);
|
||||
waitForIdle();
|
||||
if (supportActiveSessionsMetrics) {
|
||||
verify(mDeps).sendTetheringActiveSessionsReported(2);
|
||||
} else {
|
||||
verify(mDeps, never()).sendTetheringActiveSessionsReported(anyInt());
|
||||
}
|
||||
|
||||
// Verify no metrics uploaded if polling stopped.
|
||||
clearInvocations(mDeps);
|
||||
coordinator.removeIpServer(mIpServer);
|
||||
mTestLooper.moveTimeForward(CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS);
|
||||
waitForIdle();
|
||||
verify(mDeps, never()).sendTetheringActiveSessionsReported(anyInt());
|
||||
}
|
||||
|
||||
private void setElapsedRealtimeNanos(long nanoSec) {
|
||||
mElapsedRealtimeNanos = nanoSec;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue