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