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