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