Loading apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +45 −25 Original line number Diff line number Diff line Loading @@ -86,6 +86,7 @@ import java.util.concurrent.TimeUnit; /** * The main service implementation which contains AppSearch's platform functionality. * * @hide */ public class AppSearchManagerService extends SystemService { Loading Loading @@ -183,7 +184,6 @@ public class AppSearchManagerService extends SystemService { * when a user is removed. * * @param userHandle The multi-user handle of the user that need to be removed. * * @see android.os.Environment#getDataSystemCeDirectory */ private void handleUserRemoved(@NonNull UserHandle userHandle) { Loading Loading @@ -1500,7 +1500,6 @@ public class AppSearchManagerService extends SystemService { } } // TODO(b/179160886): Cache the previous storage stats. private class AppSearchStorageStatsAugmenter implements StorageStatsAugmenter { @Override public void augmentStatsForPackageForUser( Loading @@ -1514,12 +1513,19 @@ public class AppSearchManagerService extends SystemService { try { verifyUserUnlocked(userHandle); Context userContext = mContext.createContextAsUser(userHandle, /*flags=*/ 0); AppSearchUserInstance instance = mAppSearchUserInstanceManager.getOrCreateUserInstance( userContext, userHandle, AppSearchConfig.getInstance(EXECUTOR)); mAppSearchUserInstanceManager.getUserInstanceOrNull(userHandle); if (instance == null) { // augment storage info from file UserStorageInfo userStorageInfo = mAppSearchUserInstanceManager.getOrCreateUserStorageInfoInstance( userHandle); stats.dataSize += userStorageInfo.getSizeBytesForPackage(packageName); } else { stats.dataSize += instance.getAppSearchImpl() .getStorageInfoForPackage(packageName).getSizeBytes(); } } catch (Throwable t) { Log.e( TAG, Loading @@ -1543,14 +1549,23 @@ public class AppSearchManagerService extends SystemService { if (packagesForUid == null) { return; } Context userContext = mContext.createContextAsUser(userHandle, /*flags=*/ 0); AppSearchUserInstance instance = mAppSearchUserInstanceManager.getOrCreateUserInstance( userContext, userHandle, AppSearchConfig.getInstance(EXECUTOR)); mAppSearchUserInstanceManager.getUserInstanceOrNull(userHandle); if (instance == null) { // augment storage info from file UserStorageInfo userStorageInfo = mAppSearchUserInstanceManager.getOrCreateUserStorageInfoInstance( userHandle); for (int i = 0; i < packagesForUid.length; i++) { stats.dataSize += userStorageInfo.getSizeBytesForPackage( packagesForUid[i]); } } else { for (int i = 0; i < packagesForUid.length; i++) { stats.dataSize += instance.getAppSearchImpl() .getStorageInfoForPackage(packagesForUid[i]).getSizeBytes(); } } } catch (Throwable t) { Log.e(TAG, "Unable to augment storage stats for uid " + uid, t); } Loading @@ -1567,20 +1582,25 @@ public class AppSearchManagerService extends SystemService { try { verifyUserUnlocked(userHandle); AppSearchUserInstance instance = mAppSearchUserInstanceManager.getUserInstanceOrNull(userHandle); if (instance == null) { // augment storage info from file UserStorageInfo userStorageInfo = mAppSearchUserInstanceManager.getOrCreateUserStorageInfoInstance( userHandle); stats.dataSize += userStorageInfo.getTotalSizeBytes(); } else { List<PackageInfo> packagesForUser = mPackageManager.getInstalledPackagesAsUser( /*flags=*/0, userHandle.getIdentifier()); if (packagesForUser == null) { return; } Context userContext = mContext.createContextAsUser(userHandle, /*flags=*/ 0); AppSearchUserInstance instance = mAppSearchUserInstanceManager.getOrCreateUserInstance( userContext, userHandle, AppSearchConfig.getInstance(EXECUTOR)); if (packagesForUser != null) { for (int i = 0; i < packagesForUser.size(); i++) { String packageName = packagesForUser.get(i).packageName; stats.dataSize += instance.getAppSearchImpl() .getStorageInfoForPackage(packageName).getSizeBytes(); } } } } catch (Throwable t) { Log.e(TAG, "Unable to augment storage stats for " + userHandle, t); } Loading apex/appsearch/service/java/com/android/server/appsearch/AppSearchUserInstanceManager.java +43 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.appsearch; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; import android.os.Environment; Loading Loading @@ -50,6 +51,8 @@ public final class AppSearchUserInstanceManager { @GuardedBy("mInstancesLocked") private final Map<UserHandle, AppSearchUserInstance> mInstancesLocked = new ArrayMap<>(); @GuardedBy("mStorageInfoLocked") private final Map<UserHandle, UserStorageInfo> mStorageInfoLocked = new ArrayMap<>(); private AppSearchUserInstanceManager() {} Loading Loading @@ -130,6 +133,9 @@ public final class AppSearchUserInstanceManager { instance.getAppSearchImpl().close(); } } synchronized (mStorageInfoLocked) { mStorageInfoLocked.remove(userHandle); } } /** Loading Loading @@ -159,6 +165,39 @@ public final class AppSearchUserInstanceManager { } } /** * Returns the initialized {@link AppSearchUserInstance} for the given user, or {@code null} if * no such instance exists. * * @param userHandle The multi-user handle of the device user calling AppSearch */ @Nullable public AppSearchUserInstance getUserInstanceOrNull(@NonNull UserHandle userHandle) { Objects.requireNonNull(userHandle); synchronized (mInstancesLocked) { return mInstancesLocked.get(userHandle); } } /** * Gets an {@link UserStorageInfo} for the given user. * * @param userHandle The multi-user handle of the device user * @return An initialized {@link UserStorageInfo} for this user */ @NonNull public UserStorageInfo getOrCreateUserStorageInfoInstance(@NonNull UserHandle userHandle) { Objects.requireNonNull(userHandle); synchronized (mStorageInfoLocked) { UserStorageInfo userStorageInfo = mStorageInfoLocked.get(userHandle); if (userStorageInfo == null) { userStorageInfo = new UserStorageInfo(getAppSearchDir(userHandle)); mStorageInfoLocked.put(userHandle, userStorageInfo); } return userStorageInfo; } } /** * Returns the list of all {@link UserHandle}s. * Loading Loading @@ -197,6 +236,10 @@ public final class AppSearchUserInstanceManager { VisibilityStoreImpl.create(appSearchImpl, userContext); long prepareVisibilityStoreLatencyEndMillis = SystemClock.elapsedRealtime(); // Update storage info file UserStorageInfo userStorageInfo = getOrCreateUserStorageInfoInstance(userHandle); userStorageInfo.updateStorageInfoFile(appSearchImpl); initStatsBuilder .setTotalLatencyMillis( (int) (SystemClock.elapsedRealtime() - totalLatencyStartMillis)) Loading apex/appsearch/service/java/com/android/server/appsearch/UserStorageInfo.java 0 → 100644 +152 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.appsearch; import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getPackageName; import android.annotation.NonNull; import android.util.ArrayMap; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.appsearch.external.localstorage.AppSearchImpl; import com.google.android.icing.proto.DocumentStorageInfoProto; import com.google.android.icing.proto.NamespaceStorageInfoProto; import com.google.android.icing.proto.StorageInfoProto; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** Saves the storage info read from file for a user. */ public final class UserStorageInfo { public static final String STORAGE_INFO_FILE = "appsearch_storage"; private static final String TAG = "AppSearchUserStorage"; private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock(); private final File mStorageInfoFile; // Saves storage usage byte size for each package under the user, keyed by package name. private Map<String, Long> mPackageStorageSizeMap; // Saves storage usage byte size for all packages under the user. private long mTotalStorageSizeBytes; public UserStorageInfo(@NonNull File fileParentPath) { Objects.requireNonNull(fileParentPath); mStorageInfoFile = new File(fileParentPath, STORAGE_INFO_FILE); readStorageInfoFromFile(); } /** * Updates storage info file with the latest storage info queried through * {@link AppSearchImpl}. */ public void updateStorageInfoFile(@NonNull AppSearchImpl appSearchImpl) { Objects.requireNonNull(appSearchImpl); mReadWriteLock.writeLock().lock(); try (FileOutputStream out = new FileOutputStream(mStorageInfoFile)) { appSearchImpl.getRawStorageInfoProto().writeTo(out); } catch (Throwable e) { Log.w(TAG, "Failed to dump storage info into file", e); } finally { mReadWriteLock.writeLock().unlock(); } } /** * Gets storage usage byte size for a package with a given package name. * * <p> Please note the storage info cached in file may be stale. */ public long getSizeBytesForPackage(@NonNull String packageName) { Objects.requireNonNull(packageName); return mPackageStorageSizeMap.getOrDefault(packageName, 0L); } /** * Gets total storage usage byte size for all packages under the user. * * <p> Please note the storage info cached in file may be stale. */ public long getTotalSizeBytes() { return mTotalStorageSizeBytes; } @VisibleForTesting void readStorageInfoFromFile() { if (mStorageInfoFile.exists()) { mReadWriteLock.readLock().lock(); try (InputStream in = new FileInputStream(mStorageInfoFile)) { StorageInfoProto storageInfo = StorageInfoProto.parseFrom(in); mTotalStorageSizeBytes = storageInfo.getTotalStorageSize(); mPackageStorageSizeMap = calculatePackageStorageInfoMap(storageInfo); return; } catch (Throwable e) { Log.w(TAG, "Failed to read storage info from file", e); } finally { mReadWriteLock.readLock().unlock(); } } mTotalStorageSizeBytes = 0; mPackageStorageSizeMap = Collections.emptyMap(); } /** Calculates storage usage byte size for packages from a {@link StorageInfoProto}. */ // TODO(b/198553756): Storage cache effort has created two copies of the storage // calculation/interpolation logic. @NonNull @VisibleForTesting Map<String, Long> calculatePackageStorageInfoMap(@NonNull StorageInfoProto storageInfo) { Map<String, Long> packageStorageInfoMap = new ArrayMap<>(); if (storageInfo.hasDocumentStorageInfo()) { DocumentStorageInfoProto documentStorageInfo = storageInfo.getDocumentStorageInfo(); List<NamespaceStorageInfoProto> namespaceStorageInfoList = documentStorageInfo.getNamespaceStorageInfoList(); Map<String, Integer> packageDocumentCountMap = new ArrayMap<>(); long totalDocuments = 0; for (int i = 0; i < namespaceStorageInfoList.size(); i++) { NamespaceStorageInfoProto namespaceStorageInfo = namespaceStorageInfoList.get(i); String packageName = getPackageName(namespaceStorageInfo.getNamespace()); int namespaceDocuments = namespaceStorageInfo.getNumAliveDocuments() + namespaceStorageInfo.getNumExpiredDocuments(); totalDocuments += namespaceDocuments; packageDocumentCountMap.put(packageName, packageDocumentCountMap.getOrDefault(packageName, 0) + namespaceDocuments); } long totalStorageSize = storageInfo.getTotalStorageSize(); for (Map.Entry<String, Integer> entry : packageDocumentCountMap.entrySet()) { // Since we don't have the exact size of all the documents, we do an estimation. // Note that while the total storage takes into account schema, index, etc. in // addition to documents, we'll only calculate the percentage based on number of // documents under packages. packageStorageInfoMap.put(entry.getKey(), (long) (entry.getValue() * 1.0 / totalDocuments * totalStorageSize)); } } return Collections.unmodifiableMap(packageStorageInfoMap); } } services/tests/servicestests/src/com/android/server/appsearch/UserStorageInfoTest.java 0 → 100644 +123 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.appsearch; import static com.android.server.appsearch.UserStorageInfo.STORAGE_INFO_FILE; import static com.google.common.truth.Truth.assertThat; import com.android.server.appsearch.icing.proto.DocumentStorageInfoProto; import com.android.server.appsearch.icing.proto.NamespaceStorageInfoProto; import com.android.server.appsearch.icing.proto.StorageInfoProto; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; public class UserStorageInfoTest { @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); private File mFileParentPath; private UserStorageInfo mUserStorageInfo; @Before public void setUp() throws Exception { mFileParentPath = mTemporaryFolder.newFolder(); mUserStorageInfo = new UserStorageInfo(mFileParentPath); } @Test public void testReadStorageInfoFromFile() throws IOException { NamespaceStorageInfoProto namespaceStorageInfo1 = NamespaceStorageInfoProto.newBuilder() .setNamespace("pkg1$db/namespace") .setNumAliveDocuments(2) .setNumExpiredDocuments(1) .build(); NamespaceStorageInfoProto namespaceStorageInfo2 = NamespaceStorageInfoProto.newBuilder() .setNamespace("pkg2$db/namespace") .setNumAliveDocuments(3) .setNumExpiredDocuments(3) .build(); DocumentStorageInfoProto documentStorageInfo = DocumentStorageInfoProto.newBuilder() .setNumAliveDocuments(5) .setNumExpiredDocuments(4) .addNamespaceStorageInfo(namespaceStorageInfo1) .addNamespaceStorageInfo(namespaceStorageInfo2) .build(); StorageInfoProto storageInfo = StorageInfoProto.newBuilder() .setDocumentStorageInfo(documentStorageInfo) .setTotalStorageSize(9) .build(); File storageInfoFile = new File(mFileParentPath, STORAGE_INFO_FILE); try (FileOutputStream out = new FileOutputStream(storageInfoFile)) { storageInfo.writeTo(out); } mUserStorageInfo.readStorageInfoFromFile(); assertThat(mUserStorageInfo.getTotalSizeBytes()).isEqualTo( storageInfo.getTotalStorageSize()); // We calculate the package storage size based on number of documents a package has. // Here, total storage size is 9. pkg1 has 3 docs and pkg2 has 6 docs. So storage size of // pkg1 is 3. pkg2's storage size is 6. assertThat(mUserStorageInfo.getSizeBytesForPackage("pkg1")).isEqualTo(3); assertThat(mUserStorageInfo.getSizeBytesForPackage("pkg2")).isEqualTo(6); assertThat(mUserStorageInfo.getSizeBytesForPackage("invalid_pkg")).isEqualTo(0); } @Test public void testCalculatePackageStorageInfoMap() { NamespaceStorageInfoProto namespaceStorageInfo1 = NamespaceStorageInfoProto.newBuilder() .setNamespace("pkg1$db/namespace") .setNumAliveDocuments(2) .setNumExpiredDocuments(1) .build(); NamespaceStorageInfoProto namespaceStorageInfo2 = NamespaceStorageInfoProto.newBuilder() .setNamespace("pkg2$db/namespace") .setNumAliveDocuments(3) .setNumExpiredDocuments(3) .build(); DocumentStorageInfoProto documentStorageInfo = DocumentStorageInfoProto.newBuilder() .setNumAliveDocuments(5) .setNumExpiredDocuments(4) .addNamespaceStorageInfo(namespaceStorageInfo1) .addNamespaceStorageInfo(namespaceStorageInfo2) .build(); StorageInfoProto storageInfo = StorageInfoProto.newBuilder() .setDocumentStorageInfo(documentStorageInfo) .setTotalStorageSize(9) .build(); // We calculate the package storage size based on number of documents a package has. // Here, total storage size is 9. pkg1 has 3 docs and pkg2 has 6 docs. So storage size of // pkg1 is 3. pkg2's storage size is 6. assertThat(mUserStorageInfo.calculatePackageStorageInfoMap(storageInfo)) .containsExactly("pkg1", 3L, "pkg2", 6L); } } Loading
apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +45 −25 Original line number Diff line number Diff line Loading @@ -86,6 +86,7 @@ import java.util.concurrent.TimeUnit; /** * The main service implementation which contains AppSearch's platform functionality. * * @hide */ public class AppSearchManagerService extends SystemService { Loading Loading @@ -183,7 +184,6 @@ public class AppSearchManagerService extends SystemService { * when a user is removed. * * @param userHandle The multi-user handle of the user that need to be removed. * * @see android.os.Environment#getDataSystemCeDirectory */ private void handleUserRemoved(@NonNull UserHandle userHandle) { Loading Loading @@ -1500,7 +1500,6 @@ public class AppSearchManagerService extends SystemService { } } // TODO(b/179160886): Cache the previous storage stats. private class AppSearchStorageStatsAugmenter implements StorageStatsAugmenter { @Override public void augmentStatsForPackageForUser( Loading @@ -1514,12 +1513,19 @@ public class AppSearchManagerService extends SystemService { try { verifyUserUnlocked(userHandle); Context userContext = mContext.createContextAsUser(userHandle, /*flags=*/ 0); AppSearchUserInstance instance = mAppSearchUserInstanceManager.getOrCreateUserInstance( userContext, userHandle, AppSearchConfig.getInstance(EXECUTOR)); mAppSearchUserInstanceManager.getUserInstanceOrNull(userHandle); if (instance == null) { // augment storage info from file UserStorageInfo userStorageInfo = mAppSearchUserInstanceManager.getOrCreateUserStorageInfoInstance( userHandle); stats.dataSize += userStorageInfo.getSizeBytesForPackage(packageName); } else { stats.dataSize += instance.getAppSearchImpl() .getStorageInfoForPackage(packageName).getSizeBytes(); } } catch (Throwable t) { Log.e( TAG, Loading @@ -1543,14 +1549,23 @@ public class AppSearchManagerService extends SystemService { if (packagesForUid == null) { return; } Context userContext = mContext.createContextAsUser(userHandle, /*flags=*/ 0); AppSearchUserInstance instance = mAppSearchUserInstanceManager.getOrCreateUserInstance( userContext, userHandle, AppSearchConfig.getInstance(EXECUTOR)); mAppSearchUserInstanceManager.getUserInstanceOrNull(userHandle); if (instance == null) { // augment storage info from file UserStorageInfo userStorageInfo = mAppSearchUserInstanceManager.getOrCreateUserStorageInfoInstance( userHandle); for (int i = 0; i < packagesForUid.length; i++) { stats.dataSize += userStorageInfo.getSizeBytesForPackage( packagesForUid[i]); } } else { for (int i = 0; i < packagesForUid.length; i++) { stats.dataSize += instance.getAppSearchImpl() .getStorageInfoForPackage(packagesForUid[i]).getSizeBytes(); } } } catch (Throwable t) { Log.e(TAG, "Unable to augment storage stats for uid " + uid, t); } Loading @@ -1567,20 +1582,25 @@ public class AppSearchManagerService extends SystemService { try { verifyUserUnlocked(userHandle); AppSearchUserInstance instance = mAppSearchUserInstanceManager.getUserInstanceOrNull(userHandle); if (instance == null) { // augment storage info from file UserStorageInfo userStorageInfo = mAppSearchUserInstanceManager.getOrCreateUserStorageInfoInstance( userHandle); stats.dataSize += userStorageInfo.getTotalSizeBytes(); } else { List<PackageInfo> packagesForUser = mPackageManager.getInstalledPackagesAsUser( /*flags=*/0, userHandle.getIdentifier()); if (packagesForUser == null) { return; } Context userContext = mContext.createContextAsUser(userHandle, /*flags=*/ 0); AppSearchUserInstance instance = mAppSearchUserInstanceManager.getOrCreateUserInstance( userContext, userHandle, AppSearchConfig.getInstance(EXECUTOR)); if (packagesForUser != null) { for (int i = 0; i < packagesForUser.size(); i++) { String packageName = packagesForUser.get(i).packageName; stats.dataSize += instance.getAppSearchImpl() .getStorageInfoForPackage(packageName).getSizeBytes(); } } } } catch (Throwable t) { Log.e(TAG, "Unable to augment storage stats for " + userHandle, t); } Loading
apex/appsearch/service/java/com/android/server/appsearch/AppSearchUserInstanceManager.java +43 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.appsearch; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; import android.os.Environment; Loading Loading @@ -50,6 +51,8 @@ public final class AppSearchUserInstanceManager { @GuardedBy("mInstancesLocked") private final Map<UserHandle, AppSearchUserInstance> mInstancesLocked = new ArrayMap<>(); @GuardedBy("mStorageInfoLocked") private final Map<UserHandle, UserStorageInfo> mStorageInfoLocked = new ArrayMap<>(); private AppSearchUserInstanceManager() {} Loading Loading @@ -130,6 +133,9 @@ public final class AppSearchUserInstanceManager { instance.getAppSearchImpl().close(); } } synchronized (mStorageInfoLocked) { mStorageInfoLocked.remove(userHandle); } } /** Loading Loading @@ -159,6 +165,39 @@ public final class AppSearchUserInstanceManager { } } /** * Returns the initialized {@link AppSearchUserInstance} for the given user, or {@code null} if * no such instance exists. * * @param userHandle The multi-user handle of the device user calling AppSearch */ @Nullable public AppSearchUserInstance getUserInstanceOrNull(@NonNull UserHandle userHandle) { Objects.requireNonNull(userHandle); synchronized (mInstancesLocked) { return mInstancesLocked.get(userHandle); } } /** * Gets an {@link UserStorageInfo} for the given user. * * @param userHandle The multi-user handle of the device user * @return An initialized {@link UserStorageInfo} for this user */ @NonNull public UserStorageInfo getOrCreateUserStorageInfoInstance(@NonNull UserHandle userHandle) { Objects.requireNonNull(userHandle); synchronized (mStorageInfoLocked) { UserStorageInfo userStorageInfo = mStorageInfoLocked.get(userHandle); if (userStorageInfo == null) { userStorageInfo = new UserStorageInfo(getAppSearchDir(userHandle)); mStorageInfoLocked.put(userHandle, userStorageInfo); } return userStorageInfo; } } /** * Returns the list of all {@link UserHandle}s. * Loading Loading @@ -197,6 +236,10 @@ public final class AppSearchUserInstanceManager { VisibilityStoreImpl.create(appSearchImpl, userContext); long prepareVisibilityStoreLatencyEndMillis = SystemClock.elapsedRealtime(); // Update storage info file UserStorageInfo userStorageInfo = getOrCreateUserStorageInfoInstance(userHandle); userStorageInfo.updateStorageInfoFile(appSearchImpl); initStatsBuilder .setTotalLatencyMillis( (int) (SystemClock.elapsedRealtime() - totalLatencyStartMillis)) Loading
apex/appsearch/service/java/com/android/server/appsearch/UserStorageInfo.java 0 → 100644 +152 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.appsearch; import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getPackageName; import android.annotation.NonNull; import android.util.ArrayMap; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.appsearch.external.localstorage.AppSearchImpl; import com.google.android.icing.proto.DocumentStorageInfoProto; import com.google.android.icing.proto.NamespaceStorageInfoProto; import com.google.android.icing.proto.StorageInfoProto; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** Saves the storage info read from file for a user. */ public final class UserStorageInfo { public static final String STORAGE_INFO_FILE = "appsearch_storage"; private static final String TAG = "AppSearchUserStorage"; private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock(); private final File mStorageInfoFile; // Saves storage usage byte size for each package under the user, keyed by package name. private Map<String, Long> mPackageStorageSizeMap; // Saves storage usage byte size for all packages under the user. private long mTotalStorageSizeBytes; public UserStorageInfo(@NonNull File fileParentPath) { Objects.requireNonNull(fileParentPath); mStorageInfoFile = new File(fileParentPath, STORAGE_INFO_FILE); readStorageInfoFromFile(); } /** * Updates storage info file with the latest storage info queried through * {@link AppSearchImpl}. */ public void updateStorageInfoFile(@NonNull AppSearchImpl appSearchImpl) { Objects.requireNonNull(appSearchImpl); mReadWriteLock.writeLock().lock(); try (FileOutputStream out = new FileOutputStream(mStorageInfoFile)) { appSearchImpl.getRawStorageInfoProto().writeTo(out); } catch (Throwable e) { Log.w(TAG, "Failed to dump storage info into file", e); } finally { mReadWriteLock.writeLock().unlock(); } } /** * Gets storage usage byte size for a package with a given package name. * * <p> Please note the storage info cached in file may be stale. */ public long getSizeBytesForPackage(@NonNull String packageName) { Objects.requireNonNull(packageName); return mPackageStorageSizeMap.getOrDefault(packageName, 0L); } /** * Gets total storage usage byte size for all packages under the user. * * <p> Please note the storage info cached in file may be stale. */ public long getTotalSizeBytes() { return mTotalStorageSizeBytes; } @VisibleForTesting void readStorageInfoFromFile() { if (mStorageInfoFile.exists()) { mReadWriteLock.readLock().lock(); try (InputStream in = new FileInputStream(mStorageInfoFile)) { StorageInfoProto storageInfo = StorageInfoProto.parseFrom(in); mTotalStorageSizeBytes = storageInfo.getTotalStorageSize(); mPackageStorageSizeMap = calculatePackageStorageInfoMap(storageInfo); return; } catch (Throwable e) { Log.w(TAG, "Failed to read storage info from file", e); } finally { mReadWriteLock.readLock().unlock(); } } mTotalStorageSizeBytes = 0; mPackageStorageSizeMap = Collections.emptyMap(); } /** Calculates storage usage byte size for packages from a {@link StorageInfoProto}. */ // TODO(b/198553756): Storage cache effort has created two copies of the storage // calculation/interpolation logic. @NonNull @VisibleForTesting Map<String, Long> calculatePackageStorageInfoMap(@NonNull StorageInfoProto storageInfo) { Map<String, Long> packageStorageInfoMap = new ArrayMap<>(); if (storageInfo.hasDocumentStorageInfo()) { DocumentStorageInfoProto documentStorageInfo = storageInfo.getDocumentStorageInfo(); List<NamespaceStorageInfoProto> namespaceStorageInfoList = documentStorageInfo.getNamespaceStorageInfoList(); Map<String, Integer> packageDocumentCountMap = new ArrayMap<>(); long totalDocuments = 0; for (int i = 0; i < namespaceStorageInfoList.size(); i++) { NamespaceStorageInfoProto namespaceStorageInfo = namespaceStorageInfoList.get(i); String packageName = getPackageName(namespaceStorageInfo.getNamespace()); int namespaceDocuments = namespaceStorageInfo.getNumAliveDocuments() + namespaceStorageInfo.getNumExpiredDocuments(); totalDocuments += namespaceDocuments; packageDocumentCountMap.put(packageName, packageDocumentCountMap.getOrDefault(packageName, 0) + namespaceDocuments); } long totalStorageSize = storageInfo.getTotalStorageSize(); for (Map.Entry<String, Integer> entry : packageDocumentCountMap.entrySet()) { // Since we don't have the exact size of all the documents, we do an estimation. // Note that while the total storage takes into account schema, index, etc. in // addition to documents, we'll only calculate the percentage based on number of // documents under packages. packageStorageInfoMap.put(entry.getKey(), (long) (entry.getValue() * 1.0 / totalDocuments * totalStorageSize)); } } return Collections.unmodifiableMap(packageStorageInfoMap); } }
services/tests/servicestests/src/com/android/server/appsearch/UserStorageInfoTest.java 0 → 100644 +123 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.appsearch; import static com.android.server.appsearch.UserStorageInfo.STORAGE_INFO_FILE; import static com.google.common.truth.Truth.assertThat; import com.android.server.appsearch.icing.proto.DocumentStorageInfoProto; import com.android.server.appsearch.icing.proto.NamespaceStorageInfoProto; import com.android.server.appsearch.icing.proto.StorageInfoProto; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; public class UserStorageInfoTest { @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); private File mFileParentPath; private UserStorageInfo mUserStorageInfo; @Before public void setUp() throws Exception { mFileParentPath = mTemporaryFolder.newFolder(); mUserStorageInfo = new UserStorageInfo(mFileParentPath); } @Test public void testReadStorageInfoFromFile() throws IOException { NamespaceStorageInfoProto namespaceStorageInfo1 = NamespaceStorageInfoProto.newBuilder() .setNamespace("pkg1$db/namespace") .setNumAliveDocuments(2) .setNumExpiredDocuments(1) .build(); NamespaceStorageInfoProto namespaceStorageInfo2 = NamespaceStorageInfoProto.newBuilder() .setNamespace("pkg2$db/namespace") .setNumAliveDocuments(3) .setNumExpiredDocuments(3) .build(); DocumentStorageInfoProto documentStorageInfo = DocumentStorageInfoProto.newBuilder() .setNumAliveDocuments(5) .setNumExpiredDocuments(4) .addNamespaceStorageInfo(namespaceStorageInfo1) .addNamespaceStorageInfo(namespaceStorageInfo2) .build(); StorageInfoProto storageInfo = StorageInfoProto.newBuilder() .setDocumentStorageInfo(documentStorageInfo) .setTotalStorageSize(9) .build(); File storageInfoFile = new File(mFileParentPath, STORAGE_INFO_FILE); try (FileOutputStream out = new FileOutputStream(storageInfoFile)) { storageInfo.writeTo(out); } mUserStorageInfo.readStorageInfoFromFile(); assertThat(mUserStorageInfo.getTotalSizeBytes()).isEqualTo( storageInfo.getTotalStorageSize()); // We calculate the package storage size based on number of documents a package has. // Here, total storage size is 9. pkg1 has 3 docs and pkg2 has 6 docs. So storage size of // pkg1 is 3. pkg2's storage size is 6. assertThat(mUserStorageInfo.getSizeBytesForPackage("pkg1")).isEqualTo(3); assertThat(mUserStorageInfo.getSizeBytesForPackage("pkg2")).isEqualTo(6); assertThat(mUserStorageInfo.getSizeBytesForPackage("invalid_pkg")).isEqualTo(0); } @Test public void testCalculatePackageStorageInfoMap() { NamespaceStorageInfoProto namespaceStorageInfo1 = NamespaceStorageInfoProto.newBuilder() .setNamespace("pkg1$db/namespace") .setNumAliveDocuments(2) .setNumExpiredDocuments(1) .build(); NamespaceStorageInfoProto namespaceStorageInfo2 = NamespaceStorageInfoProto.newBuilder() .setNamespace("pkg2$db/namespace") .setNumAliveDocuments(3) .setNumExpiredDocuments(3) .build(); DocumentStorageInfoProto documentStorageInfo = DocumentStorageInfoProto.newBuilder() .setNumAliveDocuments(5) .setNumExpiredDocuments(4) .addNamespaceStorageInfo(namespaceStorageInfo1) .addNamespaceStorageInfo(namespaceStorageInfo2) .build(); StorageInfoProto storageInfo = StorageInfoProto.newBuilder() .setDocumentStorageInfo(documentStorageInfo) .setTotalStorageSize(9) .build(); // We calculate the package storage size based on number of documents a package has. // Here, total storage size is 9. pkg1 has 3 docs and pkg2 has 6 docs. So storage size of // pkg1 is 3. pkg2's storage size is 6. assertThat(mUserStorageInfo.calculatePackageStorageInfoMap(storageInfo)) .containsExactly("pkg1", 3L, "pkg2", 6L); } }