OtaDexoptService.java revision 1713d9e97aada3dc695800c18b1025238a11629d
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;
21
22import android.annotation.Nullable;
23import android.content.Context;
24import android.content.pm.IOtaDexopt;
25import android.content.pm.PackageParser;
26import android.os.Environment;
27import android.os.RemoteException;
28import android.os.ResultReceiver;
29import android.os.ServiceManager;
30import android.os.ShellCallback;
31import android.os.storage.StorageManager;
32import android.util.Log;
33import android.util.Slog;
34
35import com.android.internal.logging.MetricsLogger;
36import com.android.server.pm.Installer.InstallerException;
37import com.android.server.pm.dex.DexoptOptions;
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 =
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)
265                    throws InstallerException {
266                final StringBuilder builder = new StringBuilder();
267
268                // The version. Right now it's 4.
269                builder.append("4 ");
270
271                builder.append("dexopt");
272
273                encodeParameter(builder, apkPath);
274                encodeParameter(builder, uid);
275                encodeParameter(builder, pkgName);
276                encodeParameter(builder, instructionSet);
277                encodeParameter(builder, dexoptNeeded);
278                encodeParameter(builder, outputPath);
279                encodeParameter(builder, dexFlags);
280                encodeParameter(builder, compilerFilter);
281                encodeParameter(builder, volumeUuid);
282                encodeParameter(builder, sharedLibraries);
283                encodeParameter(builder, seInfo);
284                encodeParameter(builder, downgrade);
285                encodeParameter(builder, targetSdkVersion);
286
287                commands.add(builder.toString());
288            }
289
290            /**
291             * Encode a parameter as necessary for the commands string.
292             */
293            private void encodeParameter(StringBuilder builder, Object arg) {
294                builder.append(' ');
295
296                if (arg == null) {
297                    builder.append('!');
298                    return;
299                }
300
301                String txt = String.valueOf(arg);
302                if (txt.indexOf('\0') != -1 || txt.indexOf(' ') != -1 || "!".equals(txt)) {
303                    throw new IllegalArgumentException(
304                            "Invalid argument while executing " + arg);
305                }
306                builder.append(txt);
307            }
308        };
309
310        // Use the package manager install and install lock here for the OTA dex optimizer.
311        PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
312                collectingInstaller, mPackageManagerService.mInstallLock, mContext);
313
314        String[] libraryDependencies = pkg.usesLibraryFiles;
315        if (pkg.isSystem()) {
316            // For system apps, we want to avoid classpaths checks.
317            libraryDependencies = NO_LIBRARIES;
318        }
319
320
321        optimizer.performDexOpt(pkg, libraryDependencies,
322                null /* ISAs */,
323                null /* CompilerStats.PackageStats */,
324                mPackageManagerService.getDexManager().getPackageUseInfoOrDefault(pkg.packageName),
325                new DexoptOptions(pkg.packageName, compilationReason,
326                        DexoptOptions.DEXOPT_BOOT_COMPLETE));
327
328        return commands;
329    }
330
331    @Override
332    public synchronized void dexoptNextPackage() throws RemoteException {
333        throw new UnsupportedOperationException();
334    }
335
336    private void moveAbArtifacts(Installer installer) {
337        if (mDexoptCommands != null) {
338            throw new IllegalStateException("Should not be ota-dexopting when trying to move.");
339        }
340
341        if (!mPackageManagerService.isUpgrade()) {
342            Slog.d(TAG, "No upgrade, skipping A/B artifacts check.");
343            return;
344        }
345
346        // Look into all packages.
347        Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages();
348        int packagePaths = 0;
349        int pathsSuccessful = 0;
350        for (PackageParser.Package pkg : pkgs) {
351            if (pkg == null) {
352                continue;
353            }
354
355            // Does the package have code? If not, there won't be any artifacts.
356            if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
357                continue;
358            }
359            if (pkg.codePath == null) {
360                Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath");
361                continue;
362            }
363
364            // If the path is in /system, /vendor or /product, ignore. It will have been
365            // ota-dexopted into /data/ota and moved into the dalvik-cache already.
366            if (pkg.codePath.startsWith("/system") || pkg.codePath.startsWith("/vendor")
367                    || pkg.codePath.startsWith("/product")) {
368                continue;
369            }
370
371            final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
372            final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
373            final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
374            for (String dexCodeInstructionSet : dexCodeInstructionSets) {
375                for (String path : paths) {
376                    String oatDir = PackageDexOptimizer.getOatDir(new File(pkg.codePath)).
377                            getAbsolutePath();
378
379                    // TODO: Check first whether there is an artifact, to save the roundtrip time.
380
381                    packagePaths++;
382                    try {
383                        installer.moveAb(path, dexCodeInstructionSet, oatDir);
384                        pathsSuccessful++;
385                    } catch (InstallerException e) {
386                    }
387                }
388            }
389        }
390        Slog.i(TAG, "Moved " + pathsSuccessful + "/" + packagePaths);
391    }
392
393    /**
394     * Initialize logging fields.
395     */
396    private void prepareMetricsLogging(int important, int others, long spaceBegin, long spaceBulk) {
397        availableSpaceBefore = spaceBegin;
398        availableSpaceAfterBulkDelete = spaceBulk;
399        availableSpaceAfterDexopt = 0;
400
401        importantPackageCount = important;
402        otherPackageCount = others;
403
404        dexoptCommandCountTotal = mDexoptCommands.size();
405        dexoptCommandCountExecuted = 0;
406
407        otaDexoptTimeStart = System.nanoTime();
408    }
409
410    private static int inMegabytes(long value) {
411        long in_mega_bytes = value / (1024 * 1024);
412        if (in_mega_bytes > Integer.MAX_VALUE) {
413            Log.w(TAG, "Recording " + in_mega_bytes + "MB of free space, overflowing range");
414            return Integer.MAX_VALUE;
415        }
416        return (int)in_mega_bytes;
417    }
418
419    private void performMetricsLogging() {
420        long finalTime = System.nanoTime();
421
422        MetricsLogger.histogram(mContext, "ota_dexopt_available_space_before_mb",
423                inMegabytes(availableSpaceBefore));
424        MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_bulk_delete_mb",
425                inMegabytes(availableSpaceAfterBulkDelete));
426        MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_dexopt_mb",
427                inMegabytes(availableSpaceAfterDexopt));
428
429        MetricsLogger.histogram(mContext, "ota_dexopt_num_important_packages",
430                importantPackageCount);
431        MetricsLogger.histogram(mContext, "ota_dexopt_num_other_packages", otherPackageCount);
432
433        MetricsLogger.histogram(mContext, "ota_dexopt_num_commands", dexoptCommandCountTotal);
434        MetricsLogger.histogram(mContext, "ota_dexopt_num_commands_executed",
435                dexoptCommandCountExecuted);
436
437        final int elapsedTimeSeconds =
438                (int) TimeUnit.NANOSECONDS.toSeconds(finalTime - otaDexoptTimeStart);
439        MetricsLogger.histogram(mContext, "ota_dexopt_time_s", elapsedTimeSeconds);
440    }
441
442    private static class OTADexoptPackageDexOptimizer extends
443            PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
444        public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
445                Context context) {
446            super(installer, installLock, context, "*otadexopt*");
447        }
448    }
449}
450