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