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