From 5eb3eb58acf18dd624c477d5b11b45a23aea6261 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Tue, 13 Dec 2016 08:44:51 -0700 Subject: Offer to measure disk stats using quotas. Now we're getting somewhere! This CL starts measuring disk usage using quotactl(), which is almost instant and has much lower impact on flash memory lifetime. We now grant the per-app cache GID to every launched app, and the ContextImpl logic that creates cache directories matches the logic down in installd. Test: builds, boots, quota stats match manual stats Bug: 27948817 Change-Id: Ie269a2958ce0e1c17cb74dbfecc791a5c12922cf --- core/java/android/app/ContextImpl.java | 21 +++++++-- core/java/android/os/Build.java | 6 ++- core/java/android/os/Process.java | 5 ++ core/java/android/os/UserHandle.java | 9 ++++ .../java/com/android/server/EventLogTags.logtags | 3 +- .../android/server/am/ActivityManagerService.java | 9 ++-- .../core/java/com/android/server/pm/Installer.java | 10 ++-- .../android/server/pm/PackageManagerService.java | 55 ++++++++++++++++++---- 8 files changed, 96 insertions(+), 22 deletions(-) diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 8f424676dbe5..857a361788d6 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -509,10 +509,23 @@ class ContextImpl extends Context { * Common-path handling of app data dir creation */ private static File ensurePrivateDirExists(File file) { + return ensurePrivateDirExists(file, 0771, -1); + } + + private static File ensurePrivateCacheDirExists(File file) { + final int gid = UserHandle.getCacheAppGid(Process.myUid()); + return ensurePrivateDirExists(file, 02771, gid); + } + + private static File ensurePrivateDirExists(File file, int mode, int gid) { if (!file.exists()) { + final String path = file.getAbsolutePath(); try { - Os.mkdir(file.getAbsolutePath(), 0771); - Os.chmod(file.getAbsolutePath(), 0771); + Os.mkdir(path, mode); + Os.chmod(path, mode); + if (gid != -1) { + Os.chown(path, -1, gid); + } } catch (ErrnoException e) { if (e.errno == OsConstants.EEXIST) { // We must have raced with someone; that's okay @@ -581,7 +594,7 @@ class ContextImpl extends Context { if (mCacheDir == null) { mCacheDir = new File(getDataDir(), "cache"); } - return ensurePrivateDirExists(mCacheDir); + return ensurePrivateCacheDirExists(mCacheDir); } } @@ -591,7 +604,7 @@ class ContextImpl extends Context { if (mCodeCacheDir == null) { mCodeCacheDir = new File(getDataDir(), "code_cache"); } - return ensurePrivateDirExists(mCodeCacheDir); + return ensurePrivateCacheDirExists(mCodeCacheDir); } } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 151239bed02b..f6edee0f78e7 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -783,7 +783,7 @@ public class Build { */ public static boolean isBuildConsistent() { // Don't care on eng builds. Incremental build may trigger false negative. - if ("eng".equals(TYPE)) return true; + if (IS_ENG) return true; final String system = SystemProperties.get("ro.build.fingerprint"); final String vendor = SystemProperties.get("ro.vendor.build.fingerprint"); @@ -847,6 +847,10 @@ public class Build { public static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1; + /** {@hide} */ + public static final boolean IS_ENG = + "eng".equals(getString("ro.build.type")); + /** * Specifies whether the permissions needed by a legacy app should be * reviewed before any of its components can run. A legacy app is one diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 9cd1a4246a58..d6688e3c00f7 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -184,6 +184,11 @@ public class Process { */ public static final int LAST_SHARED_APPLICATION_GID = 59999; + /** {@hide} */ + public static final int FIRST_APPLICATION_CACHE_GID = 20000; + /** {@hide} */ + public static final int LAST_APPLICATION_CACHE_GID = 29999; + /** * Standard priority of application threads. * Use with {@link #setThreadPriority(int)} and diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index b3f44536214b..535a05a9fcb1 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -212,6 +212,15 @@ public final class UserHandle implements Parcelable { return appId; } + /** + * Returns the cache GID for a given UID or appId. + * @hide + */ + public static int getCacheAppGid(int id) { + return Process.FIRST_APPLICATION_CACHE_GID + (id % PER_USER_RANGE) + - Process.FIRST_APPLICATION_UID; + } + /** * Generate a text representation of the uid, breaking out its individual * components -- user, app, isolated, etc. diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index 74ff41c13802..6296375525a4 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -154,7 +154,8 @@ option java_package com.android.server 3110 unknown_sources_enabled (value|1) # Package Manager critical info 3120 pm_critical_info (msg|3) - +# Disk usage stats for verifying quota correctness +3121 pm_package_stats (manual_time|2|3),(quota_time|2|3),(manual_data|2|2),(quota_data|2|2),(manual_cache|2|2),(quota_cache|2|2) # --------------------------- # WindowManagerService.java diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5b02c7916354..bc0390119a1d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -3729,13 +3729,14 @@ public final class ActivityManagerService extends ActivityManagerNative * resources like shared libraries and access user-wide resources */ if (ArrayUtils.isEmpty(permGids)) { - gids = new int[2]; + gids = new int[3]; } else { - gids = new int[permGids.length + 2]; - System.arraycopy(permGids, 0, gids, 2, permGids.length); + gids = new int[permGids.length + 3]; + System.arraycopy(permGids, 0, gids, 3, permGids.length); } gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid)); - gids[1] = UserHandle.getUserGid(UserHandle.getUserId(uid)); + gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid)); + gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid)); } checkTime(startTime, "startProcess: building args"); if (mFactoryTest != FactoryTest.FACTORY_TEST_OFF) { diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 1f83d9e9230d..605fa5d8d0c3 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -54,6 +54,7 @@ public class Installer extends SystemService { // NOTE: keep in sync with installd public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8; public static final int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 9; + public static final int FLAG_USE_QUOTA = 1 << 12; private final boolean mIsolated; @@ -198,12 +199,13 @@ public class Installer extends SystemService { } } - public void getAppSize(String uuid, String packageName, int userId, int flags, long ceDataInode, - String codePath, PackageStats stats) throws InstallerException { + public void getAppSize(String uuid, String packageName, int userId, int flags, int appId, + long ceDataInode, String codePath, String externalUuid, PackageStats stats) + throws InstallerException { if (!checkBeforeRemote()) return; try { - final long[] res = mInstalld.getAppSize(uuid, packageName, userId, flags, ceDataInode, - codePath); + final long[] res = mInstalld.getAppSize(uuid, packageName, userId, flags, appId, + ceDataInode, codePath, externalUuid); stats.codeSize += res[0]; stats.dataSize += res[1]; stats.cacheSize += res[2]; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 7ce5aa8ae39d..614230c273dd 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -370,6 +370,9 @@ public class PackageManagerService extends IPackageManager.Stub { private static final boolean DISABLE_EPHEMERAL_APPS = false; private static final boolean HIDE_EPHEMERAL_APIS = true; + private static final boolean ENABLE_QUOTA = + SystemProperties.getBoolean("persist.fw.quota", false); + private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; private static final int NFC_UID = Process.NFC_UID; @@ -16869,6 +16872,11 @@ public class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } + private boolean equals(PackageStats a, PackageStats b) { + return (a.codeSize == b.codeSize) && (a.dataSize == b.dataSize) + && (a.cacheSize == b.cacheSize); + } + private boolean getPackageSizeInfoLI(String packageName, int userId, PackageStats stats) { final PackageSetting ps; synchronized (mPackages) { @@ -16878,20 +16886,51 @@ public class PackageManagerService extends IPackageManager.Stub { return false; } } + + final long ceDataInode = ps.getCeDataInode(userId); + final PackageStats quotaStats = new PackageStats(stats.packageName, stats.userHandle); + + final StorageManager storage = mContext.getSystemService(StorageManager.class); + final String externalUuid = storage.getPrimaryStorageUuid(); try { - mInstaller.getAppSize(ps.volumeUuid, packageName, userId, - StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE, - ps.getCeDataInode(userId), ps.codePathString, stats); + final long start = SystemClock.elapsedRealtimeNanos(); + mInstaller.getAppSize(ps.volumeUuid, packageName, userId, 0, + ps.appId, ceDataInode, ps.codePathString, externalUuid, stats); + final long stopManual = SystemClock.elapsedRealtimeNanos(); + if (ENABLE_QUOTA) { + mInstaller.getAppSize(ps.volumeUuid, packageName, userId, Installer.FLAG_USE_QUOTA, + ps.appId, ceDataInode, ps.codePathString, externalUuid, quotaStats); + } + final long stopQuota = SystemClock.elapsedRealtimeNanos(); + + // For now, ignore code size of packages on system partition + if (isSystemApp(ps) && !isUpdatedSystemApp(ps)) { + stats.codeSize = 0; + quotaStats.codeSize = 0; + } + + if (ENABLE_QUOTA && Build.IS_ENG && !ps.isSharedUser()) { + if (!equals(stats, quotaStats)) { + Log.w(TAG, "Found discrepancy between statistics:"); + Log.w(TAG, "Manual: " + stats); + Log.w(TAG, "Quota: " + quotaStats); + } + final long manualTime = stopManual - start; + final long quotaTime = stopQuota - stopManual; + EventLogTags.writePmPackageStats(manualTime, quotaTime, + stats.dataSize, quotaStats.dataSize, + stats.cacheSize, quotaStats.cacheSize); + } + + // External clients expect these to be tracked separately + stats.dataSize -= stats.cacheSize; + quotaStats.dataSize -= quotaStats.cacheSize; + } catch (InstallerException e) { Slog.w(TAG, String.valueOf(e)); return false; } - // For now, ignore code size of packages on system partition - if (isSystemApp(ps) && !isUpdatedSystemApp(ps)) { - stats.codeSize = 0; - } - return true; } -- cgit v1.2.3-59-g8ed1b