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