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