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