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