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