Optionally use ART Service for optimizing packages.
It is only used when the system property dalvik.vm.useartservice is
true and the requested options are supported.
Test: adb root
adb shell cmd package force-dex-opt com.google.android.dialer
with dalvik.vm.useartservice=true and
DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE added to the
flags in DexOptHelper.forceDexOpt. Check that artd calls dex2oat and
that /data/dalvik-cache/x86_64/*@classes.dex are created and used
with:
adb shell am start -S -W -c android.intent.category.LAUNCHER \
-a android.intent.action.MAIN com.google.android.dialer
adb shell cat '/proc/`pidof com.google.android.dialer`/maps' \
| grep classes.dex
Bug: 251903639
Change-Id: Iea2fd2d1cb5d77e725f51c00211d7df43826700a
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 3f04264..c4f6836 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -18,6 +18,7 @@
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
import static com.android.server.pm.ApexManager.ActiveApexInfo;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
@@ -34,6 +35,7 @@
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.AppGlobals;
@@ -56,9 +58,16 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+import com.android.server.pm.PackageDexOptimizer.DexOptResult;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import dalvik.system.DexFile;
@@ -72,11 +81,15 @@
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
-final class DexOptHelper {
+/**
+ * Helper class for dex optimization operations in PackageManagerService.
+ */
+public final class DexOptHelper {
private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
private final PackageManagerService mPm;
@@ -405,11 +418,12 @@
* {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
* {@link PackageDexOptimizer#DEX_OPT_FAILED}
*/
- @PackageDexOptimizer.DexOptResult
+ @DexOptResult
/* package */ int performDexOptWithStatus(DexoptOptions options) {
return performDexOptTraced(options);
}
+ @DexOptResult
private int performDexOptTraced(DexoptOptions options) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
try {
@@ -421,7 +435,13 @@
// Run dexopt on a given package. Returns true if dexopt did not fail, i.e.
// if the package can now be considered up to date for the given filter.
+ @DexOptResult
private int performDexOptInternal(DexoptOptions options) {
+ Optional<Integer> artSrvRes = performDexOptWithArtService(options);
+ if (artSrvRes.isPresent()) {
+ return artSrvRes.get();
+ }
+
AndroidPackage p;
PackageSetting pkgSetting;
synchronized (mPm.mLock) {
@@ -446,8 +466,74 @@
}
}
- private int performDexOptInternalWithDependenciesLI(AndroidPackage p,
- @NonNull PackageStateInternal pkgSetting, DexoptOptions options) {
+ /**
+ * Performs dexopt on the given package using ART Service.
+ *
+ * @return a {@link DexOptResult}, or empty if the request isn't supported so that it is
+ * necessary to fall back to the legacy code paths.
+ */
+ private Optional<Integer> performDexOptWithArtService(DexoptOptions options) {
+ ArtManagerLocal artManager = getArtManagerLocal();
+ if (artManager == null) {
+ return Optional.empty();
+ }
+
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ getPackageManagerLocal().withFilteredSnapshot()) {
+ PackageState ops = snapshot.getPackageState(options.getPackageName());
+ if (ops == null) {
+ return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED);
+ }
+ AndroidPackage oap = ops.getAndroidPackage();
+ if (oap == null) {
+ return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED);
+ }
+ if (oap.isApex()) {
+ return Optional.of(PackageDexOptimizer.DEX_OPT_SKIPPED);
+ }
+
+ // TODO(b/245301593): Delete the conditional when ART Service supports
+ // FLAG_SHOULD_INCLUDE_DEPENDENCIES and we can just set it unconditionally.
+ /*@OptimizeFlags*/ int extraFlags = ops.getUsesLibraries().isEmpty()
+ ? 0
+ : ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES;
+
+ OptimizeParams params = options.convertToOptimizeParams(extraFlags);
+ if (params == null) {
+ return Optional.empty();
+ }
+
+ // TODO(b/251903639): Either remove controlDexOptBlocking, or don't ignore it here.
+ OptimizeResult result;
+ try {
+ result = artManager.optimizePackage(snapshot, options.getPackageName(), params);
+ } catch (UnsupportedOperationException e) {
+ reportArtManagerFallback(options.getPackageName(), e.toString());
+ return Optional.empty();
+ }
+
+ // TODO(b/251903639): Move this to ArtManagerLocal.addOptimizePackageDoneCallback when
+ // it is implemented.
+ for (OptimizeResult.PackageOptimizeResult pkgRes : result.getPackageOptimizeResults()) {
+ PackageState ps = snapshot.getPackageState(pkgRes.getPackageName());
+ AndroidPackage ap = ps != null ? ps.getAndroidPackage() : null;
+ if (ap != null) {
+ CompilerStats.PackageStats stats = mPm.getOrCreateCompilerPackageStats(ap);
+ for (OptimizeResult.DexContainerFileOptimizeResult dexRes :
+ pkgRes.getDexContainerFileOptimizeResults()) {
+ stats.setCompileTime(
+ dexRes.getDexContainerFile(), dexRes.getDex2oatWallTimeMillis());
+ }
+ }
+ }
+
+ return Optional.of(convertToDexOptResult(result));
+ }
+ }
+
+ @DexOptResult
+ private int performDexOptInternalWithDependenciesLI(
+ AndroidPackage p, @NonNull PackageStateInternal pkgSetting, DexoptOptions options) {
// System server gets a special path.
if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) {
return mPm.getDexManager().dexoptSystemServer(options);
@@ -514,10 +600,20 @@
// Whoever is calling forceDexOpt wants a compiled package.
// Don't use profiles since that may cause compilation to be skipped.
- final int res = performDexOptInternalWithDependenciesLI(pkg, packageState,
- new DexoptOptions(packageName, REASON_CMDLINE,
- getDefaultCompilerFilter(), null /* splitName */,
- DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
+ DexoptOptions options = new DexoptOptions(packageName, REASON_CMDLINE,
+ getDefaultCompilerFilter(), null /* splitName */,
+ DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE);
+
+ // performDexOptWithArtService ignores the snapshot and takes its own, so it can race with
+ // the package checks above, but at worst the effect is only a bit less friendly error
+ // below.
+ Optional<Integer> artSrvRes = performDexOptWithArtService(options);
+ int res;
+ if (artSrvRes.isPresent()) {
+ res = artSrvRes.get();
+ } else {
+ res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
+ }
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -800,4 +896,59 @@
}
return false;
}
+
+ private @NonNull PackageManagerLocal getPackageManagerLocal() {
+ try {
+ return LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal.class);
+ } catch (ManagerNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Called whenever we need to fall back from ART Service to the legacy dexopt code.
+ */
+ public static void reportArtManagerFallback(String packageName, String reason) {
+ // STOPSHIP(b/251903639): Minimize these calls to avoid platform getting shipped with code
+ // paths that will always bypass ART Service.
+ Slog.i(TAG, "Falling back to old PackageManager dexopt for " + packageName + ": " + reason);
+ }
+
+ /**
+ * Returns {@link ArtManagerLocal} if one is found and should be used for package optimization.
+ */
+ private @Nullable ArtManagerLocal getArtManagerLocal() {
+ if (!"true".equals(SystemProperties.get("dalvik.vm.useartservice", ""))) {
+ return null;
+ }
+ try {
+ return LocalManagerRegistry.getManagerOrThrow(ArtManagerLocal.class);
+ } catch (ManagerNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Converts an ART Service {@link OptimizeResult} to {@link DexOptResult}.
+ *
+ * For interfacing {@link ArtManagerLocal} with legacy dex optimization code in PackageManager.
+ */
+ @DexOptResult
+ private static int convertToDexOptResult(OptimizeResult result) {
+ /*@OptimizeStatus*/ int status = result.getFinalStatus();
+ switch (status) {
+ case OptimizeResult.OPTIMIZE_SKIPPED:
+ return PackageDexOptimizer.DEX_OPT_SKIPPED;
+ case OptimizeResult.OPTIMIZE_FAILED:
+ return PackageDexOptimizer.DEX_OPT_FAILED;
+ case OptimizeResult.OPTIMIZE_PERFORMED:
+ return PackageDexOptimizer.DEX_OPT_PERFORMED;
+ case OptimizeResult.OPTIMIZE_CANCELLED:
+ return PackageDexOptimizer.DEX_OPT_CANCELLED;
+ default:
+ throw new IllegalArgumentException("OptimizeResult for "
+ + result.getPackageOptimizeResults().get(0).getPackageName()
+ + " has unsupported status " + status);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index 13f6ee7..f5557c4 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -18,6 +18,16 @@
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
+import android.annotation.Nullable;
+
+import com.android.server.art.ReasonMapping;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.pm.DexOptHelper;
+import com.android.server.pm.PackageManagerService;
+
+import dalvik.system.DexFile;
+
/**
* Options used for dexopt invocations.
*/
@@ -189,4 +199,133 @@
mSplitName,
mFlags);
}
+
+ /**
+ * Returns an {@link OptimizeParams} instance corresponding to this object, for use with
+ * {@link com.android.server.art.ArtManagerLocal}.
+ *
+ * @param extraFlags extra {@link ArtFlags#OptimizeFlags} to set in the returned
+ * {@code OptimizeParams} beyond those converted from this object
+ * @return null if the settings cannot be accurately represented, and hence the old
+ * PackageManager/installd code paths need to be used.
+ */
+ public @Nullable OptimizeParams convertToOptimizeParams(/*@OptimizeFlags*/ int extraFlags) {
+ if (mSplitName != null) {
+ DexOptHelper.reportArtManagerFallback(
+ mPackageName, "Request to optimize only split " + mSplitName);
+ return null;
+ }
+
+ /*@OptimizeFlags*/ int flags = extraFlags;
+ if ((mFlags & DEXOPT_CHECK_FOR_PROFILES_UPDATES) == 0
+ && DexFile.isProfileGuidedCompilerFilter(mCompilerFilter)) {
+ // ART Service doesn't support bypassing this, so not setting this flag is not
+ // supported.
+ DexOptHelper.reportArtManagerFallback(mPackageName,
+ "DEXOPT_CHECK_FOR_PROFILES_UPDATES not set with profile compiler filter");
+ return null;
+ }
+ if ((mFlags & DEXOPT_FORCE) != 0) {
+ flags |= ArtFlags.FLAG_FORCE;
+ }
+ if ((mFlags & DEXOPT_ONLY_SECONDARY_DEX) != 0) {
+ flags |= ArtFlags.FLAG_FOR_SECONDARY_DEX;
+ } else {
+ flags |= ArtFlags.FLAG_FOR_PRIMARY_DEX;
+ }
+ if ((mFlags & DEXOPT_DOWNGRADE) != 0) {
+ flags |= ArtFlags.FLAG_SHOULD_DOWNGRADE;
+ }
+ if ((mFlags & DEXOPT_INSTALL_WITH_DEX_METADATA_FILE) == 0) {
+ // ART Service cannot be instructed to ignore a DM file if present, so not setting this
+ // flag is not supported.
+ DexOptHelper.reportArtManagerFallback(
+ mPackageName, "DEXOPT_INSTALL_WITH_DEX_METADATA_FILE not set");
+ return null;
+ }
+
+ /*@PriorityClassApi*/ int priority;
+ // Replicates logic in RunDex2Oat::PrepareCompilerRuntimeAndPerfConfigFlags in installd.
+ if ((mFlags & DEXOPT_BOOT_COMPLETE) != 0) {
+ if ((mFlags & DEXOPT_FOR_RESTORE) != 0) {
+ priority = ArtFlags.PRIORITY_INTERACTIVE_FAST;
+ } else {
+ // TODO(b/251903639): Repurpose DEXOPT_IDLE_BACKGROUND_JOB to choose new
+ // dalvik.vm.background-dex2oat-* properties.
+ priority = ArtFlags.PRIORITY_INTERACTIVE;
+ }
+ } else {
+ priority = ArtFlags.PRIORITY_BOOT;
+ }
+
+ // The following flags in mFlags are ignored:
+ //
+ // - DEXOPT_AS_SHARED_LIBRARY: It's implicit with ART Service since it always looks at
+ // <uses-library> rather than actual dependencies.
+ //
+ // We don't require it to be set either. It's safe when switching between old and new
+ // code paths since the only effect is that some packages may be unnecessarily compiled
+ // without user profiles.
+ //
+ // - DEXOPT_IDLE_BACKGROUND_JOB: Its only effect is to allow the debug variant dex2oatd to
+ // be used, but ART Service never uses that (cf. Artd::GetDex2Oat in artd.cc).
+
+ String reason;
+ switch (mCompilationReason) {
+ case PackageManagerService.REASON_FIRST_BOOT:
+ reason = ReasonMapping.REASON_FIRST_BOOT;
+ break;
+ case PackageManagerService.REASON_BOOT_AFTER_OTA:
+ reason = ReasonMapping.REASON_BOOT_AFTER_OTA;
+ break;
+ case PackageManagerService.REASON_POST_BOOT:
+ // This reason will go away with the legacy dexopt code.
+ DexOptHelper.reportArtManagerFallback(
+ mPackageName, "Unsupported compilation reason REASON_POST_BOOT");
+ return null;
+ case PackageManagerService.REASON_INSTALL:
+ reason = ReasonMapping.REASON_INSTALL;
+ break;
+ case PackageManagerService.REASON_INSTALL_FAST:
+ reason = ReasonMapping.REASON_INSTALL_FAST;
+ break;
+ case PackageManagerService.REASON_INSTALL_BULK:
+ reason = ReasonMapping.REASON_INSTALL_BULK;
+ break;
+ case PackageManagerService.REASON_INSTALL_BULK_SECONDARY:
+ reason = ReasonMapping.REASON_INSTALL_BULK_SECONDARY;
+ break;
+ case PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED:
+ reason = ReasonMapping.REASON_INSTALL_BULK_DOWNGRADED;
+ break;
+ case PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED:
+ reason = ReasonMapping.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
+ break;
+ case PackageManagerService.REASON_BACKGROUND_DEXOPT:
+ reason = ReasonMapping.REASON_BG_DEXOPT;
+ break;
+ case PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE:
+ reason = ReasonMapping.REASON_INACTIVE;
+ break;
+ case PackageManagerService.REASON_CMDLINE:
+ reason = ReasonMapping.REASON_CMDLINE;
+ break;
+ case PackageManagerService.REASON_SHARED:
+ case PackageManagerService.REASON_AB_OTA:
+ // REASON_SHARED shouldn't go into this code path - it's only used at lower levels
+ // in PackageDexOptimizer.
+ // TODO(b/251921228): OTA isn't supported, so REASON_AB_OTA shouldn't come this way
+ // either.
+ throw new UnsupportedOperationException(
+ "ART Service unsupported compilation reason " + mCompilationReason);
+ default:
+ throw new IllegalArgumentException(
+ "Invalid compilation reason " + mCompilationReason);
+ }
+
+ return new OptimizeParams.Builder(reason, flags)
+ .setCompilerFilter(mCompilerFilter)
+ .setPriorityClass(priority)
+ .build();
+ }
}