Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e83feea2 authored by Calin Juravle's avatar Calin Juravle Committed by Android (Google) Code Review
Browse files

Merge "Record data about dex files use on disk"

parents 45e63815 b8976d8f
Loading
Loading
Loading
Loading
+27 −1
Original line number Diff line number Diff line
@@ -260,6 +260,7 @@ import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PermissionsState.PermissionState;
import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.pm.Settings.VersionInfo;
import com.android.server.pm.dex.DexManager;
import com.android.server.storage.DeviceStorageMonitorInternal;
import dalvik.system.CloseGuard;
@@ -303,6 +304,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -748,6 +750,9 @@ public class PackageManagerService extends IPackageManager.Stub {
    final PackageInstallerService mInstallerService;
    private final PackageDexOptimizer mPackageDexOptimizer;
    // DexManager handles the usage of dex files (e.g. secondary files, whether or not a package
    // is used by other apps).
    private final DexManager mDexManager;
    private AtomicInteger mNextMoveId = new AtomicInteger();
    private final MoveCallbacks mMoveCallbacks;
@@ -2165,6 +2170,7 @@ public class PackageManagerService extends IPackageManager.Stub {
        mInstaller = installer;
        mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                "*dexopt*");
        mDexManager = new DexManager();
        mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
        mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -2616,6 +2622,19 @@ public class PackageManagerService extends IPackageManager.Stub {
            mPackageUsage.read(mPackages);
            mCompilerStats.read();
            // Read and update the usage of dex files.
            // At this point we know the code paths  of the packages, so we can validate
            // the disk file and build the internal cache.
            // The usage file is expected to be small so loading and verifying it
            // should take a fairly small time compare to the other activities (e.g. package
            // scanning).
            final Map<Integer, List<PackageInfo>> userPackages = new HashMap<>();
            final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
            for (int userId : currentUserIds) {
                userPackages.put(userId, getInstalledPackages(/*flags*/ 0, userId).getList());
            }
            mDexManager.load(userPackages);
            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
                    SystemClock.uptimeMillis());
            Slog.i(TAG, "Time to scan packages: "
@@ -7641,7 +7660,14 @@ public class PackageManagerService extends IPackageManager.Stub {
    @Override
    public void notifyDexLoad(String loadingPackageName, List<String> dexPaths, String loaderIsa) {
      // TODO(calin): b/32871170
        int userId = UserHandle.getCallingUserId();
        ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
        if (ai == null) {
            Slog.w(TAG, "Loading a package that does not exist for the calling user. package="
                + loadingPackageName + ", user=" + userId);
            return;
        }
        mDexManager.notifyDexLoad(ai, dexPaths, loaderIsa, userId);
    }
    // TODO: this is not used nor needed. Delete it.
+324 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.server.pm.dex;

import android.content.pm.PackageInfo;
import android.content.pm.PackageParser;
import android.content.pm.ApplicationInfo;

import android.util.Slog;

import com.android.server.pm.PackageManagerServiceUtils;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * This class keeps track of how dex files are used.
 * Every time it gets a notification about a dex file being loaded it tracks
 * its owning package and records it in PackageDexUsage (package-dex-usage.list).
 *
 * TODO(calin): Extract related dexopt functionality from PackageManagerService
 * into this class.
 */
public class DexManager {
    private static final String TAG = "DexManager";

    private static final boolean DEBUG = false;

    // Maps package name to code locations.
    // It caches the code locations for the installed packages. This allows for
    // faster lookups (no locks) when finding what package owns the dex file.
    private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache;

    // PackageDexUsage handles the actual I/O operations. It is responsible to
    // encode and save the dex usage data.
    private final PackageDexUsage mPackageDexUsage;

    // Possible outcomes of a dex search.
    private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
    private static int DEX_SEARCH_FOUND_PRIMARY = 1;  // dex file is the primary/base apk
    private static int DEX_SEARCH_FOUND_SPLIT = 2;  // dex file is a split apk
    private static int DEX_SEARCH_FOUND_SECONDARY = 3;  // dex file is a secondary dex

    public DexManager() {
      mPackageCodeLocationsCache = new HashMap<>();
      mPackageDexUsage = new PackageDexUsage();
    }

    /**
     * Notify about dex files loads.
     * Note that this method is invoked when apps load dex files and it should
     * return as fast as possible.
     *
     * @param loadingPackage the package performing the load
     * @param dexPaths the list of dex files being loaded
     * @param loaderIsa the ISA of the app loading the dex files
     * @param loaderUserId the user id which runs the code loading the dex files
     */
    public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths,
            String loaderIsa, int loaderUserId) {
        try {
            notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId);
        } catch (Exception e) {
            Slog.w(TAG, "Exception while notifying dex load for package " +
                    loadingAppInfo.packageName, e);
        }
    }

    private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths,
            String loaderIsa, int loaderUserId) {
        if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
            Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " +
                    loaderIsa + "?");
            return;
        }

        for (String dexPath : dexPaths) {
            // Find the owning package name.
            DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);

            if (DEBUG) {
                Slog.i(TAG, loadingAppInfo.packageName
                    + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath);
            }

            if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) {
                // TODO(calin): extend isUsedByOtherApps check to detect the cases where
                // different apps share the same runtime. In that case we should not mark the dex
                // file as isUsedByOtherApps. Currently this is a safe approximation.
                boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals(
                        searchResult.mOwningPackageName);
                boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
                        searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT;

                if (primaryOrSplit && !isUsedByOtherApps) {
                    // If the dex file is the primary apk (or a split) and not isUsedByOtherApps
                    // do not record it. This case does not bring any new usable information
                    // and can be safely skipped.
                    continue;
                }

                // Record dex file usage. If the current usage is a new pattern (e.g. new secondary,
                // or UsedBytOtherApps), record will return true and we trigger an async write
                // to disk to make sure we don't loose the data in case of a reboot.
                if (mPackageDexUsage.record(searchResult.mOwningPackageName,
                        dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit)) {
                    mPackageDexUsage.maybeWriteAsync();
                }
            } else {
                // This can happen in a few situations:
                // - bogus dex loads
                // - recent installs/uninstalls that we didn't detect.
                // - new installed splits
                // If we can't find the owner of the dex we simply do not track it. The impact is
                // that the dex file will not be considered for offline optimizations.
                // TODO(calin): add hooks for install/uninstall notifications to
                // capture new or obsolete packages.
                if (DEBUG) {
                    Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
                }
            }
        }
    }

    /**
     * Read the dex usage from disk and populate the code cache locations.
     * @param existingPackages a map containing information about what packages
     *          are available to what users. Only packages in this list will be
     *          recognized during notifyDexLoad().
     */
    public void load(Map<Integer, List<PackageInfo>> existingPackages) {
        try {
            loadInternal(existingPackages);
        } catch (Exception e) {
            mPackageDexUsage.clear();
            Slog.w(TAG, "Exception while loading package dex usage. " +
                    "Starting with a fresh state.", e);
        }
    }

    private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
        Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
        // Cache the code locations for the installed packages. This allows for
        // faster lookups (no locks) when finding what package owns the dex file.
        for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) {
            List<PackageInfo> packageInfoList = entry.getValue();
            int userId = entry.getKey();
            for (PackageInfo pi : packageInfoList) {
                // Cache the code locations.
                PackageCodeLocations pcl = mPackageCodeLocationsCache.get(pi.packageName);
                if (pcl != null) {
                    pcl.mergeAppDataDirs(pi.applicationInfo, userId);
                } else {
                    mPackageCodeLocationsCache.put(pi.packageName,
                        new PackageCodeLocations(pi.applicationInfo, userId));
                }
                // Cache a map from package name to the set of user ids who installed the package.
                // We will use it to sync the data and remove obsolete entries from
                // mPackageDexUsage.
                Set<Integer> users = putIfAbsent(
                        packageToUsersMap, pi.packageName, new HashSet<>());
                users.add(userId);
            }
        }

        mPackageDexUsage.read();
        mPackageDexUsage.syncData(packageToUsersMap);
    }

    /**
     * Get the package dex usage for the given package name.
     * @return the package data or null if there is no data available for this package.
     */
    public PackageDexUsage.PackageUseInfo getPackageUseInfo(String packageName) {
        return mPackageDexUsage.getPackageUseInfo(packageName);
    }

    /**
     * Retrieves the package which owns the given dexPath.
     */
    private DexSearchResult getDexPackage(
            ApplicationInfo loadingAppInfo, String dexPath, int userId) {
        // Ignore framework code.
        // TODO(calin): is there a better way to detect it?
        if (dexPath.startsWith("/system/framework/")) {
            new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND);
        }

        // First, check if the package which loads the dex file actually owns it.
        // Most of the time this will be true and we can return early.
        PackageCodeLocations loadingPackageCodeLocations =
                new PackageCodeLocations(loadingAppInfo, userId);
        int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId);
        if (outcome != DEX_SEARCH_NOT_FOUND) {
            // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level.
            return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome);
        }

        // The loadingPackage does not own the dex file.
        // Perform a reverse look-up in the cache to detect if any package has ownership.
        // Note that we can have false negatives if the cache falls out of date.
        for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) {
            outcome = pcl.searchDex(dexPath, userId);
            if (outcome != DEX_SEARCH_NOT_FOUND) {
                return new DexSearchResult(pcl.mPackageName, outcome);
            }
        }

        // Cache miss. Return not found for the moment.
        //
        // TODO(calin): this may be because of a newly installed package, an update
        // or a new added user. We can either perform a full look up again or register
        // observers to be notified of package/user updates.
        return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND);
    }

    private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) {
        V existingValue = map.putIfAbsent(key, newValue);
        return existingValue == null ? newValue : existingValue;
    }

    /**
     * Convenience class to store the different locations where a package might
     * own code.
     */
    private static class PackageCodeLocations {
        private final String mPackageName;
        private final String mBaseCodePath;
        private final Set<String> mSplitCodePaths;
        // Maps user id to the application private directory.
        private final Map<Integer, Set<String>> mAppDataDirs;

        public PackageCodeLocations(ApplicationInfo ai, int userId) {
            mPackageName = ai.packageName;
            mBaseCodePath = ai.sourceDir;
            mSplitCodePaths = new HashSet<>();
            if (ai.splitSourceDirs != null) {
                for (String split : ai.splitSourceDirs) {
                    mSplitCodePaths.add(split);
                }
            }
            mAppDataDirs = new HashMap<>();
            mergeAppDataDirs(ai, userId);
        }

        public void mergeAppDataDirs(ApplicationInfo ai, int userId) {
            Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
            dataDirs.add(ai.dataDir);

            // Compute and cache the real path as well since data dir may be a symlink.
            // e.g. /data/data/ -> /data/user/0/
            try {
                dataDirs.add(PackageManagerServiceUtils.realpath(new File(ai.dataDir)));
            } catch (IOException e) {
                Slog.w(TAG, "Error to get realpath of " + ai.dataDir, e);
            }

        }

        public int searchDex(String dexPath, int userId) {
            // First check that this package is installed or active for the given user.
            // If we don't have a data dir it means this user is trying to load something
            // unavailable for them.
            Set<String> userDataDirs = mAppDataDirs.get(userId);
            if (userDataDirs == null) {
                Slog.w(TAG, "Trying to load a dex path which does not exist for the current " +
                        "user. dexPath=" + dexPath + ", userId=" + userId);
                return DEX_SEARCH_NOT_FOUND;
            }

            if (mBaseCodePath.equals(dexPath)) {
                return DEX_SEARCH_FOUND_PRIMARY;
            }
            if (mSplitCodePaths.contains(dexPath)) {
                return DEX_SEARCH_FOUND_SPLIT;
            }
            for (String dataDir : userDataDirs) {
                if (dexPath.startsWith(dataDir)) {
                    return DEX_SEARCH_FOUND_SECONDARY;
                }
            }
            return DEX_SEARCH_NOT_FOUND;
        }
    }

    /**
     * Convenience class to store ownership search results.
     */
    private class DexSearchResult {
        private String mOwningPackageName;
        private int mOutcome;

        public DexSearchResult(String owningPackageName, int outcome) {
            this.mOwningPackageName = owningPackageName;
            this.mOutcome = outcome;
        }

        @Override
        public String toString() {
            return mOwningPackageName + "-" + mOutcome;
        }
    }


}
+282 −0

File added.

Preview size limit exceeded, changes collapsed.