From f5a7bfc8d5f9e74d6fa1e5be3246e7b4cde65bf6 Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Mon, 13 Mar 2017 23:30:30 -0700 Subject: [PATCH] Register secondary dex files for JIT profiling Test: boot, and check that profiles get recorded for secondary dex files Bug: 32871170 Bug: 26719109 Change-Id: I2de23ef44eee3f1783ae698821f1c6d88c66c9a6 --- core/java/android/app/ActivityThread.java | 2 +- core/java/android/app/DexLoadReporter.java | 115 ++++++++++++++++++++- core/java/android/app/LoadedApk.java | 5 + 3 files changed, 119 insertions(+), 3 deletions(-) diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 92a02e9ebb0f3..85295987d9857 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -5578,7 +5578,7 @@ public final class ActivityThread { // Make sure we do this before calling onCreate so that we can capture the // complete application startup. if (SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) { - BaseDexClassLoader.setReporter(DexLoadReporter.INSTANCE); + BaseDexClassLoader.setReporter(DexLoadReporter.getInstance()); } // Install the Network Security Config Provider. This must happen before the application diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java index 009c44833e570..13f288ab74549 100644 --- a/core/java/android/app/DexLoadReporter.java +++ b/core/java/android/app/DexLoadReporter.java @@ -16,13 +16,21 @@ package android.app; +import android.os.FileUtils; import android.os.RemoteException; +import android.os.SystemProperties; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; + import dalvik.system.BaseDexClassLoader; import dalvik.system.VMRuntime; +import java.io.File; +import java.io.IOException; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * A dex load reporter which will notify package manager of any dex file loaded @@ -37,15 +45,60 @@ import java.util.List; /*package*/ class DexLoadReporter implements BaseDexClassLoader.Reporter { private static final String TAG = "DexLoadReporter"; - /*package*/ static final DexLoadReporter INSTANCE = new DexLoadReporter(); + private static final DexLoadReporter INSTANCE = new DexLoadReporter(); - private DexLoadReporter() {} + private static final boolean DEBUG = false; + + // We must guard the access to the list of data directories because + // we might have concurrent accesses. Apps might load dex files while + // new data dirs are registered (due to creation of LoadedApks via + // create createApplicationContext). + @GuardedBy("mDataDirs") + private final Set mDataDirs; + + private DexLoadReporter() { + mDataDirs = new HashSet<>(); + } + + /*package*/ static DexLoadReporter getInstance() { + return INSTANCE; + } + + /** + * Register an application data directory with the reporter. + * The data directories are used to determine if a dex file is secondary dex or not. + * Note that this method may be called multiple times for the same app, registering + * different data directories. This may happen when apps share the same user id + * ({@code android:sharedUserId}). For example, if app1 and app2 share the same user + * id, and app1 loads app2 apk, then both data directories will be registered. + */ + /*package*/ void registerAppDataDir(String packageName, String dataDir) { + if (DEBUG) { + Slog.i(TAG, "Package " + packageName + " registering data dir: " + dataDir); + } + // TODO(calin): A few code paths imply that the data dir + // might be null. Investigate when that can happen. + if (dataDir != null) { + synchronized (mDataDirs) { + mDataDirs.add(dataDir); + } + } + } @Override public void report(List dexPaths) { if (dexPaths.isEmpty()) { return; } + // Notify the package manager about the dex loads unconditionally. + // The load might be for either a primary or secondary dex file. + notifyPackageManager(dexPaths); + // Check for secondary dex files and register them for profiling if + // possible. + registerSecondaryDexForProfiling(dexPaths); + } + + private void notifyPackageManager(List dexPaths) { String packageName = ActivityThread.currentPackageName(); try { ActivityThread.getPackageManager().notifyDexLoad( @@ -54,4 +107,62 @@ import java.util.List; Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); } } + + private void registerSecondaryDexForProfiling(List dexPaths) { + if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) { + return; + } + // Make a copy of the current data directories so that we don't keep the lock + // while registering for profiling. The registration will perform I/O to + // check for or create the profile. + String[] dataDirs; + synchronized (mDataDirs) { + dataDirs = mDataDirs.toArray(new String[0]); + } + for (String dexPath : dexPaths) { + registerSecondaryDexForProfiling(dexPath, dataDirs); + } + } + + private void registerSecondaryDexForProfiling(String dexPath, String[] dataDirs) { + if (!isSecondaryDexFile(dexPath, dataDirs)) { + // The dex path is not a secondary dex file. Nothing to do. + return; + } + File secondaryProfile = getSecondaryProfileFile(dexPath); + try { + // Create the profile if not already there. + // Returns true if the file was created, false if the file already exists. + // or throws exceptions in case of errors. + boolean created = secondaryProfile.createNewFile(); + if (DEBUG && created) { + Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile); + } + } catch (IOException ex) { + Slog.e(TAG, "Failed to create profile for secondary dex " + secondaryProfile + + ":" + ex.getMessage()); + // Don't move forward with the registration if we failed to create the profile. + return; + } + + VMRuntime.registerAppInfo(secondaryProfile.getPath(), new String[] { dexPath }); + } + + // A dex file is a secondary dex file if it is in any of the registered app + // data directories. + private boolean isSecondaryDexFile(String dexPath, String[] dataDirs) { + for (String dataDir : dataDirs) { + if (FileUtils.contains(dataDir, dexPath)) { + return true; + } + } + return false; + } + + // Secondary dex profiles are stored next to the dex file and have the same + // name with '.prof' appended. + // NOTE: Keep in sync with installd. + private File getSecondaryProfileFile(String dexPath) { + return new File(dexPath + ".prof"); + } } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 8299c50ed005f..cf41e4e5dc9c8 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -750,6 +750,11 @@ public final class LoadedApk { VMRuntime.registerAppInfo(profileFile.getPath(), codePaths.toArray(new String[codePaths.size()])); + + // Register the app data directory with the reporter. It will + // help deciding whether or not a dex file is the primary apk or a + // secondary dex. + DexLoadReporter.getInstance().registerAppDataDir(mPackageName, mDataDir); } /**