OtaDexoptService.java revision 37e5fdc6b4963f3533caecdd92b129f79da69dd8
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; 34 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; 43 44/** 45 * A service for A/B OTA dexopting. 46 * 47 * {@hide} 48 */ 49public class OtaDexoptService extends IOtaDexopt.Stub { 50 private final static String TAG = "OTADexopt"; 51 private final static boolean DEBUG_DEXOPT = true; 52 53 private final Context mContext; 54 private final PackageManagerService mPackageManagerService; 55 56 // TODO: Evaluate the need for WeakReferences here. 57 58 /** 59 * The list of packages to dexopt. 60 */ 61 private List<PackageParser.Package> mDexoptPackages; 62 63 /** 64 * The list of dexopt invocations for the current package (which will no longer be in 65 * mDexoptPackages). This can be more than one as a package may have multiple code paths, 66 * e.g., in the split-APK case. 67 */ 68 private List<String> mCommandsForCurrentPackage; 69 70 private int completeSize; 71 72 public OtaDexoptService(Context context, PackageManagerService packageManagerService) { 73 this.mContext = context; 74 this.mPackageManagerService = packageManagerService; 75 76 // Now it's time to check whether we need to move any A/B artifacts. 77 moveAbArtifacts(packageManagerService.mInstaller); 78 } 79 80 public static OtaDexoptService main(Context context, 81 PackageManagerService packageManagerService) { 82 OtaDexoptService ota = new OtaDexoptService(context, packageManagerService); 83 ServiceManager.addService("otadexopt", ota); 84 85 return ota; 86 } 87 88 @Override 89 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 90 String[] args, ResultReceiver resultReceiver) throws RemoteException { 91 (new OtaDexoptShellCommand(this)).exec( 92 this, in, out, err, args, resultReceiver); 93 } 94 95 @Override 96 public synchronized void prepare() throws RemoteException { 97 if (mDexoptPackages != null) { 98 throw new IllegalStateException("already called prepare()"); 99 } 100 synchronized (mPackageManagerService.mPackages) { 101 mDexoptPackages = PackageManagerServiceUtils.getPackagesForDexopt( 102 mPackageManagerService.mPackages.values(), mPackageManagerService); 103 } 104 completeSize = mDexoptPackages.size(); 105 mCommandsForCurrentPackage = null; 106 } 107 108 @Override 109 public synchronized void cleanup() throws RemoteException { 110 if (DEBUG_DEXOPT) { 111 Log.i(TAG, "Cleaning up OTA Dexopt state."); 112 } 113 mDexoptPackages = null; 114 mCommandsForCurrentPackage = null; 115 } 116 117 @Override 118 public synchronized boolean isDone() throws RemoteException { 119 if (mDexoptPackages == null) { 120 throw new IllegalStateException("done() called before prepare()"); 121 } 122 123 return mDexoptPackages.isEmpty() && (mCommandsForCurrentPackage == null); 124 } 125 126 @Override 127 public synchronized float getProgress() throws RemoteException { 128 // We approximate by number of packages here. We could track all compiles, if we 129 // generated them ahead of time. Right now we're trying to conserve memory. 130 if (completeSize == 0) { 131 return 1f; 132 } 133 int packagesLeft = mDexoptPackages.size() + (mCommandsForCurrentPackage != null ? 1 : 0); 134 return (completeSize - packagesLeft) / ((float)completeSize); 135 } 136 137 /** 138 * Return the next dexopt command for the current package. Enforces the invariant 139 */ 140 private String getNextPackageDexopt() { 141 if (mCommandsForCurrentPackage != null) { 142 String next = mCommandsForCurrentPackage.remove(0); 143 if (mCommandsForCurrentPackage.isEmpty()) { 144 mCommandsForCurrentPackage = null; 145 } 146 return next; 147 } 148 return null; 149 } 150 151 @Override 152 public synchronized String nextDexoptCommand() throws RemoteException { 153 if (mDexoptPackages == null) { 154 throw new IllegalStateException("dexoptNextPackage() called before prepare()"); 155 } 156 157 // Get the next command. 158 for (;;) { 159 // Check whether there's one for the current package. 160 String next = getNextPackageDexopt(); 161 if (next != null) { 162 return next; 163 } 164 165 // Move to the next package, if possible. 166 if (mDexoptPackages.isEmpty()) { 167 return "Nothing to do"; 168 } 169 170 PackageParser.Package nextPackage = mDexoptPackages.remove(0); 171 172 if (DEBUG_DEXOPT) { 173 Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt."); 174 } 175 176 // Generate the next mPackageDexopts state. Ignore errors, this loop is strongly 177 // monotonically increasing, anyways. 178 generatePackageDexopts(nextPackage); 179 180 // Invariant check: mPackageDexopts is null or not empty. 181 if (mCommandsForCurrentPackage != null && mCommandsForCurrentPackage.isEmpty()) { 182 cleanup(); 183 throw new IllegalStateException("mPackageDexopts empty for " + nextPackage); 184 } 185 } 186 } 187 188 /** 189 * Generate all dexopt commands for the given package and place them into mPackageDexopts. 190 * Returns true on success, false in an error situation like low disk space. 191 */ 192 private synchronized boolean generatePackageDexopts(PackageParser.Package nextPackage) { 193 // Check for low space. 194 // TODO: If apps are not installed in the internal /data partition, we should compare 195 // against that storage's free capacity. 196 File dataDir = Environment.getDataDirectory(); 197 @SuppressWarnings("deprecation") 198 long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir); 199 if (lowThreshold == 0) { 200 throw new IllegalStateException("Invalid low memory threshold"); 201 } 202 long usableSpace = dataDir.getUsableSpace(); 203 if (usableSpace < lowThreshold) { 204 Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " + 205 usableSpace); 206 return false; 207 } 208 209 // Use our custom connection that just collects the commands. 210 RecordingInstallerConnection collectingConnection = new RecordingInstallerConnection(); 211 Installer collectingInstaller = new Installer(mContext, collectingConnection); 212 213 // Use the package manager install and install lock here for the OTA dex optimizer. 214 PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer( 215 collectingInstaller, mPackageManagerService.mInstallLock, mContext); 216 // Make sure that core apps are optimized according to their own "reason". 217 // If the core apps are not preopted in the B OTA, and REASON_AB_OTA is not speed 218 // (by default is speed-profile) they will be interepreted/JITed. This in itself is not a 219 // problem as we will end up doing profile guided compilation. However, some core apps may 220 // be loaded by system server which doesn't JIT and we need to make sure we don't 221 // interpret-only 222 int compilationReason = nextPackage.coreApp 223 ? PackageManagerService.REASON_CORE_APP 224 : PackageManagerService.REASON_AB_OTA; 225 226 optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, 227 null /* ISAs */, false /* checkProfiles */, 228 getCompilerFilterForReason(compilationReason), 229 null /* CompilerStats.PackageStats */); 230 231 mCommandsForCurrentPackage = collectingConnection.commands; 232 if (mCommandsForCurrentPackage.isEmpty()) { 233 mCommandsForCurrentPackage = null; 234 } 235 236 return true; 237 } 238 239 @Override 240 public synchronized void dexoptNextPackage() throws RemoteException { 241 if (mDexoptPackages == null) { 242 throw new IllegalStateException("dexoptNextPackage() called before prepare()"); 243 } 244 if (mDexoptPackages.isEmpty()) { 245 // Tolerate repeated calls. 246 return; 247 } 248 249 PackageParser.Package nextPackage = mDexoptPackages.remove(0); 250 251 if (DEBUG_DEXOPT) { 252 Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt."); 253 } 254 255 // Check for low space. 256 // TODO: If apps are not installed in the internal /data partition, we should compare 257 // against that storage's free capacity. 258 File dataDir = Environment.getDataDirectory(); 259 @SuppressWarnings("deprecation") 260 long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir); 261 if (lowThreshold == 0) { 262 throw new IllegalStateException("Invalid low memory threshold"); 263 } 264 long usableSpace = dataDir.getUsableSpace(); 265 if (usableSpace < lowThreshold) { 266 Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " + 267 usableSpace); 268 return; 269 } 270 271 PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer( 272 mPackageManagerService.mInstaller, mPackageManagerService.mInstallLock, mContext); 273 optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, null /* ISAs */, 274 false /* checkProfiles */, 275 getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA), 276 mPackageManagerService.getOrCreateCompilerPackageStats(nextPackage)); 277 } 278 279 private void moveAbArtifacts(Installer installer) { 280 if (mDexoptPackages != null) { 281 throw new IllegalStateException("Should not be ota-dexopting when trying to move."); 282 } 283 284 // Look into all packages. 285 Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages(); 286 for (PackageParser.Package pkg : pkgs) { 287 if (pkg == null) { 288 continue; 289 } 290 291 // Does the package have code? If not, there won't be any artifacts. 292 if (!PackageDexOptimizer.canOptimizePackage(pkg)) { 293 continue; 294 } 295 if (pkg.codePath == null) { 296 Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath"); 297 continue; 298 } 299 300 // If the path is in /system or /vendor, ignore. It will have been ota-dexopted into 301 // /data/ota and moved into the dalvik-cache already. 302 if (pkg.codePath.startsWith("/system") || pkg.codePath.startsWith("/vendor")) { 303 continue; 304 } 305 306 final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo); 307 final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly(); 308 final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); 309 for (String dexCodeInstructionSet : dexCodeInstructionSets) { 310 for (String path : paths) { 311 String oatDir = PackageDexOptimizer.getOatDir(new File(pkg.codePath)). 312 getAbsolutePath(); 313 314 // TODO: Check first whether there is an artifact, to save the roundtrip time. 315 316 try { 317 installer.moveAb(path, dexCodeInstructionSet, oatDir); 318 } catch (InstallerException e) { 319 } 320 } 321 } 322 } 323 } 324 325 private static class OTADexoptPackageDexOptimizer extends 326 PackageDexOptimizer.ForcedUpdatePackageDexOptimizer { 327 328 public OTADexoptPackageDexOptimizer(Installer installer, Object installLock, 329 Context context) { 330 super(installer, installLock, context, "*otadexopt*"); 331 } 332 333 @Override 334 protected int adjustDexoptFlags(int dexoptFlags) { 335 // Add the OTA flag. 336 return dexoptFlags | DEXOPT_OTA; 337 } 338 339 } 340 341 private static class RecordingInstallerConnection extends InstallerConnection { 342 public List<String> commands = new ArrayList<String>(1); 343 344 @Override 345 public void setWarnIfHeld(Object warnIfHeld) { 346 throw new IllegalStateException("Should not reach here"); 347 } 348 349 @Override 350 public synchronized String transact(String cmd) { 351 commands.add(cmd); 352 return "0"; 353 } 354 355 @Override 356 public boolean mergeProfiles(int uid, String pkgName) throws InstallerException { 357 throw new IllegalStateException("Should not reach here"); 358 } 359 360 @Override 361 public boolean dumpProfiles(String gid, String packageName, String codePaths) 362 throws InstallerException { 363 throw new IllegalStateException("Should not reach here"); 364 } 365 366 @Override 367 public void disconnect() { 368 throw new IllegalStateException("Should not reach here"); 369 } 370 371 @Override 372 public void waitForConnection() { 373 throw new IllegalStateException("Should not reach here"); 374 } 375 } 376} 377