Home | History | Annotate | Download | only in pm
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.pm;
     18 
     19 import static com.android.server.pm.Installer.DEXOPT_OTA;
     20 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
     21 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
     22 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
     23 
     24 import android.content.Context;
     25 import android.content.pm.IOtaDexopt;
     26 import android.content.pm.PackageParser;
     27 import android.os.Environment;
     28 import android.os.RemoteException;
     29 import android.os.ResultReceiver;
     30 import android.os.ServiceManager;
     31 import android.os.storage.StorageManager;
     32 import android.util.Log;
     33 import android.util.Slog;
     34 import com.android.internal.logging.MetricsLogger;
     35 import com.android.internal.os.InstallerConnection;
     36 import com.android.internal.os.InstallerConnection.InstallerException;
     37 
     38 import java.io.File;
     39 import java.io.FileDescriptor;
     40 import java.util.ArrayList;
     41 import java.util.Collection;
     42 import java.util.List;
     43 import java.util.concurrent.TimeUnit;
     44 
     45 /**
     46  * A service for A/B OTA dexopting.
     47  *
     48  * {@hide}
     49  */
     50 public class OtaDexoptService extends IOtaDexopt.Stub {
     51     private final static String TAG = "OTADexopt";
     52     private final static boolean DEBUG_DEXOPT = true;
     53 
     54     // The synthetic library dependencies denoting "no checks."
     55     private final static String[] NO_LIBRARIES = new String[] { "&" };
     56 
     57     // The amount of "available" (free - low threshold) space necessary at the start of an OTA to
     58     // not bulk-delete unused apps' odex files.
     59     private final static long BULK_DELETE_THRESHOLD = 1024 * 1024 * 1024;  // 1GB.
     60 
     61     private final Context mContext;
     62     private final PackageManagerService mPackageManagerService;
     63 
     64     // TODO: Evaluate the need for WeakReferences here.
     65 
     66     /**
     67      * The list of dexopt invocations for all work.
     68      */
     69     private List<String> mDexoptCommands;
     70 
     71     private int completeSize;
     72 
     73     // MetricsLogger properties.
     74 
     75     // Space before and after.
     76     private long availableSpaceBefore;
     77     private long availableSpaceAfterBulkDelete;
     78     private long availableSpaceAfterDexopt;
     79 
     80     // Packages.
     81     private int importantPackageCount;
     82     private int otherPackageCount;
     83 
     84     // Number of dexopt commands. This may be different from the count of packages.
     85     private int dexoptCommandCountTotal;
     86     private int dexoptCommandCountExecuted;
     87 
     88     // For spent time.
     89     private long otaDexoptTimeStart;
     90 
     91 
     92     public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
     93         this.mContext = context;
     94         this.mPackageManagerService = packageManagerService;
     95 
     96         // Now it's time to check whether we need to move any A/B artifacts.
     97         moveAbArtifacts(packageManagerService.mInstaller);
     98     }
     99 
    100     public static OtaDexoptService main(Context context,
    101             PackageManagerService packageManagerService) {
    102         OtaDexoptService ota = new OtaDexoptService(context, packageManagerService);
    103         ServiceManager.addService("otadexopt", ota);
    104 
    105         return ota;
    106     }
    107 
    108     @Override
    109     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
    110             String[] args, ResultReceiver resultReceiver) throws RemoteException {
    111         (new OtaDexoptShellCommand(this)).exec(
    112                 this, in, out, err, args, resultReceiver);
    113     }
    114 
    115     @Override
    116     public synchronized void prepare() throws RemoteException {
    117         if (mDexoptCommands != null) {
    118             throw new IllegalStateException("already called prepare()");
    119         }
    120         final List<PackageParser.Package> important;
    121         final List<PackageParser.Package> others;
    122         synchronized (mPackageManagerService.mPackages) {
    123             // Important: the packages we need to run with ab-ota compiler-reason.
    124             important = PackageManagerServiceUtils.getPackagesForDexopt(
    125                     mPackageManagerService.mPackages.values(), mPackageManagerService);
    126             // Others: we should optimize this with the (first-)boot compiler-reason.
    127             others = new ArrayList<>(mPackageManagerService.mPackages.values());
    128             others.removeAll(important);
    129 
    130             // Pre-size the array list by over-allocating by a factor of 1.5.
    131             mDexoptCommands = new ArrayList<>(3 * mPackageManagerService.mPackages.size() / 2);
    132         }
    133 
    134         for (PackageParser.Package p : important) {
    135             // Make sure that core apps are optimized according to their own "reason".
    136             // If the core apps are not preopted in the B OTA, and REASON_AB_OTA is not speed
    137             // (by default is speed-profile) they will be interepreted/JITed. This in itself is
    138             // not a problem as we will end up doing profile guided compilation. However, some
    139             // core apps may be loaded by system server which doesn't JIT and we need to make
    140             // sure we don't interpret-only
    141             int compilationReason = p.coreApp
    142                     ? PackageManagerService.REASON_CORE_APP
    143                     : PackageManagerService.REASON_AB_OTA;
    144             mDexoptCommands.addAll(generatePackageDexopts(p, compilationReason));
    145         }
    146         for (PackageParser.Package p : others) {
    147             // We assume here that there are no core apps left.
    148             if (p.coreApp) {
    149                 throw new IllegalStateException("Found a core app that's not important");
    150             }
    151             mDexoptCommands.addAll(
    152                     generatePackageDexopts(p, PackageManagerService.REASON_FIRST_BOOT));
    153         }
    154         completeSize = mDexoptCommands.size();
    155 
    156         long spaceAvailable = getAvailableSpace();
    157         if (spaceAvailable < BULK_DELETE_THRESHOLD) {
    158             Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: "
    159                     + PackageManagerServiceUtils.packagesToString(others));
    160             for (PackageParser.Package pkg : others) {
    161                 deleteOatArtifactsOfPackage(pkg);
    162             }
    163         }
    164         long spaceAvailableNow = getAvailableSpace();
    165 
    166         prepareMetricsLogging(important.size(), others.size(), spaceAvailable, spaceAvailableNow);
    167     }
    168 
    169     @Override
    170     public synchronized void cleanup() throws RemoteException {
    171         if (DEBUG_DEXOPT) {
    172             Log.i(TAG, "Cleaning up OTA Dexopt state.");
    173         }
    174         mDexoptCommands = null;
    175         availableSpaceAfterDexopt = getAvailableSpace();
    176 
    177         performMetricsLogging();
    178     }
    179 
    180     @Override
    181     public synchronized boolean isDone() throws RemoteException {
    182         if (mDexoptCommands == null) {
    183             throw new IllegalStateException("done() called before prepare()");
    184         }
    185 
    186         return mDexoptCommands.isEmpty();
    187     }
    188 
    189     @Override
    190     public synchronized float getProgress() throws RemoteException {
    191         // Approximate the progress by the amount of already completed commands.
    192         if (completeSize == 0) {
    193             return 1f;
    194         }
    195         int commandsLeft = mDexoptCommands.size();
    196         return (completeSize - commandsLeft) / ((float)completeSize);
    197     }
    198 
    199     @Override
    200     public synchronized String nextDexoptCommand() throws RemoteException {
    201         if (mDexoptCommands == null) {
    202             throw new IllegalStateException("dexoptNextPackage() called before prepare()");
    203         }
    204 
    205         if (mDexoptCommands.isEmpty()) {
    206             return "(all done)";
    207         }
    208 
    209         String next = mDexoptCommands.remove(0);
    210 
    211         if (getAvailableSpace() > 0) {
    212             dexoptCommandCountExecuted++;
    213 
    214             return next;
    215         } else {
    216             if (DEBUG_DEXOPT) {
    217                 Log.w(TAG, "Not enough space for OTA dexopt, stopping with "
    218                         + (mDexoptCommands.size() + 1) + " commands left.");
    219             }
    220             mDexoptCommands.clear();
    221             return "(no free space)";
    222         }
    223     }
    224 
    225     private long getMainLowSpaceThreshold() {
    226         File dataDir = Environment.getDataDirectory();
    227         @SuppressWarnings("deprecation")
    228         long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
    229         if (lowThreshold == 0) {
    230             throw new IllegalStateException("Invalid low memory threshold");
    231         }
    232         return lowThreshold;
    233     }
    234 
    235     /**
    236      * Returns the difference of free space to the low-storage-space threshold. Positive values
    237      * indicate free bytes.
    238      */
    239     private long getAvailableSpace() {
    240         // TODO: If apps are not installed in the internal /data partition, we should compare
    241         //       against that storage's free capacity.
    242         long lowThreshold = getMainLowSpaceThreshold();
    243 
    244         File dataDir = Environment.getDataDirectory();
    245         long usableSpace = dataDir.getUsableSpace();
    246 
    247         return usableSpace - lowThreshold;
    248     }
    249 
    250     private static String getOatDir(PackageParser.Package pkg) {
    251         if (!pkg.canHaveOatDir()) {
    252             return null;
    253         }
    254         File codePath = new File(pkg.codePath);
    255         if (codePath.isDirectory()) {
    256             return PackageDexOptimizer.getOatDir(codePath).getAbsolutePath();
    257         }
    258         return null;
    259     }
    260 
    261     private void deleteOatArtifactsOfPackage(PackageParser.Package pkg) {
    262         String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
    263         for (String codePath : pkg.getAllCodePaths()) {
    264             for (String isa : instructionSets) {
    265                 try {
    266                     mPackageManagerService.mInstaller.deleteOdex(codePath, isa, getOatDir(pkg));
    267                 } catch (InstallerException e) {
    268                     Log.e(TAG, "Failed deleting oat files for " + codePath, e);
    269                 }
    270             }
    271         }
    272     }
    273 
    274     /**
    275      * Generate all dexopt commands for the given package.
    276      */
    277     private synchronized List<String> generatePackageDexopts(PackageParser.Package pkg,
    278             int compilationReason) {
    279         // Use our custom connection that just collects the commands.
    280         RecordingInstallerConnection collectingConnection = new RecordingInstallerConnection();
    281         Installer collectingInstaller = new Installer(mContext, collectingConnection);
    282 
    283         // Use the package manager install and install lock here for the OTA dex optimizer.
    284         PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
    285                 collectingInstaller, mPackageManagerService.mInstallLock, mContext);
    286 
    287         String[] libraryDependencies = pkg.usesLibraryFiles;
    288         if (pkg.isSystemApp()) {
    289             // For system apps, we want to avoid classpaths checks.
    290             libraryDependencies = NO_LIBRARIES;
    291         }
    292 
    293         optimizer.performDexOpt(pkg, libraryDependencies,
    294                 null /* ISAs */, false /* checkProfiles */,
    295                 getCompilerFilterForReason(compilationReason),
    296                 null /* CompilerStats.PackageStats */);
    297 
    298         return collectingConnection.commands;
    299     }
    300 
    301     @Override
    302     public synchronized void dexoptNextPackage() throws RemoteException {
    303         throw new UnsupportedOperationException();
    304     }
    305 
    306     private void moveAbArtifacts(Installer installer) {
    307         if (mDexoptCommands != null) {
    308             throw new IllegalStateException("Should not be ota-dexopting when trying to move.");
    309         }
    310 
    311         // Look into all packages.
    312         Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages();
    313         for (PackageParser.Package pkg : pkgs) {
    314             if (pkg == null) {
    315                 continue;
    316             }
    317 
    318             // Does the package have code? If not, there won't be any artifacts.
    319             if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
    320                 continue;
    321             }
    322             if (pkg.codePath == null) {
    323                 Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath");
    324                 continue;
    325             }
    326 
    327             // If the path is in /system or /vendor, ignore. It will have been ota-dexopted into
    328             // /data/ota and moved into the dalvik-cache already.
    329             if (pkg.codePath.startsWith("/system") || pkg.codePath.startsWith("/vendor")) {
    330                 continue;
    331             }
    332 
    333             final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
    334             final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
    335             final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
    336             for (String dexCodeInstructionSet : dexCodeInstructionSets) {
    337                 for (String path : paths) {
    338                     String oatDir = PackageDexOptimizer.getOatDir(new File(pkg.codePath)).
    339                             getAbsolutePath();
    340 
    341                     // TODO: Check first whether there is an artifact, to save the roundtrip time.
    342 
    343                     try {
    344                         installer.moveAb(path, dexCodeInstructionSet, oatDir);
    345                     } catch (InstallerException e) {
    346                     }
    347                 }
    348             }
    349         }
    350     }
    351 
    352     /**
    353      * Initialize logging fields.
    354      */
    355     private void prepareMetricsLogging(int important, int others, long spaceBegin, long spaceBulk) {
    356         availableSpaceBefore = spaceBegin;
    357         availableSpaceAfterBulkDelete = spaceBulk;
    358         availableSpaceAfterDexopt = 0;
    359 
    360         importantPackageCount = important;
    361         otherPackageCount = others;
    362 
    363         dexoptCommandCountTotal = mDexoptCommands.size();
    364         dexoptCommandCountExecuted = 0;
    365 
    366         otaDexoptTimeStart = System.nanoTime();
    367     }
    368 
    369     private static int inMegabytes(long value) {
    370         long in_mega_bytes = value / (1024 * 1024);
    371         if (in_mega_bytes > Integer.MAX_VALUE) {
    372             Log.w(TAG, "Recording " + in_mega_bytes + "MB of free space, overflowing range");
    373             return Integer.MAX_VALUE;
    374         }
    375         return (int)in_mega_bytes;
    376     }
    377 
    378     private void performMetricsLogging() {
    379         long finalTime = System.nanoTime();
    380 
    381         MetricsLogger.histogram(mContext, "ota_dexopt_available_space_before_mb",
    382                 inMegabytes(availableSpaceBefore));
    383         MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_bulk_delete_mb",
    384                 inMegabytes(availableSpaceAfterBulkDelete));
    385         MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_dexopt_mb",
    386                 inMegabytes(availableSpaceAfterDexopt));
    387 
    388         MetricsLogger.histogram(mContext, "ota_dexopt_num_important_packages",
    389                 importantPackageCount);
    390         MetricsLogger.histogram(mContext, "ota_dexopt_num_other_packages", otherPackageCount);
    391 
    392         MetricsLogger.histogram(mContext, "ota_dexopt_num_commands", dexoptCommandCountTotal);
    393         MetricsLogger.histogram(mContext, "ota_dexopt_num_commands_executed",
    394                 dexoptCommandCountExecuted);
    395 
    396         final int elapsedTimeSeconds =
    397                 (int) TimeUnit.NANOSECONDS.toSeconds(finalTime - otaDexoptTimeStart);
    398         MetricsLogger.histogram(mContext, "ota_dexopt_time_s", elapsedTimeSeconds);
    399     }
    400 
    401     private static class OTADexoptPackageDexOptimizer extends
    402             PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
    403 
    404         public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
    405                 Context context) {
    406             super(installer, installLock, context, "*otadexopt*");
    407         }
    408 
    409         @Override
    410         protected int adjustDexoptFlags(int dexoptFlags) {
    411             // Add the OTA flag.
    412             return dexoptFlags | DEXOPT_OTA;
    413         }
    414 
    415     }
    416 
    417     private static class RecordingInstallerConnection extends InstallerConnection {
    418         public List<String> commands = new ArrayList<String>(1);
    419 
    420         @Override
    421         public void setWarnIfHeld(Object warnIfHeld) {
    422             throw new IllegalStateException("Should not reach here");
    423         }
    424 
    425         @Override
    426         public synchronized String transact(String cmd) {
    427             commands.add(cmd);
    428             return "0";
    429         }
    430 
    431         @Override
    432         public boolean mergeProfiles(int uid, String pkgName) throws InstallerException {
    433             throw new IllegalStateException("Should not reach here");
    434         }
    435 
    436         @Override
    437         public boolean dumpProfiles(String gid, String packageName, String codePaths)
    438                 throws InstallerException {
    439             throw new IllegalStateException("Should not reach here");
    440         }
    441 
    442         @Override
    443         public void disconnect() {
    444             throw new IllegalStateException("Should not reach here");
    445         }
    446 
    447         @Override
    448         public void waitForConnection() {
    449             throw new IllegalStateException("Should not reach here");
    450         }
    451     }
    452 }
    453