From 71ea678683bccc5cc51365a91ffae4ea87a33fc2 Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Tue, 6 Sep 2016 18:22:19 -0700 Subject: [PATCH 1/5] Package Manager: Sort list of packages to dexopt Sort the list by last-use-time, if available. Interleave the dependencies with the packages. Clean up the code a bit for better code reuse and ease of writing filters. This should help with prioritization under space constraints. Bug: 31347757 Change-Id: Ia0ec62faf013a379dc4c80b18fd6b2bfbfa470c4 (cherry picked from commit d3e07d46d41e8411def67c2150a0bae064e900dc) --- .../server/pm/PackageManagerServiceUtils.java | 150 +++++++++--------- 1 file changed, 74 insertions(+), 76 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 751c5858446bf..9a8ebedf105e6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -19,6 +19,7 @@ package com.android.server.pm; import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; import static com.android.server.pm.PackageManagerService.TAG; +import android.annotation.NonNull; import android.app.AppGlobals; import android.content.Intent; import android.content.pm.PackageParser; @@ -35,13 +36,9 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.Set; +import java.util.function.Predicate; /** * Class containing helper methods for the PackageManagerService. @@ -67,34 +64,51 @@ public class PackageManagerServiceUtils { return pkgNames; } - private static void filterRecentlyUsedApps(Collection pkgs, - long estimatedPreviousSystemUseTime, - long dexOptLRUThresholdInMills) { - // Filter out packages that aren't recently used. - int total = pkgs.size(); - int skipped = 0; - for (Iterator i = pkgs.iterator(); i.hasNext();) { - PackageParser.Package pkg = i.next(); - long then = pkg.getLatestForegroundPackageUseTimeInMills(); - if (then < estimatedPreviousSystemUseTime - dexOptLRUThresholdInMills) { - if (DEBUG_DEXOPT) { - Log.i(TAG, "Skipping dexopt of " + pkg.packageName + - " last used in foreground: " + - ((then == 0) ? "never" : new Date(then))); - } - i.remove(); - skipped++; - } else { - if (DEBUG_DEXOPT) { - Log.i(TAG, "Will dexopt " + pkg.packageName + - " last used in foreground: " + - ((then == 0) ? "never" : new Date(then))); - } + // Sort a list of apps by their last usage, most recently used apps first. The order of + // packages without usage data is undefined (but they will be sorted after the packages + // that do have usage data). + public static void sortPackagesByUsageDate(List pkgs, + PackageManagerService packageManagerService) { + if (!packageManagerService.isHistoricalPackageUsageAvailable()) { + return; + } + + Collections.sort(pkgs, (pkg1, pkg2) -> + Long.compare(pkg2.getLatestForegroundPackageUseTimeInMills(), + pkg1.getLatestForegroundPackageUseTimeInMills())); + } + + // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the + // package will be removed from {@code packages} and added to {@code result} with its + // dependencies. If usage data is available, the positive packages will be sorted by usage + // data (with {@code sortTemp} as temporary storage). + private static void applyPackageFilter(Predicate filter, + Collection result, + Collection packages, + @NonNull List sortTemp, + PackageManagerService packageManagerService) { + for (PackageParser.Package pkg : packages) { + if (filter.test(pkg)) { + sortTemp.add(pkg); } } - if (DEBUG_DEXOPT) { - Log.i(TAG, "Skipped dexopt " + skipped + " of " + total); + + sortPackagesByUsageDate(sortTemp, packageManagerService); + packages.removeAll(sortTemp); + + for (PackageParser.Package pkg : sortTemp) { + result.add(pkg); + + Collection deps = + packageManagerService.findSharedNonSystemLibraries(pkg); + if (!deps.isEmpty()) { + deps.removeAll(result); + result.addAll(deps); + packages.removeAll(deps); + } } + + sortTemp.clear(); } // Sort apps by importance for dexopt ordering. Important apps are given @@ -104,46 +118,25 @@ public class PackageManagerServiceUtils { PackageManagerService packageManagerService) { ArrayList remainingPkgs = new ArrayList<>(packages); LinkedList result = new LinkedList<>(); + ArrayList sortTemp = new ArrayList<>(remainingPkgs.size()); // Give priority to core apps. - for (PackageParser.Package pkg : remainingPkgs) { - if (pkg.coreApp) { - if (DEBUG_DEXOPT) { - Log.i(TAG, "Adding core app " + result.size() + ": " + pkg.packageName); - } - result.add(pkg); - } - } - remainingPkgs.removeAll(result); + applyPackageFilter((pkg) -> pkg.coreApp, result, remainingPkgs, sortTemp, + packageManagerService); // Give priority to system apps that listen for pre boot complete. Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); - ArraySet pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM); - for (PackageParser.Package pkg : remainingPkgs) { - if (pkgNames.contains(pkg.packageName)) { - if (DEBUG_DEXOPT) { - Log.i(TAG, "Adding pre boot system app " + result.size() + ": " + - pkg.packageName); - } - result.add(pkg); - } - } - remainingPkgs.removeAll(result); + final ArraySet pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM); + applyPackageFilter((pkg) -> pkgNames.contains(pkg.packageName), result, remainingPkgs, + sortTemp, packageManagerService); // Give priority to apps used by other apps. - for (PackageParser.Package pkg : remainingPkgs) { - if (PackageDexOptimizer.isUsedByOtherApps(pkg)) { - if (DEBUG_DEXOPT) { - Log.i(TAG, "Adding app used by other apps " + result.size() + ": " + - pkg.packageName); - } - result.add(pkg); - } - } - remainingPkgs.removeAll(result); + applyPackageFilter((pkg) -> PackageDexOptimizer.isUsedByOtherApps(pkg), result, + remainingPkgs, sortTemp, packageManagerService); // Filter out packages that aren't recently used, add all remaining apps. // TODO: add a property to control this? + Predicate remainingPredicate; if (!remainingPkgs.isEmpty() && packageManagerService.isHistoricalPackageUsageAvailable()) { if (DEBUG_DEXOPT) { Log.i(TAG, "Looking at historical package use"); @@ -159,24 +152,20 @@ public class PackageManagerServiceUtils { lastUsed.getLatestForegroundPackageUseTimeInMills(); // Be defensive if for some reason package usage has bogus data. if (estimatedPreviousSystemUseTime != 0) { - filterRecentlyUsedApps(remainingPkgs, estimatedPreviousSystemUseTime, - SEVEN_DAYS_IN_MILLISECONDS); + final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS; + remainingPredicate = + (pkg) -> pkg.getLatestForegroundPackageUseTimeInMills() >= cutoffTime; + } else { + // No meaningful historical info. Take all. + remainingPredicate = (pkg) -> true; } + sortPackagesByUsageDate(remainingPkgs, packageManagerService); + } else { + // No historical info. Take all. + remainingPredicate = (pkg) -> true; } - result.addAll(remainingPkgs); - - // Now go ahead and also add the libraries required for these packages. - // TODO: Think about interleaving things. - Set dependencies = new HashSet<>(); - for (PackageParser.Package p : result) { - dependencies.addAll(packageManagerService.findSharedNonSystemLibraries(p)); - } - if (!dependencies.isEmpty()) { - // We might have packages already in `result` that are dependencies - // of other packages. Make sure we don't add those to the list twice. - dependencies.removeAll(result); - } - result.addAll(dependencies); + applyPackageFilter(remainingPredicate, result, remainingPkgs, sortTemp, + packageManagerService); if (DEBUG_DEXOPT) { StringBuilder sb = new StringBuilder(); @@ -187,6 +176,15 @@ public class PackageManagerServiceUtils { sb.append(pkg.packageName); } Log.i(TAG, "Packages to be dexopted: " + sb.toString()); + + sb.setLength(0); + for (PackageParser.Package pkg : remainingPkgs) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(pkg.packageName); + } + Log.i(TAG, "Packages skipped from dexopt: " + sb.toString()); } return result; From 33c592da0cac04172b6edaabf47ec09cd826bc8c Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Fri, 9 Sep 2016 17:08:53 -0700 Subject: [PATCH 2/5] Installer: Support delete_odex command Add support for deleting odex files. Bug: 31347757 Change-Id: I29bca8751bcee8d6981c682fbbc816c73b78ac68 (cherry picked from commit e5fedb95761fbffbd80200440f32e99e0d8c6f90) --- services/core/java/com/android/server/pm/Installer.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 72c549f7bec6b..2e18b1c417fee 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -230,6 +230,11 @@ public final class Installer extends SystemService { mInstaller.execute("move_ab", apkPath, instructionSet, outputPath); } + public void deleteOdex(String apkPath, String instructionSet, String outputPath) + throws InstallerException { + mInstaller.execute("delete_odex", apkPath, instructionSet, outputPath); + } + private static void assertValidInstructionSet(String instructionSet) throws InstallerException { for (String abi : Build.SUPPORTED_ABIS) { From 1920d7b434ea7424cae8249adec56856fb2484ae Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Fri, 9 Sep 2016 17:50:20 -0700 Subject: [PATCH 3/5] OtaDexopt: Downgrade apps when low on space When running low on space, attempt to "downgrade" apps to lower states in the optimization flow to free up space before starting the OTA. Bug: 31347757 Change-Id: I3a44b106b83d86d7290f4c557267b319f28de12a (cherry picked from commit dab38e000436bf8234955b0333eaecf389e65b6f) --- .../android/server/pm/OtaDexoptService.java | 65 ++++++++++++++++--- .../server/pm/PackageManagerServiceUtils.java | 30 ++++----- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index bff6d2d4786ed..756ba5e88c0fd 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -53,6 +53,10 @@ public class OtaDexoptService extends IOtaDexopt.Stub { // The synthetic library dependencies denoting "no checks." private final static String[] NO_LIBRARIES = new String[] { "&" }; + // The amount of "available" (free - low threshold) space necessary at the start of an OTA to + // not bulk-delete unused apps' odex files. + private final static long BULK_DELETE_THRESHOLD = 1024 * 1024 * 1024; // 1GB. + private final Context mContext; private final PackageManagerService mPackageManagerService; @@ -128,6 +132,14 @@ public class OtaDexoptService extends IOtaDexopt.Stub { generatePackageDexopts(p, PackageManagerService.REASON_FIRST_BOOT)); } completeSize = mDexoptCommands.size(); + + if (getAvailableSpace() < BULK_DELETE_THRESHOLD) { + Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: " + + PackageManagerServiceUtils.packagesToString(others)); + for (PackageParser.Package pkg : others) { + deleteOatArtifactsOfPackage(pkg); + } + } } @Override @@ -169,28 +181,65 @@ public class OtaDexoptService extends IOtaDexopt.Stub { String next = mDexoptCommands.remove(0); - if (IsFreeSpaceAvailable()) { + if (getAvailableSpace() > 0) { return next; } else { + if (DEBUG_DEXOPT) { + Log.w(TAG, "Not enough space for OTA dexopt, stopping with " + + (mDexoptCommands.size() + 1) + " commands left."); + } mDexoptCommands.clear(); return "(no free space)"; } } - /** - * Check for low space. Returns true if there's space left. - */ - private boolean IsFreeSpaceAvailable() { - // TODO: If apps are not installed in the internal /data partition, we should compare - // against that storage's free capacity. + private long getMainLowSpaceThreshold() { File dataDir = Environment.getDataDirectory(); @SuppressWarnings("deprecation") long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir); if (lowThreshold == 0) { throw new IllegalStateException("Invalid low memory threshold"); } + return lowThreshold; + } + + /** + * Returns the difference of free space to the low-storage-space threshold. Positive values + * indicate free bytes. + */ + private long getAvailableSpace() { + // TODO: If apps are not installed in the internal /data partition, we should compare + // against that storage's free capacity. + long lowThreshold = getMainLowSpaceThreshold(); + + File dataDir = Environment.getDataDirectory(); long usableSpace = dataDir.getUsableSpace(); - return (usableSpace >= lowThreshold); + + return usableSpace - lowThreshold; + } + + private static String getOatDir(PackageParser.Package pkg) { + if (!pkg.canHaveOatDir()) { + return null; + } + File codePath = new File(pkg.codePath); + if (codePath.isDirectory()) { + return PackageDexOptimizer.getOatDir(codePath).getAbsolutePath(); + } + return null; + } + + private void deleteOatArtifactsOfPackage(PackageParser.Package pkg) { + String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo); + for (String codePath : pkg.getAllCodePaths()) { + for (String isa : instructionSets) { + try { + mPackageManagerService.mInstaller.deleteOdex(codePath, isa, getOatDir(pkg)); + } catch (InstallerException e) { + Log.e(TAG, "Failed deleting oat files for " + codePath, e); + } + } + } } /** diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 9a8ebedf105e6..cfd0af7635e82 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -168,23 +168,8 @@ public class PackageManagerServiceUtils { packageManagerService); if (DEBUG_DEXOPT) { - StringBuilder sb = new StringBuilder(); - for (PackageParser.Package pkg : result) { - if (sb.length() > 0) { - sb.append(", "); - } - sb.append(pkg.packageName); - } - Log.i(TAG, "Packages to be dexopted: " + sb.toString()); - - sb.setLength(0); - for (PackageParser.Package pkg : remainingPkgs) { - if (sb.length() > 0) { - sb.append(", "); - } - sb.append(pkg.packageName); - } - Log.i(TAG, "Packages skipped from dexopt: " + sb.toString()); + Log.i(TAG, "Packages to be dexopted: " + packagesToString(result)); + Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgs)); } return result; @@ -201,4 +186,15 @@ public class PackageManagerServiceUtils { throw ee.rethrowAsIOException(); } } + + public static String packagesToString(Collection c) { + StringBuilder sb = new StringBuilder(); + for (PackageParser.Package pkg : c) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(pkg.packageName); + } + return sb.toString(); + } } From 112e01480f62d42a0ce2527f7d98318cfec61b23 Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Tue, 13 Sep 2016 11:12:13 -0700 Subject: [PATCH 4/5] OtaDexoptService: Add tron logging Log key properties of A/B OTA dexopting. Bug: 31458900 Change-Id: Iba1e5a40ff8b4998d4d876800ee59940f01afb6e (cherry picked from commit 77cc8172d31fff398b81a039b3fddc8acd1c609b) --- .../android/server/pm/OtaDexoptService.java | 81 ++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index 756ba5e88c0fd..42079fb0df1be 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -31,7 +31,7 @@ import android.os.ServiceManager; import android.os.storage.StorageManager; import android.util.Log; import android.util.Slog; - +import com.android.internal.logging.MetricsLogger; import com.android.internal.os.InstallerConnection; import com.android.internal.os.InstallerConnection.InstallerException; @@ -40,6 +40,7 @@ import java.io.FileDescriptor; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.concurrent.TimeUnit; /** * A service for A/B OTA dexopting. @@ -69,6 +70,25 @@ public class OtaDexoptService extends IOtaDexopt.Stub { private int completeSize; + // MetricsLogger properties. + + // Space before and after. + private long availableSpaceBefore; + private long availableSpaceAfterBulkDelete; + private long availableSpaceAfterDexopt; + + // Packages. + private int importantPackageCount; + private int otherPackageCount; + + // Number of dexopt commands. This may be different from the count of packages. + private int dexoptCommandCountTotal; + private int dexoptCommandCountExecuted; + + // For spent time. + private long otaDexoptTimeStart; + + public OtaDexoptService(Context context, PackageManagerService packageManagerService) { this.mContext = context; this.mPackageManagerService = packageManagerService; @@ -133,13 +153,17 @@ public class OtaDexoptService extends IOtaDexopt.Stub { } completeSize = mDexoptCommands.size(); - if (getAvailableSpace() < BULK_DELETE_THRESHOLD) { + long spaceAvailable = getAvailableSpace(); + if (spaceAvailable < BULK_DELETE_THRESHOLD) { Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: " + PackageManagerServiceUtils.packagesToString(others)); for (PackageParser.Package pkg : others) { deleteOatArtifactsOfPackage(pkg); } } + long spaceAvailableNow = getAvailableSpace(); + + prepareMetricsLogging(important.size(), others.size(), spaceAvailable, spaceAvailableNow); } @Override @@ -148,6 +172,8 @@ public class OtaDexoptService extends IOtaDexopt.Stub { Log.i(TAG, "Cleaning up OTA Dexopt state."); } mDexoptCommands = null; + + performMetricsLogging(); } @Override @@ -182,6 +208,8 @@ public class OtaDexoptService extends IOtaDexopt.Stub { String next = mDexoptCommands.remove(0); if (getAvailableSpace() > 0) { + dexoptCommandCountExecuted++; + return next; } else { if (DEBUG_DEXOPT) { @@ -320,6 +348,55 @@ public class OtaDexoptService extends IOtaDexopt.Stub { } } + /** + * Initialize logging fields. + */ + private void prepareMetricsLogging(int important, int others, long spaceBegin, long spaceBulk) { + availableSpaceBefore = spaceBegin; + availableSpaceAfterBulkDelete = spaceBulk; + availableSpaceAfterDexopt = 0; + + importantPackageCount = important; + otherPackageCount = others; + + dexoptCommandCountTotal = mDexoptCommands.size(); + dexoptCommandCountExecuted = 0; + + otaDexoptTimeStart = System.nanoTime(); + } + + private static int inMegabytes(long value) { + long in_mega_bytes = value / (1024 * 1024); + if (in_mega_bytes > Integer.MAX_VALUE) { + Log.w(TAG, "Recording " + in_mega_bytes + "MB of free space, overflowing range"); + return Integer.MAX_VALUE; + } + return (int)in_mega_bytes; + } + + private void performMetricsLogging() { + long finalTime = System.nanoTime(); + + MetricsLogger.histogram(mContext, "ota_dexopt_available_space_before_mb", + inMegabytes(availableSpaceBefore)); + MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_bulk_delete_mb", + inMegabytes(availableSpaceAfterBulkDelete)); + MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_dexopt_mb", + inMegabytes(availableSpaceAfterDexopt)); + + MetricsLogger.histogram(mContext, "ota_dexopt_num_important_packages", + importantPackageCount); + MetricsLogger.histogram(mContext, "ota_dexopt_num_other_packages", otherPackageCount); + + MetricsLogger.histogram(mContext, "ota_dexopt_num_commands", dexoptCommandCountTotal); + MetricsLogger.histogram(mContext, "ota_dexopt_num_commands_executed", + dexoptCommandCountExecuted); + + final int elapsedTimeSeconds = + (int) TimeUnit.NANOSECONDS.toSeconds(finalTime - otaDexoptTimeStart); + MetricsLogger.histogram(mContext, "ota_dexopt_time_s", elapsedTimeSeconds); + } + private static class OTADexoptPackageDexOptimizer extends PackageDexOptimizer.ForcedUpdatePackageDexOptimizer { From 13018f37305f94c9a091d8f7ef4e6bc7235de781 Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Mon, 19 Sep 2016 15:18:46 -0700 Subject: [PATCH 5/5] OtaDexopt: Fix after-ota space measure Actually measure the available space. Bug: 31458900 Change-Id: I16ecee6c275229bc21e908b88b24152484025bc8 (cherry picked from commit 8d1d2ab5b94f011ed611259df387391f1a1fe60f) --- services/core/java/com/android/server/pm/OtaDexoptService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index 42079fb0df1be..689917cd670a7 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -172,6 +172,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { Log.i(TAG, "Cleaning up OTA Dexopt state."); } mDexoptCommands = null; + availableSpaceAfterDexopt = getAvailableSpace(); performMetricsLogging(); }