OtaDexoptService.java revision dab38e000436bf8234955b0333eaecf389e65b6f
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 // The synthetic library dependencies denoting "no checks." 54 private final static String[] NO_LIBRARIES = new String[] { "&" }; 55 56 // The amount of "available" (free - low threshold) space necessary at the start of an OTA to 57 // not bulk-delete unused apps' odex files. 58 private final static long BULK_DELETE_THRESHOLD = 1024 * 1024 * 1024; // 1GB. 59 60 private final Context mContext; 61 private final PackageManagerService mPackageManagerService; 62 63 // TODO: Evaluate the need for WeakReferences here. 64 65 /** 66 * The list of dexopt invocations for all work. 67 */ 68 private List<String> mDexoptCommands; 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 (mDexoptCommands != null) { 98 throw new IllegalStateException("already called prepare()"); 99 } 100 final List<PackageParser.Package> important; 101 final List<PackageParser.Package> others; 102 synchronized (mPackageManagerService.mPackages) { 103 // Important: the packages we need to run with ab-ota compiler-reason. 104 important = PackageManagerServiceUtils.getPackagesForDexopt( 105 mPackageManagerService.mPackages.values(), mPackageManagerService); 106 // Others: we should optimize this with the (first-)boot compiler-reason. 107 others = new ArrayList<>(mPackageManagerService.mPackages.values()); 108 others.removeAll(important); 109 110 // Pre-size the array list by over-allocating by a factor of 1.5. 111 mDexoptCommands = new ArrayList<>(3 * mPackageManagerService.mPackages.size() / 2); 112 } 113 114 for (PackageParser.Package p : important) { 115 // Make sure that core apps are optimized according to their own "reason". 116 // If the core apps are not preopted in the B OTA, and REASON_AB_OTA is not speed 117 // (by default is speed-profile) they will be interepreted/JITed. This in itself is 118 // not a problem as we will end up doing profile guided compilation. However, some 119 // core apps may be loaded by system server which doesn't JIT and we need to make 120 // sure we don't interpret-only 121 int compilationReason = p.coreApp 122 ? PackageManagerService.REASON_CORE_APP 123 : PackageManagerService.REASON_AB_OTA; 124 mDexoptCommands.addAll(generatePackageDexopts(p, compilationReason)); 125 } 126 for (PackageParser.Package p : others) { 127 // We assume here that there are no core apps left. 128 if (p.coreApp) { 129 throw new IllegalStateException("Found a core app that's not important"); 130 } 131 mDexoptCommands.addAll( 132 generatePackageDexopts(p, PackageManagerService.REASON_FIRST_BOOT)); 133 } 134 completeSize = mDexoptCommands.size(); 135 136 if (getAvailableSpace() < BULK_DELETE_THRESHOLD) { 137 Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: " 138 + PackageManagerServiceUtils.packagesToString(others)); 139 for (PackageParser.Package pkg : others) { 140 deleteOatArtifactsOfPackage(pkg); 141 } 142 } 143 } 144 145 @Override 146 public synchronized void cleanup() throws RemoteException { 147 if (DEBUG_DEXOPT) { 148 Log.i(TAG, "Cleaning up OTA Dexopt state."); 149 } 150 mDexoptCommands = null; 151 } 152 153 @Override 154 public synchronized boolean isDone() throws RemoteException { 155 if (mDexoptCommands == null) { 156 throw new IllegalStateException("done() called before prepare()"); 157 } 158 159 return mDexoptCommands.isEmpty(); 160 } 161 162 @Override 163 public synchronized float getProgress() throws RemoteException { 164 // Approximate the progress by the amount of already completed commands. 165 if (completeSize == 0) { 166 return 1f; 167 } 168 int commandsLeft = mDexoptCommands.size(); 169 return (completeSize - commandsLeft) / ((float)completeSize); 170 } 171 172 @Override 173 public synchronized String nextDexoptCommand() throws RemoteException { 174 if (mDexoptCommands == null) { 175 throw new IllegalStateException("dexoptNextPackage() called before prepare()"); 176 } 177 178 if (mDexoptCommands.isEmpty()) { 179 return "(all done)"; 180 } 181 182 String next = mDexoptCommands.remove(0); 183 184 if (getAvailableSpace() > 0) { 185 return next; 186 } else { 187 if (DEBUG_DEXOPT) { 188 Log.w(TAG, "Not enough space for OTA dexopt, stopping with " 189 + (mDexoptCommands.size() + 1) + " commands left."); 190 } 191 mDexoptCommands.clear(); 192 return "(no free space)"; 193 } 194 } 195 196 private long getMainLowSpaceThreshold() { 197 File dataDir = Environment.getDataDirectory(); 198 @SuppressWarnings("deprecation") 199 long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir); 200 if (lowThreshold == 0) { 201 throw new IllegalStateException("Invalid low memory threshold"); 202 } 203 return lowThreshold; 204 } 205 206 /** 207 * Returns the difference of free space to the low-storage-space threshold. Positive values 208 * indicate free bytes. 209 */ 210 private long getAvailableSpace() { 211 // TODO: If apps are not installed in the internal /data partition, we should compare 212 // against that storage's free capacity. 213 long lowThreshold = getMainLowSpaceThreshold(); 214 215 File dataDir = Environment.getDataDirectory(); 216 long usableSpace = dataDir.getUsableSpace(); 217 218 return usableSpace - lowThreshold; 219 } 220 221 private static String getOatDir(PackageParser.Package pkg) { 222 if (!pkg.canHaveOatDir()) { 223 return null; 224 } 225 File codePath = new File(pkg.codePath); 226 if (codePath.isDirectory()) { 227 return PackageDexOptimizer.getOatDir(codePath).getAbsolutePath(); 228 } 229 return null; 230 } 231 232 private void deleteOatArtifactsOfPackage(PackageParser.Package pkg) { 233 String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo); 234 for (String codePath : pkg.getAllCodePaths()) { 235 for (String isa : instructionSets) { 236 try { 237 mPackageManagerService.mInstaller.deleteOdex(codePath, isa, getOatDir(pkg)); 238 } catch (InstallerException e) { 239 Log.e(TAG, "Failed deleting oat files for " + codePath, e); 240 } 241 } 242 } 243 } 244 245 /** 246 * Generate all dexopt commands for the given package. 247 */ 248 private synchronized List<String> generatePackageDexopts(PackageParser.Package pkg, 249 int compilationReason) { 250 // Use our custom connection that just collects the commands. 251 RecordingInstallerConnection collectingConnection = new RecordingInstallerConnection(); 252 Installer collectingInstaller = new Installer(mContext, collectingConnection); 253 254 // Use the package manager install and install lock here for the OTA dex optimizer. 255 PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer( 256 collectingInstaller, mPackageManagerService.mInstallLock, mContext); 257 258 String[] libraryDependencies = pkg.usesLibraryFiles; 259 if (pkg.isSystemApp()) { 260 // For system apps, we want to avoid classpaths checks. 261 libraryDependencies = NO_LIBRARIES; 262 } 263 264 optimizer.performDexOpt(pkg, libraryDependencies, 265 null /* ISAs */, false /* checkProfiles */, 266 getCompilerFilterForReason(compilationReason), 267 null /* CompilerStats.PackageStats */); 268 269 return collectingConnection.commands; 270 } 271 272 @Override 273 public synchronized void dexoptNextPackage() throws RemoteException { 274 throw new UnsupportedOperationException(); 275 } 276 277 private void moveAbArtifacts(Installer installer) { 278 if (mDexoptCommands != null) { 279 throw new IllegalStateException("Should not be ota-dexopting when trying to move."); 280 } 281 282 // Look into all packages. 283 Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages(); 284 for (PackageParser.Package pkg : pkgs) { 285 if (pkg == null) { 286 continue; 287 } 288 289 // Does the package have code? If not, there won't be any artifacts. 290 if (!PackageDexOptimizer.canOptimizePackage(pkg)) { 291 continue; 292 } 293 if (pkg.codePath == null) { 294 Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath"); 295 continue; 296 } 297 298 // If the path is in /system or /vendor, ignore. It will have been ota-dexopted into 299 // /data/ota and moved into the dalvik-cache already. 300 if (pkg.codePath.startsWith("/system") || pkg.codePath.startsWith("/vendor")) { 301 continue; 302 } 303 304 final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo); 305 final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly(); 306 final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); 307 for (String dexCodeInstructionSet : dexCodeInstructionSets) { 308 for (String path : paths) { 309 String oatDir = PackageDexOptimizer.getOatDir(new File(pkg.codePath)). 310 getAbsolutePath(); 311 312 // TODO: Check first whether there is an artifact, to save the roundtrip time. 313 314 try { 315 installer.moveAb(path, dexCodeInstructionSet, oatDir); 316 } catch (InstallerException e) { 317 } 318 } 319 } 320 } 321 } 322 323 private static class OTADexoptPackageDexOptimizer extends 324 PackageDexOptimizer.ForcedUpdatePackageDexOptimizer { 325 326 public OTADexoptPackageDexOptimizer(Installer installer, Object installLock, 327 Context context) { 328 super(installer, installLock, context, "*otadexopt*"); 329 } 330 331 @Override 332 protected int adjustDexoptFlags(int dexoptFlags) { 333 // Add the OTA flag. 334 return dexoptFlags | DEXOPT_OTA; 335 } 336 337 } 338 339 private static class RecordingInstallerConnection extends InstallerConnection { 340 public List<String> commands = new ArrayList<String>(1); 341 342 @Override 343 public void setWarnIfHeld(Object warnIfHeld) { 344 throw new IllegalStateException("Should not reach here"); 345 } 346 347 @Override 348 public synchronized String transact(String cmd) { 349 commands.add(cmd); 350 return "0"; 351 } 352 353 @Override 354 public boolean mergeProfiles(int uid, String pkgName) throws InstallerException { 355 throw new IllegalStateException("Should not reach here"); 356 } 357 358 @Override 359 public boolean dumpProfiles(String gid, String packageName, String codePaths) 360 throws InstallerException { 361 throw new IllegalStateException("Should not reach here"); 362 } 363 364 @Override 365 public void disconnect() { 366 throw new IllegalStateException("Should not reach here"); 367 } 368 369 @Override 370 public void waitForConnection() { 371 throw new IllegalStateException("Should not reach here"); 372 } 373 } 374} 375