Add cache savings to hibernation savings stats

Use StorageStatsManager to get the cache savings from deleting cache
data and add this to the hibernation stats API.

Bug: 213480570
Test: atest AppHibernationServiceTest
Change-Id: I9f9dedb26d9bbaadfb28608f6cfdc14e805146cf
This commit is contained in:
Kevin Han
2022-02-01 16:32:41 -08:00
parent da6dafc0bb
commit a55cc00828
3 changed files with 72 additions and 10 deletions

View File

@@ -35,6 +35,8 @@ import android.app.ActivityThread;
import android.app.IActivityManager;
import android.app.StatsManager;
import android.app.StatsManager.StatsPullAtomCallback;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
@@ -78,6 +80,7 @@ import com.android.server.SystemService;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -111,6 +114,7 @@ public final class AppHibernationService extends SystemService {
private final PackageManagerInternal mPackageManagerInternal;
private final IActivityManager mIActivityManager;
private final UserManager mUserManager;
private final StorageStatsManager mStorageStatsManager;
@GuardedBy("mLock")
private final SparseArray<Map<String, UserLevelState>> mUserStates = new SparseArray<>();
@@ -147,6 +151,7 @@ public final class AppHibernationService extends SystemService {
mPackageManagerInternal = injector.getPackageManagerInternal();
mIActivityManager = injector.getActivityManager();
mUserManager = injector.getUserManager();
mStorageStatsManager = injector.getStorageStatsManager();
mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore();
mBackgroundExecutor = injector.getBackgroundExecutor();
mOatArtifactDeletionEnabled = injector.isOatArtifactDeletionEnabled();
@@ -297,7 +302,8 @@ public final class AppHibernationService extends SystemService {
pkgState.hibernated = isHibernating;
if (isHibernating) {
mBackgroundExecutor.execute(() -> hibernatePackageForUser(packageName, realUserId));
mBackgroundExecutor.execute(
() -> hibernatePackageForUser(packageName, realUserId, pkgState));
} else {
mBackgroundExecutor.execute(
() -> unhibernatePackageForUser(packageName, realUserId));
@@ -412,8 +418,9 @@ public final class AppHibernationService extends SystemService {
+ "the package was uninstalled? ", pkgName, userId));
continue;
}
HibernationStats stats = new HibernationStats(
mGlobalHibernationStates.get(pkgName).savedByte);
long diskBytesSaved = mGlobalHibernationStates.get(pkgName).savedByte
+ userPackageStates.get(pkgName).savedByte;
HibernationStats stats = new HibernationStats(diskBytesSaved);
statsMap.put(pkgName, stats);
}
}
@@ -424,16 +431,28 @@ public final class AppHibernationService extends SystemService {
* Put an app into hibernation for a given user, allowing user-level optimizations to occur. Do
* not hold {@link #mLock} while calling this to avoid deadlock scenarios.
*/
private void hibernatePackageForUser(@NonNull String packageName, int userId) {
private void hibernatePackageForUser(@NonNull String packageName, int userId,
UserLevelState state) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage");
final long caller = Binder.clearCallingIdentity();
try {
ApplicationInfo info = mIPackageManager.getApplicationInfo(
packageName, PACKAGE_MATCH_FLAGS, userId);
StorageStats stats = mStorageStatsManager.queryStatsForPackage(
info.storageUuid, packageName, new UserHandle(userId));
mIActivityManager.forceStopPackage(packageName, userId);
mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId,
null /* observer */);
synchronized (mLock) {
state.savedByte = stats.getCacheBytes();
}
} catch (RemoteException e) {
throw new IllegalStateException(
"Failed to hibernate due to manager not being available", e);
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Package name not found when querying storage stats", e);
} catch (IOException e) {
Slog.e(TAG, "Storage device not found", e);
} finally {
Binder.restoreCallingIdentity(caller);
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
@@ -923,6 +942,8 @@ public final class AppHibernationService extends SystemService {
UserManager getUserManager();
StorageStatsManager getStorageStatsManager();
Executor getBackgroundExecutor();
UsageStatsManagerInternal getUsageStatsManagerInternal();
@@ -971,6 +992,11 @@ public final class AppHibernationService extends SystemService {
return mContext.getSystemService(UserManager.class);
}
@Override
public StorageStatsManager getStorageStatsManager() {
return mContext.getSystemService(StorageStatsManager.class);
}
@Override
public Executor getBackgroundExecutor() {
return mScheduledExecutorService;

View File

@@ -28,6 +28,8 @@ final class UserLevelState {
public String packageName;
public boolean hibernated;
// Saved bytes from user level hibernation.
public long savedByte;
@CurrentTimeMillisLong
public long lastUnhibernatedMs;
@@ -36,6 +38,7 @@ final class UserLevelState {
UserLevelState(UserLevelState state) {
packageName = state.packageName;
hibernated = state.hibernated;
savedByte = state.savedByte;
lastUnhibernatedMs = state.lastUnhibernatedMs;
}
@@ -44,6 +47,7 @@ final class UserLevelState {
return "UserLevelState{"
+ "packageName='" + packageName + '\''
+ ", hibernated=" + hibernated + '\''
+ ", savedByte=" + savedByte + '\''
+ ", lastUnhibernated=" + DATE_FORMAT.format(lastUnhibernatedMs)
+ '}';
}

View File

@@ -29,6 +29,8 @@ import static org.mockito.AdditionalAnswers.returnsArgAt;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.longThat;
import static org.mockito.Mockito.doAnswer;
@@ -38,6 +40,8 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.IActivityManager;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
@@ -48,6 +52,7 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
@@ -68,10 +73,12 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executor;
/**
@@ -104,6 +111,8 @@ public final class AppHibernationServiceTest {
@Mock
private UserManager mUserManager;
@Mock
private StorageStatsManager mStorageStatsManager;
@Mock
private HibernationStateDiskStore<UserLevelState> mUserLevelDiskStore;
@Mock
private UsageStatsManagerInternal mUsageStatsManagerInternal;
@@ -115,7 +124,7 @@ public final class AppHibernationServiceTest {
private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor;
@Before
public void setUp() throws RemoteException {
public void setUp() throws RemoteException, PackageManager.NameNotFoundException, IOException {
// Share class loader to allow access to package-private classes
System.setProperty("dexmaker.share_classloader", "true");
MockitoAnnotations.initMocks(this);
@@ -140,6 +149,11 @@ public final class AppHibernationServiceTest {
packages.add(makePackageInfo(PACKAGE_NAME_3));
doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages(
longThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt());
doReturn(mock(ApplicationInfo.class)).when(mIPackageManager).getApplicationInfo(
any(), anyLong(), anyInt());
StorageStats storageStats = new StorageStats();
doReturn(storageStats).when(mStorageStatsManager).queryStatsForPackage(
(UUID) any(), anyString(), any());
mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
UserInfo userInfo = addUser(USER_ID_1);
@@ -381,18 +395,31 @@ public final class AppHibernationServiceTest {
}
@Test
public void testGetHibernationStatsForUser_getsStatsForPackage() {
// GIVEN a package is hibernating globally and for a user
public void testGetHibernationStatsForUser_getsStatsForPackage()
throws PackageManager.NameNotFoundException, IOException, RemoteException {
// GIVEN a package is hibernating globally and for a user with some storage saved
final long cacheSavings = 1000;
StorageStats storageStats = new StorageStats();
storageStats.cacheBytes = cacheSavings;
doReturn(storageStats).when(mStorageStatsManager).queryStatsForPackage(
(UUID) any(), eq(PACKAGE_NAME_1), any());
final long oatDeletionSavings = 2000;
doReturn(oatDeletionSavings).when(mPackageManagerInternal).deleteOatArtifactsOfPackage(
PACKAGE_NAME_1);
mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true);
mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true);
// WHEN we ask for the hibernation stats for the package
Map<String, HibernationStats> stats =
Map<String, HibernationStats> statsMap =
mAppHibernationService.getHibernationStatsForUser(
Set.of(PACKAGE_NAME_1), USER_ID_1);
// THEN the stats exist for the package
assertTrue(stats.containsKey(PACKAGE_NAME_1));
// THEN the stats exist for the package and add up to the OAT deletion and cache deletion
// savings
HibernationStats stats = statsMap.get(PACKAGE_NAME_1);
assertNotNull(stats);
assertEquals(cacheSavings + oatDeletionSavings, stats.getDiskBytesSaved());
}
@Test
@@ -518,6 +545,11 @@ public final class AppHibernationServiceTest {
return mUserManager;
}
@Override
public StorageStatsManager getStorageStatsManager() {
return mStorageStatsManager;
}
@Override
public UsageStatsManagerInternal getUsageStatsManagerInternal() {
return mUsageStatsManagerInternal;