OtaDexoptService.java revision 77cc8172d31fff398b81a039b3fddc8acd1c609b
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.Installer.DEXOPT_OTA;
20import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
21import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
22import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
23
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.storage.StorageManager;
32import android.util.Log;
33import android.util.Slog;
34import com.android.internal.logging.MetricsLogger;
35import com.android.internal.os.InstallerConnection;
36import com.android.internal.os.InstallerConnection.InstallerException;
37
38import java.io.File;
39import java.io.FileDescriptor;
40import java.util.ArrayList;
41import java.util.Collection;
42import java.util.List;
43import java.util.concurrent.TimeUnit;
44
45/**
46 * A service for A/B OTA dexopting.
47 *
48 * {@hide}
49 */
50public 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
176        performMetricsLogging();
177    }
178
179    @Override
180    public synchronized boolean isDone() throws RemoteException {
181        if (mDexoptCommands == null) {
182            throw new IllegalStateException("done() called before prepare()");
183        }
184
185        return mDexoptCommands.isEmpty();
186    }
187
188    @Override
189    public synchronized float getProgress() throws RemoteException {
190        // Approximate the progress by the amount of already completed commands.
191        if (completeSize == 0) {
192            return 1f;
193        }
194        int commandsLeft = mDexoptCommands.size();
195        return (completeSize - commandsLeft) / ((float)completeSize);
196    }
197
198    @Override
199    public synchronized String nextDexoptCommand() throws RemoteException {
200        if (mDexoptCommands == null) {
201            throw new IllegalStateException("dexoptNextPackage() called before prepare()");
202        }
203
204        if (mDexoptCommands.isEmpty()) {
205            return "(all done)";
206        }
207
208        String next = mDexoptCommands.remove(0);
209
210        if (getAvailableSpace() > 0) {
211            dexoptCommandCountExecuted++;
212
213            return next;
214        } else {
215            if (DEBUG_DEXOPT) {
216                Log.w(TAG, "Not enough space for OTA dexopt, stopping with "
217                        + (mDexoptCommands.size() + 1) + " commands left.");
218            }
219            mDexoptCommands.clear();
220            return "(no free space)";
221        }
222    }
223
224    private long getMainLowSpaceThreshold() {
225        File dataDir = Environment.getDataDirectory();
226        @SuppressWarnings("deprecation")
227        long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
228        if (lowThreshold == 0) {
229            throw new IllegalStateException("Invalid low memory threshold");
230        }
231        return lowThreshold;
232    }
233
234    /**
235     * Returns the difference of free space to the low-storage-space threshold. Positive values
236     * indicate free bytes.
237     */
238    private long getAvailableSpace() {
239        // TODO: If apps are not installed in the internal /data partition, we should compare
240        //       against that storage's free capacity.
241        long lowThreshold = getMainLowSpaceThreshold();
242
243        File dataDir = Environment.getDataDirectory();
244        long usableSpace = dataDir.getUsableSpace();
245
246        return usableSpace - lowThreshold;
247    }
248
249    private static String getOatDir(PackageParser.Package pkg) {
250        if (!pkg.canHaveOatDir()) {
251            return null;
252        }
253        File codePath = new File(pkg.codePath);
254        if (codePath.isDirectory()) {
255            return PackageDexOptimizer.getOatDir(codePath).getAbsolutePath();
256        }
257        return null;
258    }
259
260    private void deleteOatArtifactsOfPackage(PackageParser.Package pkg) {
261        String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
262        for (String codePath : pkg.getAllCodePaths()) {
263            for (String isa : instructionSets) {
264                try {
265                    mPackageManagerService.mInstaller.deleteOdex(codePath, isa, getOatDir(pkg));
266                } catch (InstallerException e) {
267                    Log.e(TAG, "Failed deleting oat files for " + codePath, e);
268                }
269            }
270        }
271    }
272
273    /**
274     * Generate all dexopt commands for the given package.
275     */
276    private synchronized List<String> generatePackageDexopts(PackageParser.Package pkg,
277            int compilationReason) {
278        // Use our custom connection that just collects the commands.
279        RecordingInstallerConnection collectingConnection = new RecordingInstallerConnection();
280        Installer collectingInstaller = new Installer(mContext, collectingConnection);
281
282        // Use the package manager install and install lock here for the OTA dex optimizer.
283        PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
284                collectingInstaller, mPackageManagerService.mInstallLock, mContext);
285
286        String[] libraryDependencies = pkg.usesLibraryFiles;
287        if (pkg.isSystemApp()) {
288            // For system apps, we want to avoid classpaths checks.
289            libraryDependencies = NO_LIBRARIES;
290        }
291
292        optimizer.performDexOpt(pkg, libraryDependencies,
293                null /* ISAs */, false /* checkProfiles */,
294                getCompilerFilterForReason(compilationReason),
295                null /* CompilerStats.PackageStats */);
296
297        return collectingConnection.commands;
298    }
299
300    @Override
301    public synchronized void dexoptNextPackage() throws RemoteException {
302        throw new UnsupportedOperationException();
303    }
304
305    private void moveAbArtifacts(Installer installer) {
306        if (mDexoptCommands != null) {
307            throw new IllegalStateException("Should not be ota-dexopting when trying to move.");
308        }
309
310        // Look into all packages.
311        Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages();
312        for (PackageParser.Package pkg : pkgs) {
313            if (pkg == null) {
314                continue;
315            }
316
317            // Does the package have code? If not, there won't be any artifacts.
318            if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
319                continue;
320            }
321            if (pkg.codePath == null) {
322                Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath");
323                continue;
324            }
325
326            // If the path is in /system or /vendor, ignore. It will have been ota-dexopted into
327            // /data/ota and moved into the dalvik-cache already.
328            if (pkg.codePath.startsWith("/system") || pkg.codePath.startsWith("/vendor")) {
329                continue;
330            }
331
332            final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
333            final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
334            final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
335            for (String dexCodeInstructionSet : dexCodeInstructionSets) {
336                for (String path : paths) {
337                    String oatDir = PackageDexOptimizer.getOatDir(new File(pkg.codePath)).
338                            getAbsolutePath();
339
340                    // TODO: Check first whether there is an artifact, to save the roundtrip time.
341
342                    try {
343                        installer.moveAb(path, dexCodeInstructionSet, oatDir);
344                    } catch (InstallerException e) {
345                    }
346                }
347            }
348        }
349    }
350
351    /**
352     * Initialize logging fields.
353     */
354    private void prepareMetricsLogging(int important, int others, long spaceBegin, long spaceBulk) {
355        availableSpaceBefore = spaceBegin;
356        availableSpaceAfterBulkDelete = spaceBulk;
357        availableSpaceAfterDexopt = 0;
358
359        importantPackageCount = important;
360        otherPackageCount = others;
361
362        dexoptCommandCountTotal = mDexoptCommands.size();
363        dexoptCommandCountExecuted = 0;
364
365        otaDexoptTimeStart = System.nanoTime();
366    }
367
368    private static int inMegabytes(long value) {
369        long in_mega_bytes = value / (1024 * 1024);
370        if (in_mega_bytes > Integer.MAX_VALUE) {
371            Log.w(TAG, "Recording " + in_mega_bytes + "MB of free space, overflowing range");
372            return Integer.MAX_VALUE;
373        }
374        return (int)in_mega_bytes;
375    }
376
377    private void performMetricsLogging() {
378        long finalTime = System.nanoTime();
379
380        MetricsLogger.histogram(mContext, "ota_dexopt_available_space_before_mb",
381                inMegabytes(availableSpaceBefore));
382        MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_bulk_delete_mb",
383                inMegabytes(availableSpaceAfterBulkDelete));
384        MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_dexopt_mb",
385                inMegabytes(availableSpaceAfterDexopt));
386
387        MetricsLogger.histogram(mContext, "ota_dexopt_num_important_packages",
388                importantPackageCount);
389        MetricsLogger.histogram(mContext, "ota_dexopt_num_other_packages", otherPackageCount);
390
391        MetricsLogger.histogram(mContext, "ota_dexopt_num_commands", dexoptCommandCountTotal);
392        MetricsLogger.histogram(mContext, "ota_dexopt_num_commands_executed",
393                dexoptCommandCountExecuted);
394
395        final int elapsedTimeSeconds =
396                (int) TimeUnit.NANOSECONDS.toSeconds(finalTime - otaDexoptTimeStart);
397        MetricsLogger.histogram(mContext, "ota_dexopt_time_s", elapsedTimeSeconds);
398    }
399
400    private static class OTADexoptPackageDexOptimizer extends
401            PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
402
403        public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
404                Context context) {
405            super(installer, installLock, context, "*otadexopt*");
406        }
407
408        @Override
409        protected int adjustDexoptFlags(int dexoptFlags) {
410            // Add the OTA flag.
411            return dexoptFlags | DEXOPT_OTA;
412        }
413
414    }
415
416    private static class RecordingInstallerConnection extends InstallerConnection {
417        public List<String> commands = new ArrayList<String>(1);
418
419        @Override
420        public void setWarnIfHeld(Object warnIfHeld) {
421            throw new IllegalStateException("Should not reach here");
422        }
423
424        @Override
425        public synchronized String transact(String cmd) {
426            commands.add(cmd);
427            return "0";
428        }
429
430        @Override
431        public boolean mergeProfiles(int uid, String pkgName) throws InstallerException {
432            throw new IllegalStateException("Should not reach here");
433        }
434
435        @Override
436        public boolean dumpProfiles(String gid, String packageName, String codePaths)
437                throws InstallerException {
438            throw new IllegalStateException("Should not reach here");
439        }
440
441        @Override
442        public void disconnect() {
443            throw new IllegalStateException("Should not reach here");
444        }
445
446        @Override
447        public void waitForConnection() {
448            throw new IllegalStateException("Should not reach here");
449        }
450    }
451}
452