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