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