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