Merge "Remove service-side TrafficStats rate limit cache" into main

This commit is contained in:
Junyu Lai 2025-09-08 01:20:02 -07:00 • committed by Android (Google) Code Review
commit fe61faa220
6 changed files with 9 additions and 525 deletions

View file

@ -103,9 +103,6 @@ interface INetworkStatsService {
*/
void setStatsProviderWarningAndLimitAsync(String iface, long warning, long limit);
/** Clear TrafficStats rate-limit caches. */
void clearTrafficStatsRateLimitCaches();
/** Get rate-limit cache config. */
TrafficStatsRateLimitCacheConfig getRateLimitCacheConfig();
}

View file

@ -892,11 +892,6 @@ public class TrafficStats {
if (uidCache != null) {
uidCache.clear();
}
try {
getStatsService().clearTrafficStatsRateLimitCaches();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**

View file

@ -16,7 +16,6 @@
package com.android.server.net;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.NETWORK_STATS_PROVIDER;
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
@ -308,10 +307,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
static final String TRAFFIC_STATS_CACHE_EXPIRY_DURATION_NAME =
"trafficstats_cache_expiry_duration_ms";
static final String TRAFFIC_STATS_SERVICE_CACHE_MAX_ENTRIES_NAME =
static final String TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME =
"trafficstats_cache_max_entries";
static final int DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS = 1000;
static final int DEFAULT_TRAFFIC_STATS_SERVICE_CACHE_MAX_ENTRIES = 400;
static final int DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES = 400;
/**
* The delay time between to network stats update intents.
* Added to fix intent spams (b/343844995)
@ -489,25 +488,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
@GuardedBy("mStatsLock")
private long mLatestNetworkStatsUpdatedBroadcastScheduledTime = Long.MIN_VALUE;
@Nullable
private final TrafficStatsRateLimitCache mTrafficStatsTotalCache;
@Nullable
private final TrafficStatsRateLimitCache mTrafficStatsIfaceCache;
@Nullable
private final TrafficStatsRateLimitCache mTrafficStatsUidCache;
// A feature flag to control whether the client-side rate limit cache should be enabled.
@VisibleForTesting
public static final String TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG =
"trafficstats_client_rate_limit_cache_enabled_flag";
@VisibleForTesting
public static final String STORE_TRANSPORT_TYPES = "store_transport_types";
static final String TRAFFICSTATS_SERVICE_RATE_LIMIT_CACHE_ENABLED_FLAG =
"trafficstats_rate_limit_cache_enabled_flag";
static final String BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG =
"broadcast_network_stats_updated_rate_limit_enabled_flag";
private final boolean mIsTrafficStatsServiceRateLimitCacheEnabled;
private final int mTrafficStatsRateLimitCacheExpiryDuration;
private final int mTrafficStatsServiceRateLimitCacheMaxEntries;
private final TrafficStatsRateLimitCacheConfig mTrafficStatsRateLimitCacheClientSideConfig;
private final boolean mBroadcastNetworkStatsUpdatedRateLimitEnabled;
@ -706,32 +694,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
mTrafficStatsRateLimitCacheClientSideConfig =
mDeps.getTrafficStatsRateLimitCacheClientSideConfig(mContext);
// If the client side cache feature is enabled, disable the service side
// cache unconditionally.
mIsTrafficStatsServiceRateLimitCacheEnabled =
mDeps.isTrafficStatsServiceRateLimitCacheEnabled(mContext,
mTrafficStatsRateLimitCacheClientSideConfig.isCacheEnabled);
mBroadcastNetworkStatsUpdatedRateLimitEnabled =
mDeps.enabledBroadcastNetworkStatsUpdatedRateLimiting(mContext);
mTrafficStatsRateLimitCacheExpiryDuration =
mDeps.getTrafficStatsRateLimitCacheExpiryDuration();
mTrafficStatsServiceRateLimitCacheMaxEntries =
mDeps.getTrafficStatsServiceRateLimitCacheMaxEntries();
if (mIsTrafficStatsServiceRateLimitCacheEnabled) {
mTrafficStatsTotalCache = new TrafficStatsRateLimitCache(mClock,
mTrafficStatsRateLimitCacheExpiryDuration,
mTrafficStatsServiceRateLimitCacheMaxEntries);
mTrafficStatsIfaceCache = new TrafficStatsRateLimitCache(mClock,
mTrafficStatsRateLimitCacheExpiryDuration,
mTrafficStatsServiceRateLimitCacheMaxEntries);
mTrafficStatsUidCache = new TrafficStatsRateLimitCache(mClock,
mTrafficStatsRateLimitCacheExpiryDuration,
mTrafficStatsServiceRateLimitCacheMaxEntries);
} else {
mTrafficStatsTotalCache = null;
mTrafficStatsIfaceCache = null;
mTrafficStatsUidCache = null;
}
mSkDestroyListener = mDeps.makeSkDestroyListener((message) -> {
final StructInetDiagSockId sockId = message.inetDiagMsg.id;
@ -1012,52 +976,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS))
.setMaxEntries(getDeviceConfigPropertyInt(
NAMESPACE_TETHERING,
TRAFFIC_STATS_SERVICE_CACHE_MAX_ENTRIES_NAME,
DEFAULT_TRAFFIC_STATS_SERVICE_CACHE_MAX_ENTRIES))
TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME,
DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES))
.build();
return config;
}
/**
* Determines whether the service-side rate-limiting cache is enabled.
*
* The cache is enabled for devices running Android V+ or apps targeting SDK V+
* if the `TRAFFICSTATS_SERVICE_RATE_LIMIT_CACHE_ENABLED_FLAG` feature flag
* is enabled and client-side caching is disabled.
*
* This method should only be called once in the constructor,
* to ensure that the code does not need to deal with flag values changing at runtime.
*/
public boolean isTrafficStatsServiceRateLimitCacheEnabled(@NonNull Context ctx,
boolean clientCacheEnabled) {
return !clientCacheEnabled && DeviceConfigUtils.isTetheringFeatureNotChickenedOut(
ctx, TRAFFICSTATS_SERVICE_RATE_LIMIT_CACHE_ENABLED_FLAG);
}
/**
* Get TrafficStats rate-limit cache expiry.
*
* This method should only be called once in the constructor,
* to ensure that the code does not need to deal with flag values changing at runtime.
*/
public int getTrafficStatsRateLimitCacheExpiryDuration() {
return getDeviceConfigPropertyInt(
NAMESPACE_TETHERING, TRAFFIC_STATS_CACHE_EXPIRY_DURATION_NAME,
DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS);
}
/**
* Get TrafficStats service side rate-limit cache max entries.
*
* This method should only be called once in the constructor,
* to ensure that the code does not need to deal with flag values changing at runtime.
*/
public int getTrafficStatsServiceRateLimitCacheMaxEntries() {
return getDeviceConfigPropertyInt(
NAMESPACE_TETHERING, TRAFFIC_STATS_SERVICE_CACHE_MAX_ENTRIES_NAME,
DEFAULT_TRAFFIC_STATS_SERVICE_CACHE_MAX_ENTRIES);
}
/**
* Wrapper method for {@link CompatChanges#isChangeEnabled(long, int)}
*/
@ -2207,20 +2131,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|| mDeps.isChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, callingUid));
}
/**
* Determines whether to use the service-side cache for traffic stats rate limiting.
*
* This is based on the cache enabled feature flag. If enabled, the service-side cache
* is used for V+ devices or callers with V+ target sdk.
*
* @param callingUid The UID of the app making the request.
* @return True if the service-side cache should be used, false otherwise.
*/
private boolean useServiceSideCache(int callingUid) {
return mIsTrafficStatsServiceRateLimitCacheEnabled && (SdkLevel.isAtLeastV()
|| mDeps.isChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, callingUid));
}
@Nullable
@Override
public StatsResult getUidStats(int uid) {
@ -2228,13 +2138,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
if (callingUid != android.os.Process.SYSTEM_UID && callingUid != uid) {
return null;
}
final NetworkStats.Entry entry;
if (useServiceSideCache(callingUid)) {
entry = mTrafficStatsUidCache.getOrCompute(IFACE_ALL, uid,
() -> mDeps.nativeGetUidStat(uid));
} else entry = mDeps.nativeGetUidStat(uid);
return getStatsResultFromEntryOrNull(entry);
return getStatsResultFromEntryOrNull(mDeps.nativeGetUidStat(uid));
}
@Nullable
@ -2255,14 +2159,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
@Override
public StatsResult getIfaceStats(@NonNull String iface) {
Objects.requireNonNull(iface);
final NetworkStats.Entry entry;
if (useServiceSideCache(Binder.getCallingUid())) {
entry = mTrafficStatsIfaceCache.getOrCompute(iface, UID_ALL,
() -> getIfaceStatsInternal(iface));
} else entry = getIfaceStatsInternal(iface);
return getStatsResultFromEntryOrNull(entry);
return getStatsResultFromEntryOrNull(getIfaceStatsInternal(iface));
}
@Nullable
@ -2284,23 +2181,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
@Nullable
@Override
public StatsResult getTotalStats() {
final NetworkStats.Entry entry;
if (useServiceSideCache(Binder.getCallingUid())) {
entry = mTrafficStatsTotalCache.getOrCompute(
IFACE_ALL, UID_ALL, () -> getTotalStatsInternal());
} else entry = getTotalStatsInternal();
return getStatsResultFromEntryOrNull(entry);
}
@Override
public void clearTrafficStatsRateLimitCaches() {
PermissionUtils.enforceNetworkStackPermissionOr(mContext, NETWORK_SETTINGS);
if (mIsTrafficStatsServiceRateLimitCacheEnabled) {
mTrafficStatsUidCache.clear();
mTrafficStatsIfaceCache.clear();
mTrafficStatsTotalCache.clear();
}
return getStatsResultFromEntryOrNull(getTotalStatsInternal());
}
@Override
@ -3084,15 +2965,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
} catch (IOException e) {
pw.println("(failed to dump FastDataInput counters)");
}
pw.print("trafficstats.service.cache.isenabled",
mIsTrafficStatsServiceRateLimitCacheEnabled);
pw.println();
pw.print(TRAFFIC_STATS_CACHE_EXPIRY_DURATION_NAME,
mTrafficStatsRateLimitCacheExpiryDuration);
pw.println();
pw.print(TRAFFIC_STATS_SERVICE_CACHE_MAX_ENTRIES_NAME,
mTrafficStatsServiceRateLimitCacheMaxEntries);
pw.println();
pw.print("trafficstats.client.cache.config",
mTrafficStatsRateLimitCacheClientSideConfig);
pw.println();

View file

@ -1,115 +0,0 @@
/*
* 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.net;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.NetworkStats;
import com.android.net.module.util.LruCacheWithExpiry;
import java.time.Clock;
import java.util.Objects;
import java.util.function.Supplier;
/**
* A thread-safe cache for storing and retrieving {@link NetworkStats.Entry} objects,
* with an adjustable expiry duration to manage data freshness.
*
* @deprecated Use {@link LruCacheWithExpiry} instead.
*/
// TODO: Remove this when service side rate limit cache solution is removed.
class TrafficStatsRateLimitCache extends
LruCacheWithExpiry<TrafficStatsRateLimitCache.TrafficStatsCacheKey, NetworkStats.Entry> {
/**
* Constructs a new {@link TrafficStatsRateLimitCache} with the specified expiry duration.
*
* @param clock The {@link Clock} to use for determining timestamps.
* @param expiryDurationMs The expiry duration in milliseconds.
* @param maxSize Maximum number of entries.
*/
TrafficStatsRateLimitCache(@NonNull Clock clock, long expiryDurationMs, int maxSize) {
super(()-> clock.millis(), expiryDurationMs, maxSize, it -> !it.isEmpty());
}
public static class TrafficStatsCacheKey {
@Nullable
private final String mIface;
private final int mUid;
TrafficStatsCacheKey(@Nullable String iface, int uid) {
this.mIface = iface;
this.mUid = uid;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TrafficStatsCacheKey)) return false;
TrafficStatsCacheKey that = (TrafficStatsCacheKey) o;
return mUid == that.mUid && Objects.equals(mIface, that.mIface);
}
@Override
public int hashCode() {
return Objects.hash(mIface, mUid);
}
}
/**
* Retrieves a {@link NetworkStats.Entry} from the cache, associated with the given key.
*
* @param iface The interface name to include in the cache key. Null if not applicable.
* @param uid The UID to include in the cache key. {@code UID_ALL} if not applicable.
* @return The cached {@link NetworkStats.Entry}, or null if not found or expired.
*/
@Nullable
NetworkStats.Entry get(String iface, int uid) {
return super.get(new TrafficStatsCacheKey(iface, uid));
}
/**
* Retrieves a {@link NetworkStats.Entry} from the cache, associated with the given key.
* If the entry is not found in the cache or has expired, computes it using the provided
* {@code supplier} and stores the result in the cache.
*
* @param iface The interface name to include in the cache key. {@code IFACE_ALL}
* if not applicable.
* @param uid The UID to include in the cache key. {@code UID_ALL} if not applicable.
* @param supplier The {@link Supplier} to compute the {@link NetworkStats.Entry} if not found.
* @return The cached or computed {@link NetworkStats.Entry}, or null if not found, expired,
* or if the {@code supplier} returns null.
*/
@Nullable
NetworkStats.Entry getOrCompute(String iface, int uid,
@NonNull Supplier<NetworkStats.Entry> supplier) {
return super.getOrCompute(new TrafficStatsCacheKey(iface, uid), supplier);
}
/**
* Stores a {@link NetworkStats.Entry} in the cache, associated with the given key.
*
* @param iface The interface name to include in the cache key. Null if not applicable.
* @param uid The UID to include in the cache key. {@code UID_ALL} if not applicable.
* @param entry The {@link NetworkStats.Entry} to store in the cache.
*/
void put(String iface, int uid, @NonNull final NetworkStats.Entry entry) {
super.put(new TrafficStatsCacheKey(iface, uid), entry);
}
}

View file

@ -73,14 +73,13 @@ import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_PO
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
import static com.android.server.net.NetworkStatsService.BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG;
import static com.android.server.net.NetworkStatsService.DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS;
import static com.android.server.net.NetworkStatsService.DEFAULT_TRAFFIC_STATS_SERVICE_CACHE_MAX_ENTRIES;
import static com.android.server.net.NetworkStatsService.DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES;
import static com.android.server.net.NetworkStatsService.NETSTATS_FASTDATAINPUT_FALLBACKS_COUNTER_NAME;
import static com.android.server.net.NetworkStatsService.NETSTATS_FASTDATAINPUT_SUCCESSES_COUNTER_NAME;
import static com.android.server.net.NetworkStatsService.NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME;
import static com.android.server.net.NetworkStatsService.NETSTATS_IMPORT_FALLBACKS_COUNTER_NAME;
import static com.android.server.net.NetworkStatsService.NETSTATS_IMPORT_SUCCESSES_COUNTER_NAME;
import static com.android.server.net.NetworkStatsService.TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG;
import static com.android.server.net.NetworkStatsService.TRAFFICSTATS_SERVICE_RATE_LIMIT_CACHE_ENABLED_FLAG;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -130,7 +129,6 @@ import android.net.TestNetworkSpecifier;
import android.net.TetherStatsParcel;
import android.net.TetheringManager;
import android.net.UnderlyingNetworkInfo;
import android.net.netstats.StatsResult;
import android.net.netstats.TrafficStatsRateLimitCacheConfig;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.wifi.WifiInfo;
@ -625,29 +623,12 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
return mFeatureFlags.getOrDefault(feature, true);
}
@Override
public boolean isTrafficStatsServiceRateLimitCacheEnabled(Context ctx,
boolean isClientCacheEnabled) {
return !isClientCacheEnabled && mFeatureFlags.getOrDefault(
TRAFFICSTATS_SERVICE_RATE_LIMIT_CACHE_ENABLED_FLAG, false);
}
@Override
public boolean enabledBroadcastNetworkStatsUpdatedRateLimiting(Context ctx) {
return mFeatureFlags.getOrDefault(
BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG, true);
}
@Override
public int getTrafficStatsRateLimitCacheExpiryDuration() {
return DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS;
}
@Override
public int getTrafficStatsServiceRateLimitCacheMaxEntries() {
return DEFAULT_TRAFFIC_STATS_SERVICE_CACHE_MAX_ENTRIES;
}
@Override
public TrafficStatsRateLimitCacheConfig getTrafficStatsRateLimitCacheClientSideConfig(
@NonNull Context ctx) {
@ -656,7 +637,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
.setIsCacheEnabled(mFeatureFlags.getOrDefault(
TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG, false))
.setExpiryDurationMs(DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS)
.setMaxEntries(DEFAULT_TRAFFIC_STATS_SERVICE_CACHE_MAX_ENTRIES)
.setMaxEntries(DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES)
.build();
return config;
}
@ -2573,102 +2554,6 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
assertTrue(mService.getRateLimitCacheConfig().isCacheEnabled);
}
@FeatureFlag(name = TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG)
@FeatureFlag(name = TRAFFICSTATS_SERVICE_RATE_LIMIT_CACHE_ENABLED_FLAG)
@Test
public void testTrafficStatsRateLimitCache_clientCacheEnabledDisableServiceCache()
throws Exception {
mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, true);
doTestTrafficStatsRateLimitCache(false /* expectCached */);
}
@FeatureFlag(name = TRAFFICSTATS_SERVICE_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
@Test
public void testTrafficStatsRateLimitCache_disabledWithCompatChangeEnabled() throws Exception {
mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, true);
doTestTrafficStatsRateLimitCache(false /* expectCached */);
}
@FeatureFlag(name = TRAFFICSTATS_SERVICE_RATE_LIMIT_CACHE_ENABLED_FLAG)
@Test
public void testTrafficStatsRateLimitCache_enabledWithCompatChangeEnabled() throws Exception {
mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, true);
doTestTrafficStatsRateLimitCache(true /* expectCached */);
}
@FeatureFlag(name = TRAFFICSTATS_SERVICE_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
@Test
public void testTrafficStatsRateLimitCache_disabledWithCompatChangeDisabled() throws Exception {
mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, false);
doTestTrafficStatsRateLimitCache(false /* expectCached */);
}
@FeatureFlag(name = TRAFFICSTATS_SERVICE_RATE_LIMIT_CACHE_ENABLED_FLAG)
@DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
public void testTrafficStatsRateLimitCache_enabledWithCompatChangeDisabled_belowV()
throws Exception {
mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, false);
doTestTrafficStatsRateLimitCache(false /* expectCached */);
}
@FeatureFlag(name = TRAFFICSTATS_SERVICE_RATE_LIMIT_CACHE_ENABLED_FLAG)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
public void testTrafficStatsRateLimitCache_enabledWithCompatChangeDisabled_vOrAbove()
throws Exception {
mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, false);
doTestTrafficStatsRateLimitCache(true /* expectCached */);
}
private void doTestTrafficStatsRateLimitCache(boolean expectCached) throws Exception {
mockDefaultSettings();
// Calling uid is not injected into the service, use the real uid to pass the caller check.
final int myUid = Process.myUid();
mockTrafficStatsValues(64L, 3L, 1024L, 8L);
assertTrafficStatsValues(TEST_IFACE, myUid, 64L, 3L, 1024L, 8L);
// Verify the values are cached.
incrementCurrentTime(DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS / 2);
mockTrafficStatsValues(65L, 8L, 1055L, 9L);
if (expectCached) {
assertTrafficStatsValues(TEST_IFACE, myUid, 64L, 3L, 1024L, 8L);
} else {
assertTrafficStatsValues(TEST_IFACE, myUid, 65L, 8L, 1055L, 9L);
}
// Verify the values are updated after cache expiry.
incrementCurrentTime(DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS);
assertTrafficStatsValues(TEST_IFACE, myUid, 65L, 8L, 1055L, 9L);
}
private void mockTrafficStatsValues(long rxBytes, long rxPackets,
long txBytes, long txPackets) {
// In practice, keys and operations are not used and filled with default values when
// returned by JNI layer.
final NetworkStats.Entry entry = new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_DEFAULT,
TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
rxBytes, rxPackets, txBytes, txPackets, 0L);
mDeps.setNativeStat(entry);
}
// Assert for 3 different API return values respectively.
private void assertTrafficStatsValues(String iface, int uid, long rxBytes, long rxPackets,
long txBytes, long txPackets) {
assertStatsResultEquals(mService.getTotalStats(), rxBytes, rxPackets, txBytes, txPackets);
assertStatsResultEquals(mService.getIfaceStats(iface), rxBytes, rxPackets, txBytes,
txPackets);
assertStatsResultEquals(mService.getUidStats(uid), rxBytes, rxPackets, txBytes, txPackets);
}
private void assertStatsResultEquals(StatsResult stats, long rxBytes, long rxPackets,
long txBytes, long txPackets) {
assertEquals(rxBytes, stats.rxBytes);
assertEquals(rxPackets, stats.rxPackets);
assertEquals(txBytes, stats.txBytes);
assertEquals(txPackets, stats.txPackets);
}
private void assertShouldRunComparison(boolean expected, boolean isDebuggable) {
assertEquals("shouldRunComparison (debuggable=" + isDebuggable + "): ",
expected, mService.shouldRunComparison());

View file

@ -1,150 +0,0 @@
/*
* 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.net
import android.net.NetworkStats.Entry
import com.android.testutils.DevSdkIgnoreRunner
import java.time.Clock
import java.util.function.Supplier
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
@RunWith(DevSdkIgnoreRunner::class)
class TrafficStatsRateLimitCacheTest {
companion object {
private const val expiryDurationMs = 1000L
private const val maxSize = 2
}
private val clock = mock(Clock::class.java)
private val entry = mock(Entry::class.java)
private val cache = TrafficStatsRateLimitCache(clock, expiryDurationMs, maxSize)
@Test
fun testGet_returnsEntryIfNotExpired() {
cache.put("iface", 2, entry)
doReturn(500L).`when`(clock).millis() // Set clock to before expiry
val result = cache.get("iface", 2)
assertEquals(entry, result)
}
@Test
fun testGet_returnsNullIfExpired() {
cache.put("iface", 2, entry)
doReturn(2000L).`when`(clock).millis() // Set clock to after expiry
assertNull(cache.get("iface", 2))
}
@Test
fun testGet_returnsNullForNonExistentKey() {
val result = cache.get("otherIface", 99)
assertNull(result)
}
@Test
fun testPutAndGet_retrievesCorrectEntryForDifferentKeys() {
val entry1 = mock(Entry::class.java)
val entry2 = mock(Entry::class.java)
cache.put("iface1", 2, entry1)
cache.put("iface2", 4, entry2)
assertEquals(entry1, cache.get("iface1", 2))
assertEquals(entry2, cache.get("iface2", 4))
}
@Test
fun testPut_overridesExistingEntry() {
val entry1 = mock(Entry::class.java)
val entry2 = mock(Entry::class.java)
cache.put("iface", 2, entry1)
cache.put("iface", 2, entry2) // Put with the same key
assertEquals(entry2, cache.get("iface", 2))
}
@Test
fun testPut_removeLru() {
// Assumes max size is 2. Verify eldest entry get removed.
val entry1 = mock(Entry::class.java)
val entry2 = mock(Entry::class.java)
val entry3 = mock(Entry::class.java)
cache.put("iface1", 2, entry1)
cache.put("iface2", 4, entry2)
cache.put("iface3", 8, entry3)
assertNull(cache.get("iface1", 2))
assertEquals(entry2, cache.get("iface2", 4))
assertEquals(entry3, cache.get("iface3", 8))
}
@Test
fun testGetOrCompute_cacheHit() {
val entry1 = mock(Entry::class.java)
cache.put("iface1", 2, entry1)
// Set clock to before expiry.
doReturn(500L).`when`(clock).millis()
// Now call getOrCompute
val result = cache.getOrCompute("iface1", 2) {
fail("Supplier should not be called")
}
// Assertions
assertEquals(entry1, result) // Should get the cached entry.
}
@Suppress("UNCHECKED_CAST")
@Test
fun testGetOrCompute_cacheMiss() {
val entry1 = mock(Entry::class.java)
cache.put("iface1", 2, entry1)
// Set clock to after expiry.
doReturn(1500L).`when`(clock).millis()
// Mock the supplier to return our network stats entry.
val supplier = mock(Supplier::class.java) as Supplier<Entry>
doReturn(entry1).`when`(supplier).get()
// Now call getOrCompute.
val result = cache.getOrCompute("iface1", 2, supplier)
// Assertions.
assertEquals(entry1, result) // Should get the cached entry.
verify(supplier).get()
}
@Test
fun testClear() {
cache.put("iface", 2, entry)
cache.clear()
assertNull(cache.get("iface", 2))
}
}