OtaDexoptService: Run dexopt on all apps

To avoid work after reboot, dexopt all apps. However, unimportant
apps are optimized with the first-boot reason (which is usually
interpret-only) for space reasons.

Bug: 30468718
Change-Id: Ia05d879957388967c69f9380e50d8a9834afe07d
(cherry picked from commit 115514b236)
This commit is contained in:
Andreas Gampe
2016-07-28 16:54:41 -07:00
parent 7e619a912c
commit 14186731f9

View File

@@ -56,16 +56,9 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
// TODO: Evaluate the need for WeakReferences here. // TODO: Evaluate the need for WeakReferences here.
/** /**
* The list of packages to dexopt. * The list of dexopt invocations for all work.
*/ */
private List<PackageParser.Package> mDexoptPackages; private List<String> mDexoptCommands;
/**
* The list of dexopt invocations for the current package (which will no longer be in
* mDexoptPackages). This can be more than one as a package may have multiple code paths,
* e.g., in the split-APK case.
*/
private List<String> mCommandsForCurrentPackage;
private int completeSize; private int completeSize;
@@ -94,15 +87,43 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
@Override @Override
public synchronized void prepare() throws RemoteException { public synchronized void prepare() throws RemoteException {
if (mDexoptPackages != null) { if (mDexoptCommands != null) {
throw new IllegalStateException("already called prepare()"); throw new IllegalStateException("already called prepare()");
} }
synchronized (mPackageManagerService.mPackages) { synchronized (mPackageManagerService.mPackages) {
mDexoptPackages = PackageManagerServiceUtils.getPackagesForDexopt( // Important: the packages we need to run with ab-ota compiler-reason.
List<PackageParser.Package> important = PackageManagerServiceUtils.getPackagesForDexopt(
mPackageManagerService.mPackages.values(), mPackageManagerService); mPackageManagerService.mPackages.values(), mPackageManagerService);
// Others: we should optimize this with the (first-)boot compiler-reason.
List<PackageParser.Package> others =
new ArrayList<>(mPackageManagerService.mPackages.values());
others.removeAll(important);
// Pre-size the array list by over-allocating by a factor of 1.5.
mDexoptCommands = new ArrayList<>(3 * mPackageManagerService.mPackages.size() / 2);
for (PackageParser.Package p : important) {
// Make sure that core apps are optimized according to their own "reason".
// If the core apps are not preopted in the B OTA, and REASON_AB_OTA is not speed
// (by default is speed-profile) they will be interepreted/JITed. This in itself is
// not a problem as we will end up doing profile guided compilation. However, some
// core apps may be loaded by system server which doesn't JIT and we need to make
// sure we don't interpret-only
int compilationReason = p.coreApp
? PackageManagerService.REASON_CORE_APP
: PackageManagerService.REASON_AB_OTA;
mDexoptCommands.addAll(generatePackageDexopts(p, compilationReason));
}
for (PackageParser.Package p : others) {
// We assume here that there are no core apps left.
if (p.coreApp) {
throw new IllegalStateException("Found a core app that's not important");
}
mDexoptCommands.addAll(
generatePackageDexopts(p, PackageManagerService.REASON_FIRST_BOOT));
}
} }
completeSize = mDexoptPackages.size(); completeSize = mDexoptCommands.size();
mCommandsForCurrentPackage = null;
} }
@Override @Override
@@ -110,87 +131,52 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
if (DEBUG_DEXOPT) { if (DEBUG_DEXOPT) {
Log.i(TAG, "Cleaning up OTA Dexopt state."); Log.i(TAG, "Cleaning up OTA Dexopt state.");
} }
mDexoptPackages = null; mDexoptCommands = null;
mCommandsForCurrentPackage = null;
} }
@Override @Override
public synchronized boolean isDone() throws RemoteException { public synchronized boolean isDone() throws RemoteException {
if (mDexoptPackages == null) { if (mDexoptCommands == null) {
throw new IllegalStateException("done() called before prepare()"); throw new IllegalStateException("done() called before prepare()");
} }
return mDexoptPackages.isEmpty() && (mCommandsForCurrentPackage == null); return mDexoptCommands.isEmpty();
} }
@Override @Override
public synchronized float getProgress() throws RemoteException { public synchronized float getProgress() throws RemoteException {
// We approximate by number of packages here. We could track all compiles, if we // Approximate the progress by the amount of already completed commands.
// generated them ahead of time. Right now we're trying to conserve memory.
if (completeSize == 0) { if (completeSize == 0) {
return 1f; return 1f;
} }
int packagesLeft = mDexoptPackages.size() + (mCommandsForCurrentPackage != null ? 1 : 0); int commandsLeft = mDexoptCommands.size();
return (completeSize - packagesLeft) / ((float)completeSize); return (completeSize - commandsLeft) / ((float)completeSize);
}
/**
* Return the next dexopt command for the current package. Enforces the invariant
*/
private String getNextPackageDexopt() {
if (mCommandsForCurrentPackage != null) {
String next = mCommandsForCurrentPackage.remove(0);
if (mCommandsForCurrentPackage.isEmpty()) {
mCommandsForCurrentPackage = null;
}
return next;
}
return null;
} }
@Override @Override
public synchronized String nextDexoptCommand() throws RemoteException { public synchronized String nextDexoptCommand() throws RemoteException {
if (mDexoptPackages == null) { if (mDexoptCommands == null) {
throw new IllegalStateException("dexoptNextPackage() called before prepare()"); throw new IllegalStateException("dexoptNextPackage() called before prepare()");
} }
// Get the next command. if (mDexoptCommands.isEmpty()) {
for (;;) { return "(all done)";
// Check whether there's one for the current package. }
String next = getNextPackageDexopt();
if (next != null) {
return next;
}
// Move to the next package, if possible. String next = mDexoptCommands.remove(0);
if (mDexoptPackages.isEmpty()) {
return "Nothing to do";
}
PackageParser.Package nextPackage = mDexoptPackages.remove(0); if (IsFreeSpaceAvailable()) {
return next;
if (DEBUG_DEXOPT) { } else {
Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt."); mDexoptCommands.clear();
} return "(no free space)";
// Generate the next mPackageDexopts state. Ignore errors, this loop is strongly
// monotonically increasing, anyways.
generatePackageDexopts(nextPackage);
// Invariant check: mPackageDexopts is null or not empty.
if (mCommandsForCurrentPackage != null && mCommandsForCurrentPackage.isEmpty()) {
cleanup();
throw new IllegalStateException("mPackageDexopts empty for " + nextPackage);
}
} }
} }
/** /**
* Generate all dexopt commands for the given package and place them into mPackageDexopts. * Check for low space. Returns true if there's space left.
* Returns true on success, false in an error situation like low disk space.
*/ */
private synchronized boolean generatePackageDexopts(PackageParser.Package nextPackage) { private boolean IsFreeSpaceAvailable() {
// Check for low space.
// TODO: If apps are not installed in the internal /data partition, we should compare // TODO: If apps are not installed in the internal /data partition, we should compare
// against that storage's free capacity. // against that storage's free capacity.
File dataDir = Environment.getDataDirectory(); File dataDir = Environment.getDataDirectory();
@@ -200,12 +186,14 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
throw new IllegalStateException("Invalid low memory threshold"); throw new IllegalStateException("Invalid low memory threshold");
} }
long usableSpace = dataDir.getUsableSpace(); long usableSpace = dataDir.getUsableSpace();
if (usableSpace < lowThreshold) { return (usableSpace >= lowThreshold);
Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " + }
usableSpace);
return false;
}
/**
* Generate all dexopt commands for the given package.
*/
private synchronized List<String> generatePackageDexopts(PackageParser.Package pkg,
int compilationReason) {
// Use our custom connection that just collects the commands. // Use our custom connection that just collects the commands.
RecordingInstallerConnection collectingConnection = new RecordingInstallerConnection(); RecordingInstallerConnection collectingConnection = new RecordingInstallerConnection();
Installer collectingInstaller = new Installer(mContext, collectingConnection); Installer collectingInstaller = new Installer(mContext, collectingConnection);
@@ -213,71 +201,22 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
// Use the package manager install and install lock here for the OTA dex optimizer. // Use the package manager install and install lock here for the OTA dex optimizer.
PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer( PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
collectingInstaller, mPackageManagerService.mInstallLock, mContext); collectingInstaller, mPackageManagerService.mInstallLock, mContext);
// Make sure that core apps are optimized according to their own "reason".
// If the core apps are not preopted in the B OTA, and REASON_AB_OTA is not speed
// (by default is speed-profile) they will be interepreted/JITed. This in itself is not a
// problem as we will end up doing profile guided compilation. However, some core apps may
// be loaded by system server which doesn't JIT and we need to make sure we don't
// interpret-only
int compilationReason = nextPackage.coreApp
? PackageManagerService.REASON_CORE_APP
: PackageManagerService.REASON_AB_OTA;
optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, optimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
null /* ISAs */, false /* checkProfiles */, null /* ISAs */, false /* checkProfiles */,
getCompilerFilterForReason(compilationReason), getCompilerFilterForReason(compilationReason),
null /* CompilerStats.PackageStats */); null /* CompilerStats.PackageStats */);
mCommandsForCurrentPackage = collectingConnection.commands; return collectingConnection.commands;
if (mCommandsForCurrentPackage.isEmpty()) {
mCommandsForCurrentPackage = null;
}
return true;
} }
@Override @Override
public synchronized void dexoptNextPackage() throws RemoteException { public synchronized void dexoptNextPackage() throws RemoteException {
if (mDexoptPackages == null) { throw new UnsupportedOperationException();
throw new IllegalStateException("dexoptNextPackage() called before prepare()");
}
if (mDexoptPackages.isEmpty()) {
// Tolerate repeated calls.
return;
}
PackageParser.Package nextPackage = mDexoptPackages.remove(0);
if (DEBUG_DEXOPT) {
Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt.");
}
// Check for low space.
// TODO: If apps are not installed in the internal /data partition, we should compare
// against that storage's free capacity.
File dataDir = Environment.getDataDirectory();
@SuppressWarnings("deprecation")
long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
if (lowThreshold == 0) {
throw new IllegalStateException("Invalid low memory threshold");
}
long usableSpace = dataDir.getUsableSpace();
if (usableSpace < lowThreshold) {
Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " +
usableSpace);
return;
}
PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
mPackageManagerService.mInstaller, mPackageManagerService.mInstallLock, mContext);
optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, null /* ISAs */,
false /* checkProfiles */,
getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA),
mPackageManagerService.getOrCreateCompilerPackageStats(nextPackage));
} }
private void moveAbArtifacts(Installer installer) { private void moveAbArtifacts(Installer installer) {
if (mDexoptPackages != null) { if (mDexoptCommands != null) {
throw new IllegalStateException("Should not be ota-dexopting when trying to move."); throw new IllegalStateException("Should not be ota-dexopting when trying to move.");
} }