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