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 17 package com.android.server.pm; 18 19 import static com.android.server.pm.Installer.DEXOPT_OTA; 20 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; 21 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; 22 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason; 23 24 import android.content.Context; 25 import android.content.pm.IOtaDexopt; 26 import android.content.pm.PackageParser; 27 import android.os.Environment; 28 import android.os.RemoteException; 29 import android.os.ResultReceiver; 30 import android.os.ServiceManager; 31 import android.os.storage.StorageManager; 32 import android.util.Log; 33 import android.util.Slog; 34 import com.android.internal.logging.MetricsLogger; 35 import com.android.internal.os.InstallerConnection; 36 import com.android.internal.os.InstallerConnection.InstallerException; 37 38 import java.io.File; 39 import java.io.FileDescriptor; 40 import java.util.ArrayList; 41 import java.util.Collection; 42 import java.util.List; 43 import java.util.concurrent.TimeUnit; 44 45 /** 46 * A service for A/B OTA dexopting. 47 * 48 * {@hide} 49 */ 50 public 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 availableSpaceAfterDexopt = getAvailableSpace(); 176 177 performMetricsLogging(); 178 } 179 180 @Override 181 public synchronized boolean isDone() throws RemoteException { 182 if (mDexoptCommands == null) { 183 throw new IllegalStateException("done() called before prepare()"); 184 } 185 186 return mDexoptCommands.isEmpty(); 187 } 188 189 @Override 190 public synchronized float getProgress() throws RemoteException { 191 // Approximate the progress by the amount of already completed commands. 192 if (completeSize == 0) { 193 return 1f; 194 } 195 int commandsLeft = mDexoptCommands.size(); 196 return (completeSize - commandsLeft) / ((float)completeSize); 197 } 198 199 @Override 200 public synchronized String nextDexoptCommand() throws RemoteException { 201 if (mDexoptCommands == null) { 202 throw new IllegalStateException("dexoptNextPackage() called before prepare()"); 203 } 204 205 if (mDexoptCommands.isEmpty()) { 206 return "(all done)"; 207 } 208 209 String next = mDexoptCommands.remove(0); 210 211 if (getAvailableSpace() > 0) { 212 dexoptCommandCountExecuted++; 213 214 return next; 215 } else { 216 if (DEBUG_DEXOPT) { 217 Log.w(TAG, "Not enough space for OTA dexopt, stopping with " 218 + (mDexoptCommands.size() + 1) + " commands left."); 219 } 220 mDexoptCommands.clear(); 221 return "(no free space)"; 222 } 223 } 224 225 private long getMainLowSpaceThreshold() { 226 File dataDir = Environment.getDataDirectory(); 227 @SuppressWarnings("deprecation") 228 long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir); 229 if (lowThreshold == 0) { 230 throw new IllegalStateException("Invalid low memory threshold"); 231 } 232 return lowThreshold; 233 } 234 235 /** 236 * Returns the difference of free space to the low-storage-space threshold. Positive values 237 * indicate free bytes. 238 */ 239 private long getAvailableSpace() { 240 // TODO: If apps are not installed in the internal /data partition, we should compare 241 // against that storage's free capacity. 242 long lowThreshold = getMainLowSpaceThreshold(); 243 244 File dataDir = Environment.getDataDirectory(); 245 long usableSpace = dataDir.getUsableSpace(); 246 247 return usableSpace - lowThreshold; 248 } 249 250 private static String getOatDir(PackageParser.Package pkg) { 251 if (!pkg.canHaveOatDir()) { 252 return null; 253 } 254 File codePath = new File(pkg.codePath); 255 if (codePath.isDirectory()) { 256 return PackageDexOptimizer.getOatDir(codePath).getAbsolutePath(); 257 } 258 return null; 259 } 260 261 private void deleteOatArtifactsOfPackage(PackageParser.Package pkg) { 262 String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo); 263 for (String codePath : pkg.getAllCodePaths()) { 264 for (String isa : instructionSets) { 265 try { 266 mPackageManagerService.mInstaller.deleteOdex(codePath, isa, getOatDir(pkg)); 267 } catch (InstallerException e) { 268 Log.e(TAG, "Failed deleting oat files for " + codePath, e); 269 } 270 } 271 } 272 } 273 274 /** 275 * Generate all dexopt commands for the given package. 276 */ 277 private synchronized List<String> generatePackageDexopts(PackageParser.Package pkg, 278 int compilationReason) { 279 // Use our custom connection that just collects the commands. 280 RecordingInstallerConnection collectingConnection = new RecordingInstallerConnection(); 281 Installer collectingInstaller = new Installer(mContext, collectingConnection); 282 283 // Use the package manager install and install lock here for the OTA dex optimizer. 284 PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer( 285 collectingInstaller, mPackageManagerService.mInstallLock, mContext); 286 287 String[] libraryDependencies = pkg.usesLibraryFiles; 288 if (pkg.isSystemApp()) { 289 // For system apps, we want to avoid classpaths checks. 290 libraryDependencies = NO_LIBRARIES; 291 } 292 293 optimizer.performDexOpt(pkg, libraryDependencies, 294 null /* ISAs */, false /* checkProfiles */, 295 getCompilerFilterForReason(compilationReason), 296 null /* CompilerStats.PackageStats */); 297 298 return collectingConnection.commands; 299 } 300 301 @Override 302 public synchronized void dexoptNextPackage() throws RemoteException { 303 throw new UnsupportedOperationException(); 304 } 305 306 private void moveAbArtifacts(Installer installer) { 307 if (mDexoptCommands != null) { 308 throw new IllegalStateException("Should not be ota-dexopting when trying to move."); 309 } 310 311 // Look into all packages. 312 Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages(); 313 for (PackageParser.Package pkg : pkgs) { 314 if (pkg == null) { 315 continue; 316 } 317 318 // Does the package have code? If not, there won't be any artifacts. 319 if (!PackageDexOptimizer.canOptimizePackage(pkg)) { 320 continue; 321 } 322 if (pkg.codePath == null) { 323 Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath"); 324 continue; 325 } 326 327 // If the path is in /system or /vendor, ignore. It will have been ota-dexopted into 328 // /data/ota and moved into the dalvik-cache already. 329 if (pkg.codePath.startsWith("/system") || pkg.codePath.startsWith("/vendor")) { 330 continue; 331 } 332 333 final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo); 334 final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly(); 335 final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); 336 for (String dexCodeInstructionSet : dexCodeInstructionSets) { 337 for (String path : paths) { 338 String oatDir = PackageDexOptimizer.getOatDir(new File(pkg.codePath)). 339 getAbsolutePath(); 340 341 // TODO: Check first whether there is an artifact, to save the roundtrip time. 342 343 try { 344 installer.moveAb(path, dexCodeInstructionSet, oatDir); 345 } catch (InstallerException e) { 346 } 347 } 348 } 349 } 350 } 351 352 /** 353 * Initialize logging fields. 354 */ 355 private void prepareMetricsLogging(int important, int others, long spaceBegin, long spaceBulk) { 356 availableSpaceBefore = spaceBegin; 357 availableSpaceAfterBulkDelete = spaceBulk; 358 availableSpaceAfterDexopt = 0; 359 360 importantPackageCount = important; 361 otherPackageCount = others; 362 363 dexoptCommandCountTotal = mDexoptCommands.size(); 364 dexoptCommandCountExecuted = 0; 365 366 otaDexoptTimeStart = System.nanoTime(); 367 } 368 369 private static int inMegabytes(long value) { 370 long in_mega_bytes = value / (1024 * 1024); 371 if (in_mega_bytes > Integer.MAX_VALUE) { 372 Log.w(TAG, "Recording " + in_mega_bytes + "MB of free space, overflowing range"); 373 return Integer.MAX_VALUE; 374 } 375 return (int)in_mega_bytes; 376 } 377 378 private void performMetricsLogging() { 379 long finalTime = System.nanoTime(); 380 381 MetricsLogger.histogram(mContext, "ota_dexopt_available_space_before_mb", 382 inMegabytes(availableSpaceBefore)); 383 MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_bulk_delete_mb", 384 inMegabytes(availableSpaceAfterBulkDelete)); 385 MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_dexopt_mb", 386 inMegabytes(availableSpaceAfterDexopt)); 387 388 MetricsLogger.histogram(mContext, "ota_dexopt_num_important_packages", 389 importantPackageCount); 390 MetricsLogger.histogram(mContext, "ota_dexopt_num_other_packages", otherPackageCount); 391 392 MetricsLogger.histogram(mContext, "ota_dexopt_num_commands", dexoptCommandCountTotal); 393 MetricsLogger.histogram(mContext, "ota_dexopt_num_commands_executed", 394 dexoptCommandCountExecuted); 395 396 final int elapsedTimeSeconds = 397 (int) TimeUnit.NANOSECONDS.toSeconds(finalTime - otaDexoptTimeStart); 398 MetricsLogger.histogram(mContext, "ota_dexopt_time_s", elapsedTimeSeconds); 399 } 400 401 private static class OTADexoptPackageDexOptimizer extends 402 PackageDexOptimizer.ForcedUpdatePackageDexOptimizer { 403 404 public OTADexoptPackageDexOptimizer(Installer installer, Object installLock, 405 Context context) { 406 super(installer, installLock, context, "*otadexopt*"); 407 } 408 409 @Override 410 protected int adjustDexoptFlags(int dexoptFlags) { 411 // Add the OTA flag. 412 return dexoptFlags | DEXOPT_OTA; 413 } 414 415 } 416 417 private static class RecordingInstallerConnection extends InstallerConnection { 418 public List<String> commands = new ArrayList<String>(1); 419 420 @Override 421 public void setWarnIfHeld(Object warnIfHeld) { 422 throw new IllegalStateException("Should not reach here"); 423 } 424 425 @Override 426 public synchronized String transact(String cmd) { 427 commands.add(cmd); 428 return "0"; 429 } 430 431 @Override 432 public boolean mergeProfiles(int uid, String pkgName) throws InstallerException { 433 throw new IllegalStateException("Should not reach here"); 434 } 435 436 @Override 437 public boolean dumpProfiles(String gid, String packageName, String codePaths) 438 throws InstallerException { 439 throw new IllegalStateException("Should not reach here"); 440 } 441 442 @Override 443 public void disconnect() { 444 throw new IllegalStateException("Should not reach here"); 445 } 446 447 @Override 448 public void waitForConnection() { 449 throw new IllegalStateException("Should not reach here"); 450 } 451 } 452 } 453