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