Merge "Cache storage info into file."
This commit is contained in:
@@ -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 {
|
||||
@@ -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) {
|
||||
@@ -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(
|
||||
@@ -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));
|
||||
stats.dataSize += instance.getAppSearchImpl()
|
||||
.getStorageInfoForPackage(packageName).getSizeBytes();
|
||||
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,
|
||||
@@ -1543,13 +1549,22 @@ 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));
|
||||
for (int i = 0; i < packagesForUid.length; i++) {
|
||||
stats.dataSize += instance.getAppSearchImpl()
|
||||
.getStorageInfoForPackage(packagesForUid[i]).getSizeBytes();
|
||||
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);
|
||||
@@ -1567,19 +1582,24 @@ public class AppSearchManagerService extends SystemService {
|
||||
|
||||
try {
|
||||
verifyUserUnlocked(userHandle);
|
||||
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));
|
||||
for (int i = 0; i < packagesForUser.size(); i++) {
|
||||
String packageName = packagesForUser.get(i).packageName;
|
||||
stats.dataSize += instance.getAppSearchImpl()
|
||||
.getStorageInfoForPackage(packageName).getSizeBytes();
|
||||
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) {
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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() {}
|
||||
|
||||
@@ -130,6 +133,9 @@ public final class AppSearchUserInstanceManager {
|
||||
instance.getAppSearchImpl().close();
|
||||
}
|
||||
}
|
||||
synchronized (mStorageInfoLocked) {
|
||||
mStorageInfoLocked.remove(userHandle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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.
|
||||
*
|
||||
@@ -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))
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user