Implement NetworkRequestsState atom

Bug: 278833398
Test: atest FrameworksNetTests
Change-Id: I95a8c8dddd2d8f6863fe64cd4a65ef48dfb5153a
This commit is contained in:
Danuta Brotikovskaya 2023-12-28 18:21:21 +00:00
commit 878fd25d84
7 changed files with 730 additions and 0 deletions

View file

@ -328,6 +328,7 @@ import com.android.server.connectivity.NetworkNotificationManager.NotificationTy
import com.android.server.connectivity.NetworkOffer;
import com.android.server.connectivity.NetworkPreferenceList;
import com.android.server.connectivity.NetworkRanker;
import com.android.server.connectivity.NetworkRequestStateStatsMetrics;
import com.android.server.connectivity.PermissionMonitor;
import com.android.server.connectivity.ProfileNetworkPreferenceInfo;
import com.android.server.connectivity.ProxyTracker;
@ -941,6 +942,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
private final IpConnectivityLog mMetricsLog;
@Nullable private final NetworkRequestStateStatsMetrics mNetworkRequestStateStatsMetrics;
@GuardedBy("mBandwidthRequests")
private final SparseArray<Integer> mBandwidthRequests = new SparseArray<>(10);
@ -1421,6 +1424,19 @@ public class ConnectivityService extends IConnectivityManager.Stub
return new AutomaticOnOffKeepaliveTracker(c, h);
}
/**
* @see NetworkRequestStateStatsMetrics
*/
public NetworkRequestStateStatsMetrics makeNetworkRequestStateStatsMetrics(
Context context) {
// We currently have network requests metric for Watch devices only
if (context.getPackageManager().hasSystemFeature(FEATURE_WATCH)) {
return new NetworkRequestStateStatsMetrics();
} else {
return null;
}
}
/**
* @see BatteryStatsManager
*/
@ -1654,6 +1670,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
new RequestInfoPerUidCounter(MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - 1);
mMetricsLog = logger;
mNetworkRequestStateStatsMetrics = mDeps.makeNetworkRequestStateStatsMetrics(mContext);
final NetworkRequest defaultInternetRequest = createDefaultRequest();
mDefaultRequest = new NetworkRequestInfo(
Process.myUid(), defaultInternetRequest, null,
@ -5324,6 +5341,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
updateSignalStrengthThresholds(network, "REGISTER", req);
}
}
} else if (req.isRequest() && mNetworkRequestStateStatsMetrics != null) {
mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(req);
}
}
@ -5541,6 +5560,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
if (req.isListen()) {
removeListenRequestFromNetworks(req);
} else if (req.isRequest() && mNetworkRequestStateStatsMetrics != null) {
mNetworkRequestStateStatsMetrics.onNetworkRequestRemoved(req);
}
}
nri.unlinkDeathRecipient();

View file

@ -0,0 +1,108 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.connectivity;
import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED;
import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED;
import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_UNKNOWN;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.SystemClock;
import com.android.net.module.util.BitUtils;
class NetworkRequestStateInfo {
private final NetworkRequest mNetworkRequest;
private final long mNetworkRequestReceivedTime;
private enum NetworkRequestState {
RECEIVED,
REMOVED
}
private NetworkRequestState mNetworkRequestState;
private int mNetworkRequestDurationMillis;
private final Dependencies mDependencies;
NetworkRequestStateInfo(NetworkRequest networkRequest,
Dependencies deps) {
mDependencies = deps;
mNetworkRequest = networkRequest;
mNetworkRequestReceivedTime = mDependencies.getElapsedRealtime();
mNetworkRequestDurationMillis = 0;
mNetworkRequestState = NetworkRequestState.RECEIVED;
}
NetworkRequestStateInfo(NetworkRequestStateInfo anotherNetworkRequestStateInfo) {
mDependencies = anotherNetworkRequestStateInfo.mDependencies;
mNetworkRequest = new NetworkRequest(anotherNetworkRequestStateInfo.mNetworkRequest);
mNetworkRequestReceivedTime = anotherNetworkRequestStateInfo.mNetworkRequestReceivedTime;
mNetworkRequestDurationMillis =
anotherNetworkRequestStateInfo.mNetworkRequestDurationMillis;
mNetworkRequestState = anotherNetworkRequestStateInfo.mNetworkRequestState;
}
public void setNetworkRequestRemoved() {
mNetworkRequestState = NetworkRequestState.REMOVED;
mNetworkRequestDurationMillis = (int) (
mDependencies.getElapsedRealtime() - mNetworkRequestReceivedTime);
}
public int getNetworkRequestStateStatsType() {
if (mNetworkRequestState == NetworkRequestState.RECEIVED) {
return NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED;
} else if (mNetworkRequestState == NetworkRequestState.REMOVED) {
return NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED;
} else {
return NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_UNKNOWN;
}
}
public int getRequestId() {
return mNetworkRequest.requestId;
}
public int getPackageUid() {
return mNetworkRequest.networkCapabilities.getRequestorUid();
}
public int getTransportTypes() {
return (int) BitUtils.packBits(mNetworkRequest.networkCapabilities.getTransportTypes());
}
public boolean getNetCapabilityNotMetered() {
return mNetworkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
}
public boolean getNetCapabilityInternet() {
return mNetworkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
}
public int getNetworkRequestDurationMillis() {
return mNetworkRequestDurationMillis;
}
/** Dependency class */
public static class Dependencies {
// Returns a timestamp with the time base of SystemClock.elapsedRealtime to keep durations
// relative to start time and avoid timezone change, including time spent in deep sleep.
public long getElapsedRealtime() {
return SystemClock.elapsedRealtime();
}
}
}

View file

@ -0,0 +1,228 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.connectivity;
import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED;
import android.annotation.NonNull;
import android.net.NetworkRequest;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ConnectivityStatsLog;
import java.util.ArrayDeque;
/**
* A Connectivity Service helper class to push atoms capturing network requests have been received
* and removed and its metadata.
*
* Atom events are logged in the ConnectivityStatsLog. Network request id: network request metadata
* hashmap is stored to calculate network request duration when it is removed.
*
* Note that this class is not thread-safe. The instance of the class needs to be
* synchronized in the callers when being used in multiple threads.
*/
public class NetworkRequestStateStatsMetrics {
private static final String TAG = "NetworkRequestStateStatsMetrics";
private static final int CMD_SEND_PENDING_NETWORK_REQUEST_STATE_METRIC = 0;
private static final int CMD_SEND_MAYBE_ENQUEUE_NETWORK_REQUEST_STATE_METRIC = 1;
@VisibleForTesting
static final int MAX_QUEUED_REQUESTS = 20;
// Stats logging frequency is limited to 10 ms at least, 500ms are taken as a safely margin
// for cases of longer periods of frequent network requests.
private static final int ATOM_INTERVAL_MS = 500;
private final StatsLoggingHandler mStatsLoggingHandler;
private final Dependencies mDependencies;
private final NetworkRequestStateInfo.Dependencies mNRStateInfoDeps;
private final SparseArray<NetworkRequestStateInfo> mNetworkRequestsActive;
public NetworkRequestStateStatsMetrics() {
this(new Dependencies(), new NetworkRequestStateInfo.Dependencies());
}
@VisibleForTesting
NetworkRequestStateStatsMetrics(Dependencies deps,
NetworkRequestStateInfo.Dependencies nrStateInfoDeps) {
mNetworkRequestsActive = new SparseArray<>();
mDependencies = deps;
mNRStateInfoDeps = nrStateInfoDeps;
HandlerThread handlerThread = mDependencies.makeHandlerThread(TAG);
handlerThread.start();
mStatsLoggingHandler = new StatsLoggingHandler(handlerThread.getLooper());
}
/**
* Register network request receive event, push RECEIVE atom
*
* @param networkRequest network request received
*/
public void onNetworkRequestReceived(NetworkRequest networkRequest) {
if (mNetworkRequestsActive.contains(networkRequest.requestId)) {
Log.w(TAG, "Received already registered network request, id = "
+ networkRequest.requestId);
} else {
Log.d(TAG, "Registered nr with ID = " + networkRequest.requestId
+ ", package_uid = " + networkRequest.networkCapabilities.getRequestorUid());
NetworkRequestStateInfo networkRequestStateInfo = new NetworkRequestStateInfo(
networkRequest, mNRStateInfoDeps);
mNetworkRequestsActive.put(networkRequest.requestId, networkRequestStateInfo);
mStatsLoggingHandler.sendMessage(Message.obtain(
mStatsLoggingHandler,
CMD_SEND_MAYBE_ENQUEUE_NETWORK_REQUEST_STATE_METRIC,
networkRequestStateInfo));
}
}
/**
* Register network request remove event, push REMOVE atom
*
* @param networkRequest network request removed
*/
public void onNetworkRequestRemoved(NetworkRequest networkRequest) {
NetworkRequestStateInfo networkRequestStateInfo = mNetworkRequestsActive.get(
networkRequest.requestId);
if (networkRequestStateInfo == null) {
Log.w(TAG, "This NR hasn't been registered. NR id = " + networkRequest.requestId);
} else {
Log.d(TAG, "Removed nr with ID = " + networkRequest.requestId);
mNetworkRequestsActive.remove(networkRequest.requestId);
networkRequestStateInfo = new NetworkRequestStateInfo(networkRequestStateInfo);
networkRequestStateInfo.setNetworkRequestRemoved();
mStatsLoggingHandler.sendMessage(Message.obtain(
mStatsLoggingHandler,
CMD_SEND_MAYBE_ENQUEUE_NETWORK_REQUEST_STATE_METRIC,
networkRequestStateInfo));
}
}
/** Dependency class */
public static class Dependencies {
/**
* Creates a thread with provided tag.
*
* @param tag for the thread.
*/
public HandlerThread makeHandlerThread(@NonNull final String tag) {
return new HandlerThread(tag);
}
/**
* @see Handler#sendMessageDelayed(Message, long)
*/
public void sendMessageDelayed(@NonNull Handler handler, int what, long delayMillis) {
handler.sendMessageDelayed(Message.obtain(handler, what), delayMillis);
}
/**
* Gets number of millis since event.
*
* @param eventTimeMillis long timestamp in millis when the event occurred.
*/
public long getMillisSinceEvent(long eventTimeMillis) {
return SystemClock.elapsedRealtime() - eventTimeMillis;
}
/**
* Writes a NETWORK_REQUEST_STATE_CHANGED event to ConnectivityStatsLog.
*
* @param networkRequestStateInfo NetworkRequestStateInfo containing network request info.
*/
public void writeStats(NetworkRequestStateInfo networkRequestStateInfo) {
ConnectivityStatsLog.write(
NETWORK_REQUEST_STATE_CHANGED,
networkRequestStateInfo.getPackageUid(),
networkRequestStateInfo.getTransportTypes(),
networkRequestStateInfo.getNetCapabilityNotMetered(),
networkRequestStateInfo.getNetCapabilityInternet(),
networkRequestStateInfo.getNetworkRequestStateStatsType(),
networkRequestStateInfo.getNetworkRequestDurationMillis());
}
}
private class StatsLoggingHandler extends Handler {
private static final String TAG = "NetworkRequestsStateStatsLoggingHandler";
private final ArrayDeque<NetworkRequestStateInfo> mPendingState = new ArrayDeque<>();
private long mLastLogTime = 0;
StatsLoggingHandler(Looper looper) {
super(looper);
}
private void maybeEnqueueStatsMessage(NetworkRequestStateInfo networkRequestStateInfo) {
if (mPendingState.size() < MAX_QUEUED_REQUESTS) {
mPendingState.add(networkRequestStateInfo);
} else {
Log.w(TAG, "Too many network requests received within last " + ATOM_INTERVAL_MS
+ " ms, dropping the last network request (id = "
+ networkRequestStateInfo.getRequestId() + ") event");
return;
}
if (hasMessages(CMD_SEND_PENDING_NETWORK_REQUEST_STATE_METRIC)) {
return;
}
long millisSinceLastLog = mDependencies.getMillisSinceEvent(mLastLogTime);
if (millisSinceLastLog >= ATOM_INTERVAL_MS) {
sendMessage(
Message.obtain(this, CMD_SEND_PENDING_NETWORK_REQUEST_STATE_METRIC));
} else {
mDependencies.sendMessageDelayed(
this,
CMD_SEND_PENDING_NETWORK_REQUEST_STATE_METRIC,
ATOM_INTERVAL_MS - millisSinceLastLog);
}
}
@Override
public void handleMessage(Message msg) {
NetworkRequestStateInfo loggingInfo;
switch (msg.what) {
case CMD_SEND_MAYBE_ENQUEUE_NETWORK_REQUEST_STATE_METRIC:
maybeEnqueueStatsMessage((NetworkRequestStateInfo) msg.obj);
break;
case CMD_SEND_PENDING_NETWORK_REQUEST_STATE_METRIC:
mLastLogTime = SystemClock.elapsedRealtime();
if (!mPendingState.isEmpty()) {
loggingInfo = mPendingState.remove();
mDependencies.writeStats(loggingInfo);
if (!mPendingState.isEmpty()) {
mDependencies.sendMessageDelayed(
this,
CMD_SEND_PENDING_NETWORK_REQUEST_STATE_METRIC,
ATOM_INTERVAL_MS);
}
}
break;
default: // fall out
}
}
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.connectivity;
import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED;
import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED;
import static org.junit.Assert.assertEquals;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Build;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@RunWith(DevSdkIgnoreRunner.class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
public class NetworkRequestStateInfoTest {
@Mock
private NetworkRequestStateInfo.Dependencies mDependencies;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testSetNetworkRequestRemoved() {
final long nrStartTime = 1L;
final long nrEndTime = 101L;
NetworkRequest notMeteredWifiNetworkRequest = new NetworkRequest(
new NetworkCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, true),
0, 1, NetworkRequest.Type.REQUEST
);
// This call will be used to calculate NR received time
Mockito.when(mDependencies.getElapsedRealtime()).thenReturn(nrStartTime);
NetworkRequestStateInfo networkRequestStateInfo = new NetworkRequestStateInfo(
notMeteredWifiNetworkRequest, mDependencies);
// This call will be used to calculate NR removed time
Mockito.when(mDependencies.getElapsedRealtime()).thenReturn(nrEndTime);
networkRequestStateInfo.setNetworkRequestRemoved();
assertEquals(
nrEndTime - nrStartTime,
networkRequestStateInfo.getNetworkRequestDurationMillis());
assertEquals(networkRequestStateInfo.getNetworkRequestStateStatsType(),
NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED);
}
@Test
public void testCheckInitialState() {
NetworkRequestStateInfo networkRequestStateInfo = new NetworkRequestStateInfo(
new NetworkRequest(new NetworkCapabilities(), 0, 1, NetworkRequest.Type.REQUEST),
mDependencies);
assertEquals(networkRequestStateInfo.getNetworkRequestStateStatsType(),
NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED);
}
}

View file

@ -0,0 +1,201 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.connectivity;
import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED;
import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.testutils.HandlerUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class NetworkRequestStateStatsMetricsTest {
@Mock
private NetworkRequestStateStatsMetrics.Dependencies mNRStateStatsDeps;
@Mock
private NetworkRequestStateInfo.Dependencies mNRStateInfoDeps;
@Captor
private ArgumentCaptor<Handler> mHandlerCaptor;
@Captor
private ArgumentCaptor<Integer> mMessageWhatCaptor;
private NetworkRequestStateStatsMetrics mNetworkRequestStateStatsMetrics;
private HandlerThread mHandlerThread;
private static final int TEST_REQUEST_ID = 10;
private static final int TEST_PACKAGE_UID = 20;
private static final int TIMEOUT_MS = 30_000;
private static final NetworkRequest NOT_METERED_WIFI_NETWORK_REQUEST = new NetworkRequest(
new NetworkCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, true)
.setCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET, false)
.setRequestorUid(TEST_PACKAGE_UID),
0, TEST_REQUEST_ID, NetworkRequest.Type.REQUEST
);
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mHandlerThread = new HandlerThread("NetworkRequestStateStatsMetrics");
Mockito.when(mNRStateStatsDeps.makeHandlerThread("NetworkRequestStateStatsMetrics"))
.thenReturn(mHandlerThread);
Mockito.when(mNRStateStatsDeps.getMillisSinceEvent(anyLong())).thenReturn(0L);
Mockito.doAnswer(invocation -> {
mHandlerCaptor.getValue().sendMessage(
Message.obtain(mHandlerCaptor.getValue(), mMessageWhatCaptor.getValue()));
return null;
}).when(mNRStateStatsDeps).sendMessageDelayed(
mHandlerCaptor.capture(), mMessageWhatCaptor.capture(), anyLong());
mNetworkRequestStateStatsMetrics = new NetworkRequestStateStatsMetrics(
mNRStateStatsDeps, mNRStateInfoDeps);
}
@Test
public void testNetworkRequestReceivedRemoved() {
final long nrStartTime = 1L;
final long nrEndTime = 101L;
// This call will be used to calculate NR received time
Mockito.when(mNRStateInfoDeps.getElapsedRealtime()).thenReturn(nrStartTime);
mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(NOT_METERED_WIFI_NETWORK_REQUEST);
HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
ArgumentCaptor<NetworkRequestStateInfo> networkRequestStateInfoCaptor =
ArgumentCaptor.forClass(NetworkRequestStateInfo.class);
verify(mNRStateStatsDeps, times(1))
.writeStats(networkRequestStateInfoCaptor.capture());
NetworkRequestStateInfo nrStateInfoSent = networkRequestStateInfoCaptor.getValue();
assertEquals(NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED,
nrStateInfoSent.getNetworkRequestStateStatsType());
assertEquals(NOT_METERED_WIFI_NETWORK_REQUEST.requestId, nrStateInfoSent.getRequestId());
assertEquals(TEST_PACKAGE_UID, nrStateInfoSent.getPackageUid());
assertEquals(1 << NetworkCapabilities.TRANSPORT_WIFI, nrStateInfoSent.getTransportTypes());
assertTrue(nrStateInfoSent.getNetCapabilityNotMetered());
assertFalse(nrStateInfoSent.getNetCapabilityInternet());
assertEquals(0, nrStateInfoSent.getNetworkRequestDurationMillis());
clearInvocations(mNRStateStatsDeps);
// This call will be used to calculate NR removed time
Mockito.when(mNRStateInfoDeps.getElapsedRealtime()).thenReturn(nrEndTime);
mNetworkRequestStateStatsMetrics.onNetworkRequestRemoved(NOT_METERED_WIFI_NETWORK_REQUEST);
HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
verify(mNRStateStatsDeps, times(1))
.writeStats(networkRequestStateInfoCaptor.capture());
nrStateInfoSent = networkRequestStateInfoCaptor.getValue();
assertEquals(NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED,
nrStateInfoSent.getNetworkRequestStateStatsType());
assertEquals(NOT_METERED_WIFI_NETWORK_REQUEST.requestId, nrStateInfoSent.getRequestId());
assertEquals(TEST_PACKAGE_UID, nrStateInfoSent.getPackageUid());
assertEquals(1 << NetworkCapabilities.TRANSPORT_WIFI, nrStateInfoSent.getTransportTypes());
assertTrue(nrStateInfoSent.getNetCapabilityNotMetered());
assertFalse(nrStateInfoSent.getNetCapabilityInternet());
assertEquals(nrEndTime - nrStartTime, nrStateInfoSent.getNetworkRequestDurationMillis());
}
@Test
public void testUnreceivedNetworkRequestRemoved() {
mNetworkRequestStateStatsMetrics.onNetworkRequestRemoved(NOT_METERED_WIFI_NETWORK_REQUEST);
HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
verify(mNRStateStatsDeps, never())
.writeStats(any(NetworkRequestStateInfo.class));
}
@Test
public void testNoMessagesWhenNetworkRequestReceived() {
mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(NOT_METERED_WIFI_NETWORK_REQUEST);
HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
verify(mNRStateStatsDeps, times(1))
.writeStats(any(NetworkRequestStateInfo.class));
clearInvocations(mNRStateStatsDeps);
mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(NOT_METERED_WIFI_NETWORK_REQUEST);
HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
verify(mNRStateStatsDeps, never())
.writeStats(any(NetworkRequestStateInfo.class));
}
@Test
public void testMessageQueueSizeLimitNotExceeded() {
// Imitate many events (MAX_QUEUED_REQUESTS) are coming together at once while
// the other event is being processed.
final ConditionVariable cv = new ConditionVariable();
mHandlerThread.getThreadHandler().post(() -> cv.block());
for (int i = 0; i < NetworkRequestStateStatsMetrics.MAX_QUEUED_REQUESTS / 2; i++) {
mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(new NetworkRequest(
new NetworkCapabilities().setRequestorUid(TEST_PACKAGE_UID),
0, i + 1, NetworkRequest.Type.REQUEST));
mNetworkRequestStateStatsMetrics.onNetworkRequestRemoved(new NetworkRequest(
new NetworkCapabilities().setRequestorUid(TEST_PACKAGE_UID),
0, i + 1, NetworkRequest.Type.REQUEST));
}
// When event queue is full, all other events should be dropped.
mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(new NetworkRequest(
new NetworkCapabilities().setRequestorUid(TEST_PACKAGE_UID),
0, 2 * NetworkRequestStateStatsMetrics.MAX_QUEUED_REQUESTS + 1,
NetworkRequest.Type.REQUEST));
cv.open();
// Check only first MAX_QUEUED_REQUESTS events are logged.
ArgumentCaptor<NetworkRequestStateInfo> networkRequestStateInfoCaptor =
ArgumentCaptor.forClass(NetworkRequestStateInfo.class);
verify(mNRStateStatsDeps, timeout(100).times(
NetworkRequestStateStatsMetrics.MAX_QUEUED_REQUESTS))
.writeStats(networkRequestStateInfoCaptor.capture());
for (int i = 0; i < NetworkRequestStateStatsMetrics.MAX_QUEUED_REQUESTS; i++) {
NetworkRequestStateInfo nrStateInfoSent =
networkRequestStateInfoCaptor.getAllValues().get(i);
assertEquals(i / 2 + 1, nrStateInfoSent.getRequestId());
assertEquals(
(i % 2 == 0)
? NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED
: NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED,
nrStateInfoSent.getNetworkRequestStateStatsType());
}
}
}

View file

@ -0,0 +1,83 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.os.Process
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.TestableNetworkCallback
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.argThat
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class CSNetworkRequestStateStatsMetricsTests : CSTest() {
private val CELL_INTERNET_NOT_METERED_NC = NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
.build().setRequestorUidAndPackageName(Process.myUid(), context.getPackageName())
private val CELL_INTERNET_NOT_METERED_NR = NetworkRequest.Builder()
.setCapabilities(CELL_INTERNET_NOT_METERED_NC).build()
@Before
fun setup() {
waitForIdle()
clearInvocations(networkRequestStateStatsMetrics)
}
@Test
fun testRequestTypeNRProduceMetrics() {
cm.requestNetwork(CELL_INTERNET_NOT_METERED_NR, TestableNetworkCallback())
waitForIdle()
verify(networkRequestStateStatsMetrics).onNetworkRequestReceived(
argThat{req -> req.networkCapabilities.equals(
CELL_INTERNET_NOT_METERED_NR.networkCapabilities)})
}
@Test
fun testListenTypeNRProduceNoMetrics() {
cm.registerNetworkCallback(CELL_INTERNET_NOT_METERED_NR, TestableNetworkCallback())
waitForIdle()
verify(networkRequestStateStatsMetrics, never()).onNetworkRequestReceived(any())
}
@Test
fun testRemoveRequestTypeNRProduceMetrics() {
val cb = TestableNetworkCallback()
cm.requestNetwork(CELL_INTERNET_NOT_METERED_NR, cb)
waitForIdle()
clearInvocations(networkRequestStateStatsMetrics)
cm.unregisterNetworkCallback(cb)
waitForIdle()
verify(networkRequestStateStatsMetrics).onNetworkRequestRemoved(
argThat{req -> req.networkCapabilities.equals(
CELL_INTERNET_NOT_METERED_NR.networkCapabilities)})
}
}

View file

@ -63,6 +63,7 @@ import com.android.server.connectivity.ClatCoordinator
import com.android.server.connectivity.ConnectivityFlags
import com.android.server.connectivity.MultinetworkPolicyTracker
import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies
import com.android.server.connectivity.NetworkRequestStateStatsMetrics
import com.android.server.connectivity.ProxyTracker
import com.android.testutils.visibleOnHandlerThread
import com.android.testutils.waitForIdle
@ -157,6 +158,7 @@ open class CSTest {
val netd = mock<INetd>()
val bpfNetMaps = mock<BpfNetMaps>()
val clatCoordinator = mock<ClatCoordinator>()
val networkRequestStateStatsMetrics = mock<NetworkRequestStateStatsMetrics>()
val proxyTracker = ProxyTracker(context, mock<Handler>(), 16 /* EVENT_PROXY_HAS_CHANGED */)
val alarmManager = makeMockAlarmManager()
val systemConfigManager = makeMockSystemConfigManager()
@ -197,6 +199,9 @@ open class CSTest {
MultinetworkPolicyTracker(c, h, r,
MultinetworkPolicyTrackerTestDependencies(connResources.get()))
override fun makeNetworkRequestStateStatsMetrics(c: Context) =
this@CSTest.networkRequestStateStatsMetrics
// All queried features must be mocked, because the test cannot hold the
// READ_DEVICE_CONFIG permission and device config utils use static methods for
// checking permissions.