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.InstructionSets.getAppDexInstructionSets;
     20 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
     21 
     22 import android.annotation.Nullable;
     23 import android.content.Context;
     24 import android.content.pm.IOtaDexopt;
     25 import android.content.pm.PackageParser;
     26 import android.os.Environment;
     27 import android.os.RemoteException;
     28 import android.os.ResultReceiver;
     29 import android.os.ServiceManager;
     30 import android.os.ShellCallback;
     31 import android.os.storage.StorageManager;
     32 import android.util.Log;
     33 import android.util.Slog;
     34 
     35 import com.android.internal.logging.MetricsLogger;
     36 import com.android.server.pm.Installer.InstallerException;
     37 import com.android.server.pm.dex.DexoptOptions;
     38 
     39 import java.io.File;
     40 import java.io.FileDescriptor;
     41 import java.util.ArrayList;
     42 import java.util.Collection;
     43 import java.util.List;
     44 import java.util.concurrent.TimeUnit;
     45 
     46 /**
     47  * A service for A/B OTA dexopting.
     48  *
     49  * {@hide}
     50  */
     51 public class OtaDexoptService extends IOtaDexopt.Stub {
     52     private final static String TAG = "OTADexopt";
     53     private final static boolean DEBUG_DEXOPT = true;
     54 
     55     // The synthetic library dependencies denoting "no checks."
     56     private final static String[] NO_LIBRARIES =
     57             new String[] { PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK };
     58 
     59     // The amount of "available" (free - low threshold) space necessary at the start of an OTA to
     60     // not bulk-delete unused apps' odex files.
     61     private final static long BULK_DELETE_THRESHOLD = 1024 * 1024 * 1024;  // 1GB.
     62 
     63     private final Context mContext;
     64     private final PackageManagerService mPackageManagerService;
     65 
     66     // TODO: Evaluate the need for WeakReferences here.
     67 
     68     /**
     69      * The list of dexopt invocations for all work.
     70      */
     71     private List<String> mDexoptCommands;
     72 
     73     private int completeSize;
     74 
     75     // MetricsLogger properties.
     76 
     77     // Space before and after.
     78     private long availableSpaceBefore;
     79     private long availableSpaceAfterBulkDelete;
     80     private long availableSpaceAfterDexopt;
     81 
     82     // Packages.
     83     private int importantPackageCount;
     84     private int otherPackageCount;
     85 
     86     // Number of dexopt commands. This may be different from the count of packages.
     87     private int dexoptCommandCountTotal;
     88     private int dexoptCommandCountExecuted;
     89 
     90     // For spent time.
     91     private long otaDexoptTimeStart;
     92 
     93 
     94     public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
     95         this.mContext = context;
     96         this.mPackageManagerService = packageManagerService;
     97     }
     98 
     99     public static OtaDexoptService main(Context context,
    100             PackageManagerService packageManagerService) {
    101         OtaDexoptService ota = new OtaDexoptService(context, packageManagerService);
    102         ServiceManager.addService("otadexopt", ota);
    103 
    104         // Now it's time to check whether we need to move any A/B artifacts.
    105         ota.moveAbArtifacts(packageManagerService.mInstaller);
    106 
    107         return ota;
    108     }
    109 
    110     @Override
    111     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
    112             String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
    113         (new OtaDexoptShellCommand(this)).exec(
    114                 this, in, out, err, args, callback, resultReceiver);
    115     }
    116 
    117     @Override
    118     public synchronized void prepare() throws RemoteException {
    119         if (mDexoptCommands != null) {
    120             throw new IllegalStateException("already called prepare()");
    121         }
    122         final List<PackageParser.Package> important;
    123         final List<PackageParser.Package> others;
    124         synchronized (mPackageManagerService.mPackages) {
    125             // Important: the packages we need to run with ab-ota compiler-reason.
    126             important = PackageManagerServiceUtils.getPackagesForDexopt(
    127                     mPackageManagerService.mPackages.values(), mPackageManagerService);
    128             // Others: we should optimize this with the (first-)boot compiler-reason.
    129             others = new ArrayList<>(mPackageManagerService.mPackages.values());
    130             others.removeAll(important);
    131 
    132             // Pre-size the array list by over-allocating by a factor of 1.5.
    133             mDexoptCommands = new ArrayList<>(3 * mPackageManagerService.mPackages.size() / 2);
    134         }
    135 
    136         for (PackageParser.Package p : important) {
    137             mDexoptCommands.addAll(generatePackageDexopts(p, PackageManagerService.REASON_AB_OTA));
    138         }
    139         for (PackageParser.Package p : others) {
    140             // We assume here that there are no core apps left.
    141             if (p.coreApp) {
    142                 throw new IllegalStateException("Found a core app that's not important");
    143             }
    144             mDexoptCommands.addAll(
    145                     generatePackageDexopts(p, PackageManagerService.REASON_FIRST_BOOT));
    146         }
    147         completeSize = mDexoptCommands.size();
    148 
    149         long spaceAvailable = getAvailableSpace();
    150         if (spaceAvailable < BULK_DELETE_THRESHOLD) {
    151             Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: "
    152                     + PackageManagerServiceUtils.packagesToString(others));
    153             for (PackageParser.Package pkg : others) {
    154                 mPackageManagerService.deleteOatArtifactsOfPackage(pkg.packageName);
    155             }
    156         }
    157         long spaceAvailableNow = getAvailableSpace();
    158 
    159         prepareMetricsLogging(important.size(), others.size(), spaceAvailable, spaceAvailableNow);
    160     }
    161 
    162     @Override
    163     public synchronized void cleanup() throws RemoteException {
    164         if (DEBUG_DEXOPT) {
    165             Log.i(TAG, "Cleaning up OTA Dexopt state.");
    166         }
    167         mDexoptCommands = null;
    168         availableSpaceAfterDexopt = getAvailableSpace();
    169 
    170         performMetricsLogging();
    171     }
    172 
    173     @Override
    174     public synchronized boolean isDone() throws RemoteException {
    175         if (mDexoptCommands == null) {
    176             throw new IllegalStateException("done() called before prepare()");
    177         }
    178 
    179         return mDexoptCommands.isEmpty();
    180     }
    181 
    182     @Override
    183     public synchronized float getProgress() throws RemoteException {
    184         // Approximate the progress by the amount of already completed commands.
    185         if (completeSize == 0) {
    186             return 1f;
    187         }
    188         int commandsLeft = mDexoptCommands.size();
    189         return (completeSize - commandsLeft) / ((float)completeSize);
    190     }
    191 
    192     @Override
    193     public synchronized String nextDexoptCommand() throws RemoteException {
    194         if (mDexoptCommands == null) {
    195             throw new IllegalStateException("dexoptNextPackage() called before prepare()");
    196         }
    197 
    198         if (mDexoptCommands.isEmpty()) {
    199             return "(all done)";
    200         }
    201 
    202         String next = mDexoptCommands.remove(0);
    203 
    204         if (getAvailableSpace() > 0) {
    205             dexoptCommandCountExecuted++;
    206 
    207             Log.d(TAG, "Next command: " + next);
    208             return next;
    209         } else {
    210             if (DEBUG_DEXOPT) {
    211                 Log.w(TAG, "Not enough space for OTA dexopt, stopping with "
    212                         + (mDexoptCommands.size() + 1) + " commands left.");
    213             }
    214             mDexoptCommands.clear();
    215             return "(no free space)";
    216         }
    217     }
    218 
    219     private long getMainLowSpaceThreshold() {
    220         File dataDir = Environment.getDataDirectory();
    221         @SuppressWarnings("deprecation")
    222         long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
    223         if (lowThreshold == 0) {
    224             throw new IllegalStateException("Invalid low memory threshold");
    225         }
    226         return lowThreshold;
    227     }
    228 
    229     /**
    230      * Returns the difference of free space to the low-storage-space threshold. Positive values
    231      * indicate free bytes.
    232      */
    233     private long getAvailableSpace() {
    234         // TODO: If apps are not installed in the internal /data partition, we should compare
    235         //       against that storage's free capacity.
    236         long lowThreshold = getMainLowSpaceThreshold();
    237 
    238         File dataDir = Environment.getDataDirectory();
    239         long usableSpace = dataDir.getUsableSpace();
    240 
    241         return usableSpace - lowThreshold;
    242     }
    243 
    244     /**
    245      * Generate all dexopt commands for the given package.
    246      */
    247     private synchronized List<String> generatePackageDexopts(PackageParser.Package pkg,
    248             int compilationReason) {
    249         // Intercept and collect dexopt requests
    250         final List<String> commands = new ArrayList<String>();
    251         final Installer collectingInstaller = new Installer(mContext, true) {
    252             /**
    253              * Encode the dexopt command into a string.
    254              *
    255              * Note: If you have to change the signature of this function, increase the version
    256              *       number, and update the counterpart in
    257              *       frameworks/native/cmds/installd/otapreopt.cpp.
    258              */
    259             @Override
    260             public void dexopt(String apkPath, int uid, @Nullable String pkgName,
    261                     String instructionSet, int dexoptNeeded, @Nullable String outputPath,
    262                     int dexFlags, String compilerFilter, @Nullable String volumeUuid,
    263                     @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade,
    264                     int targetSdkVersion, @Nullable String profileName,
    265                     @Nullable String dexMetadataPath, @Nullable String dexoptCompilationReason)
    266                     throws InstallerException {
    267                 final StringBuilder builder = new StringBuilder();
    268 
    269                 // The current version.
    270                 builder.append("9 ");
    271 
    272                 builder.append("dexopt");
    273 
    274                 encodeParameter(builder, apkPath);
    275                 encodeParameter(builder, uid);
    276                 encodeParameter(builder, pkgName);
    277                 encodeParameter(builder, instructionSet);
    278                 encodeParameter(builder, dexoptNeeded);
    279                 encodeParameter(builder, outputPath);
    280                 encodeParameter(builder, dexFlags);
    281                 encodeParameter(builder, compilerFilter);
    282                 encodeParameter(builder, volumeUuid);
    283                 encodeParameter(builder, sharedLibraries);
    284                 encodeParameter(builder, seInfo);
    285                 encodeParameter(builder, downgrade);
    286                 encodeParameter(builder, targetSdkVersion);
    287                 encodeParameter(builder, profileName);
    288                 encodeParameter(builder, dexMetadataPath);
    289                 encodeParameter(builder, dexoptCompilationReason);
    290 
    291                 commands.add(builder.toString());
    292             }
    293 
    294             /**
    295              * Encode a parameter as necessary for the commands string.
    296              */
    297             private void encodeParameter(StringBuilder builder, Object arg) {
    298                 builder.append(' ');
    299 
    300                 if (arg == null) {
    301                     builder.append('!');
    302                     return;
    303                 }
    304 
    305                 String txt = String.valueOf(arg);
    306                 if (txt.indexOf('\0') != -1 || txt.indexOf(' ') != -1 || "!".equals(txt)) {
    307                     throw new IllegalArgumentException(
    308                             "Invalid argument while executing " + arg);
    309                 }
    310                 builder.append(txt);
    311             }
    312         };
    313 
    314         // Use the package manager install and install lock here for the OTA dex optimizer.
    315         PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
    316                 collectingInstaller, mPackageManagerService.mInstallLock, mContext);
    317 
    318         String[] libraryDependencies = pkg.usesLibraryFiles;
    319         if (pkg.isSystem()) {
    320             // For system apps, we want to avoid classpaths checks.
    321             libraryDependencies = NO_LIBRARIES;
    322         }
    323 
    324 
    325         optimizer.performDexOpt(pkg, libraryDependencies,
    326                 null /* ISAs */,
    327                 null /* CompilerStats.PackageStats */,
    328                 mPackageManagerService.getDexManager().getPackageUseInfoOrDefault(pkg.packageName),
    329                 new DexoptOptions(pkg.packageName, compilationReason,
    330                         DexoptOptions.DEXOPT_BOOT_COMPLETE));
    331 
    332         return commands;
    333     }
    334 
    335     @Override
    336     public synchronized void dexoptNextPackage() throws RemoteException {
    337         throw new UnsupportedOperationException();
    338     }
    339 
    340     private void moveAbArtifacts(Installer installer) {
    341         if (mDexoptCommands != null) {
    342             throw new IllegalStateException("Should not be ota-dexopting when trying to move.");
    343         }
    344 
    345         if (!mPackageManagerService.isUpgrade()) {
    346             Slog.d(TAG, "No upgrade, skipping A/B artifacts check.");
    347             return;
    348         }
    349 
    350         // Look into all packages.
    351         Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages();
    352         int packagePaths = 0;
    353         int pathsSuccessful = 0;
    354         for (PackageParser.Package pkg : pkgs) {
    355             if (pkg == null) {
    356                 continue;
    357             }
    358 
    359             // Does the package have code? If not, there won't be any artifacts.
    360             if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
    361                 continue;
    362             }
    363             if (pkg.codePath == null) {
    364                 Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath");
    365                 continue;
    366             }
    367 
    368             // If the path is in /system, /vendor or /product, ignore. It will have been
    369             // ota-dexopted into /data/ota and moved into the dalvik-cache already.
    370             if (pkg.codePath.startsWith("/system") || pkg.codePath.startsWith("/vendor")
    371                     || pkg.codePath.startsWith("/product")) {
    372                 continue;
    373             }
    374 
    375             final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
    376             final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
    377             final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
    378             for (String dexCodeInstructionSet : dexCodeInstructionSets) {
    379                 for (String path : paths) {
    380                     String oatDir = PackageDexOptimizer.getOatDir(new File(pkg.codePath)).
    381                             getAbsolutePath();
    382 
    383                     // TODO: Check first whether there is an artifact, to save the roundtrip time.
    384 
    385                     packagePaths++;
    386                     try {
    387                         installer.moveAb(path, dexCodeInstructionSet, oatDir);
    388                         pathsSuccessful++;
    389                     } catch (InstallerException e) {
    390                     }
    391                 }
    392             }
    393         }
    394         Slog.i(TAG, "Moved " + pathsSuccessful + "/" + packagePaths);
    395     }
    396 
    397     /**
    398      * Initialize logging fields.
    399      */
    400     private void prepareMetricsLogging(int important, int others, long spaceBegin, long spaceBulk) {
    401         availableSpaceBefore = spaceBegin;
    402         availableSpaceAfterBulkDelete = spaceBulk;
    403         availableSpaceAfterDexopt = 0;
    404 
    405         importantPackageCount = important;
    406         otherPackageCount = others;
    407 
    408         dexoptCommandCountTotal = mDexoptCommands.size();
    409         dexoptCommandCountExecuted = 0;
    410 
    411         otaDexoptTimeStart = System.nanoTime();
    412     }
    413 
    414     private static int inMegabytes(long value) {
    415         long in_mega_bytes = value / (1024 * 1024);
    416         if (in_mega_bytes > Integer.MAX_VALUE) {
    417             Log.w(TAG, "Recording " + in_mega_bytes + "MB of free space, overflowing range");
    418             return Integer.MAX_VALUE;
    419         }
    420         return (int)in_mega_bytes;
    421     }
    422 
    423     private void performMetricsLogging() {
    424         long finalTime = System.nanoTime();
    425 
    426         MetricsLogger.histogram(mContext, "ota_dexopt_available_space_before_mb",
    427                 inMegabytes(availableSpaceBefore));
    428         MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_bulk_delete_mb",
    429                 inMegabytes(availableSpaceAfterBulkDelete));
    430         MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_dexopt_mb",
    431                 inMegabytes(availableSpaceAfterDexopt));
    432 
    433         MetricsLogger.histogram(mContext, "ota_dexopt_num_important_packages",
    434                 importantPackageCount);
    435         MetricsLogger.histogram(mContext, "ota_dexopt_num_other_packages", otherPackageCount);
    436 
    437         MetricsLogger.histogram(mContext, "ota_dexopt_num_commands", dexoptCommandCountTotal);
    438         MetricsLogger.histogram(mContext, "ota_dexopt_num_commands_executed",
    439                 dexoptCommandCountExecuted);
    440 
    441         final int elapsedTimeSeconds =
    442                 (int) TimeUnit.NANOSECONDS.toSeconds(finalTime - otaDexoptTimeStart);
    443         MetricsLogger.histogram(mContext, "ota_dexopt_time_s", elapsedTimeSeconds);
    444     }
    445 
    446     private static class OTADexoptPackageDexOptimizer extends
    447             PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
    448         public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
    449                 Context context) {
    450             super(installer, installLock, context, "*otadexopt*");
    451         }
    452     }
    453 }
    454