diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 6506df2ade75d..a79fe775e07bd 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -139,6 +139,9 @@ message Atom { BluetoothLinkLayerConnectionEvent bluetooth_link_layer_connection_event = 125; BluetoothAclConnectionStateChanged bluetooth_acl_connection_state_changed = 126; BluetoothScoConnectionStateChanged bluetooth_sco_connection_state_changed = 127; + AppDowngraded app_downgraded = 128; + AppOptimizedAfterDowngraded app_optimized_after_downgraded = 129; + LowStorageStateChanged low_storage_state_changed = 130; NfcErrorOccurred nfc_error_occurred = 134; NfcStateChanged nfc_state_changed = 135; NfcBeamOccurred nfc_beam_occurred = 136; @@ -2006,6 +2009,47 @@ message ActivityForegroundStateChanged { optional State state = 4; } +/** + * Logs when a volume entered low Storage state. + * Logged from: + * frameworks/base/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java + */ +message LowStorageStateChanged { + // Volume that ran out of storage. + optional string volume_description = 1; + + enum State { + UNKNOWN = 0; + OFF = 1; + ON = 2; + } + optional State state = 2; +} + +/** + * Logs when an app is downgraded. + * Logged from: + * frameworks/base/services/core/java/com/android/server/pm/BackgroundDexOptService.java + */ +message AppDowngraded { + optional string package_name = 1; + // Size of the package (all data) before being downgraded. + optional int64 size_in_bytes_before = 2; + // Size of the package (all data) after being downgraded. + optional int64 size_in_bytes_after = 3; + + optional bool aggressive = 4; +} + +/** + * Logs when an app is optimized after being downgraded. + * Logged from: + * frameworks/base/services/core/java/com/android/server/pm/BackgroundDexOptService.java + */ +message AppOptimizedAfterDowngraded { + optional string package_name = 1; +} + /** * Logs when an app crashes. * Logged from: diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index d6ab5f7175681..ad9ac12324378 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -27,24 +27,30 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageInfo; import android.os.BatteryManager; import android.os.Environment; import android.os.ServiceManager; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.storage.StorageManager; import android.util.ArraySet; import android.util.Log; +import android.util.StatsLog; -import com.android.server.pm.dex.DexManager; +import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.PinnerService; +import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DexoptOptions; import java.io.File; +import java.nio.file.Paths; import java.util.List; import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; /** * {@hide} @@ -52,7 +58,7 @@ import java.util.concurrent.TimeUnit; public class BackgroundDexOptService extends JobService { private static final String TAG = "BackgroundDexOptService"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int JOB_IDLE_OPTIMIZE = 800; private static final int JOB_POST_BOOT_UPDATE = 801; @@ -97,7 +103,6 @@ public class BackgroundDexOptService extends JobService { private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false); private final File mDataDir = Environment.getDataDirectory(); - private static final long mDowngradeUnusedAppsThresholdInMillis = getDowngradeUnusedAppsThresholdInMillis(); @@ -270,108 +275,147 @@ public class BackgroundDexOptService extends JobService { long lowStorageThreshold = getLowStorageThreshold(context); // Optimize primary apks. - int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true, - sFailedPackageNamesPrimary); - + int result = optimizePackages(pm, pkgs, lowStorageThreshold, + /*isForPrimaryDex=*/ true); if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { return result; } - - if (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) { + if (supportSecondaryDex()) { result = reconcileSecondaryDexFiles(pm.getDexManager()); if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { return result; } - - result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false, - sFailedPackageNamesSecondary); + result = optimizePackages(pm, pkgs, lowStorageThreshold, + /*isForPrimaryDex=*/ false); } return result; } + /** + * Get the size of the directory. It uses recursion to go over all files. + * @param f + * @return + */ + private long getDirectorySize(File f) { + long size = 0; + if (f.isDirectory()) { + for (File file: f.listFiles()) { + size += getDirectorySize(file); + } + } else { + size = f.length(); + } + return size; + } + + /** + * Get the size of a package. + * @param pkg + */ + private long getPackageSize(PackageManagerService pm, String pkg) { + PackageInfo info = pm.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM); + long size = 0; + if (info != null && info.applicationInfo != null) { + File path = Paths.get(info.applicationInfo.sourceDir).toFile(); + if (path.isFile()) { + path = path.getParentFile(); + } + size += getDirectorySize(path); + if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) { + for (String splitSourceDir : info.applicationInfo.splitSourceDirs) { + path = Paths.get(splitSourceDir).toFile(); + if (path.isFile()) { + path = path.getParentFile(); + } + size += getDirectorySize(path); + } + } + return size; + } + return 0; + } + private int optimizePackages(PackageManagerService pm, ArraySet pkgs, - long lowStorageThreshold, boolean is_for_primary_dex, - ArraySet failedPackageNames) { + long lowStorageThreshold, boolean isForPrimaryDex) { ArraySet updatedPackages = new ArraySet<>(); Set unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis); + Log.d(TAG, "Unsused Packages " + String.join(",", unusedPackages)); // Only downgrade apps when space is low on device. // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean // up disk before user hits the actual lowStorageThreshold. final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE * lowStorageThreshold; boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade); + Log.d(TAG, "Should Downgrade " + shouldDowngrade); + boolean dex_opt_performed = false; for (String pkg : pkgs) { int abort_code = abortIdleOptimizations(lowStorageThreshold); if (abort_code == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { return abort_code; } - - synchronized (failedPackageNames) { - if (failedPackageNames.contains(pkg)) { - // Skip previously failing package - continue; - } - } - - int reason; - boolean downgrade; // Downgrade unused packages. if (unusedPackages.contains(pkg) && shouldDowngrade) { - // This applies for system apps or if packages location is not a directory, i.e. - // monolithic install. - if (is_for_primary_dex && !pm.canHaveOatDir(pkg)) { - // For apps that don't have the oat directory, instead of downgrading, - // remove their compiler artifacts from dalvik cache. - pm.deleteOatArtifactsOfPackage(pkg); + dex_opt_performed = downgradePackage(pm, pkg, isForPrimaryDex); + } else { + if (abort_code == OPTIMIZE_ABORT_NO_SPACE_LEFT) { + // can't dexopt because of low space. continue; - } else { - reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE; - downgrade = true; } - } else if (abort_code != OPTIMIZE_ABORT_NO_SPACE_LEFT) { - reason = PackageManagerService.REASON_BACKGROUND_DEXOPT; - downgrade = false; - } else { - // can't dexopt because of low space. - continue; + dex_opt_performed = optimizePackage(pm, pkg, isForPrimaryDex); } - - synchronized (failedPackageNames) { - // Conservatively add package to the list of failing ones in case - // performDexOpt never returns. - failedPackageNames.add(pkg); - } - - // Optimize package if needed. Note that there can be no race between - // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. - boolean success; - int dexoptFlags = - DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES | - DexoptOptions.DEXOPT_BOOT_COMPLETE | - (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0) | - DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB; - if (is_for_primary_dex) { - int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, - dexoptFlags)); - success = result != PackageDexOptimizer.DEX_OPT_FAILED; - if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) { - updatedPackages.add(pkg); - } - } else { - success = pm.performDexOpt(new DexoptOptions(pkg, - reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX)); - } - if (success) { - // Dexopt succeeded, remove package from the list of failing ones. - synchronized (failedPackageNames) { - failedPackageNames.remove(pkg); - } + if (dex_opt_performed) { + updatedPackages.add(pkg); } } + notifyPinService(updatedPackages); return OPTIMIZE_PROCESSED; } + + /** + * Try to downgrade the package to a smaller compilation filter. + * eg. if the package is in speed-profile the package will be downgraded to verify. + * @param pm PackageManagerService + * @param pkg The package to be downgraded. + * @param isForPrimaryDex. Apps can have several dex file, primary and secondary. + * @return true if the package was downgraded. + */ + private boolean downgradePackage(PackageManagerService pm, String pkg, + boolean isForPrimaryDex) { + Log.d(TAG, "Downgrading " + pkg); + boolean dex_opt_performed = false; + int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE; + int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE + | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB + | DexoptOptions.DEXOPT_DOWNGRADE; + long package_size_before = getPackageSize(pm, pkg); + + if (isForPrimaryDex) { + // This applies for system apps or if packages location is not a directory, i.e. + // monolithic install. + if (!pm.canHaveOatDir(pkg)) { + // For apps that don't have the oat directory, instead of downgrading, + // remove their compiler artifacts from dalvik cache. + pm.deleteOatArtifactsOfPackage(pkg); + } else { + dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags); + } + } else { + dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags); + } + + if (dex_opt_performed) { + StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before, + getPackageSize(pm, pkg), /*aggressive=*/ false); + } + return dex_opt_performed; + } + + private boolean supportSecondaryDex() { + return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)); + } + private int reconcileSecondaryDexFiles(DexManager dm) { // TODO(calin): should we blacklist packages for which we fail to reconcile? for (String p : dm.getAllPackagesWithSecondaryDexFiles()) { @@ -383,6 +427,73 @@ public class BackgroundDexOptService extends JobService { return OPTIMIZE_PROCESSED; } + /** + * + * Optimize package if needed. Note that there can be no race between + * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. + * @param pm An instance of PackageManagerService + * @param pkg The package to be downgraded. + * @param isForPrimaryDex. Apps can have several dex file, primary and secondary. + * @return true if the package was downgraded. + */ + private boolean optimizePackage(PackageManagerService pm, String pkg, + boolean isForPrimaryDex) { + int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT; + int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES + | DexoptOptions.DEXOPT_BOOT_COMPLETE + | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB; + + return isForPrimaryDex + ? performDexOptPrimary(pm, pkg, reason, dexoptFlags) + : performDexOptSecondary(pm, pkg, reason, dexoptFlags); + } + + private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason, + int dexoptFlags) { + int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false, + () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags))); + return result == PackageDexOptimizer.DEX_OPT_PERFORMED; + } + + private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason, + int dexoptFlags) { + DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, + dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX); + int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true, + () -> pm.performDexOpt(dexoptOptions) + ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED + ); + return result == PackageDexOptimizer.DEX_OPT_PERFORMED; + } + + /** + * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails + * the package is added to the list of failed packages. + * Return one of following result: + * {@link PackageDexOptimizer#DEX_OPT_SKIPPED} + * {@link PackageDexOptimizer#DEX_OPT_PERFORMED} + * {@link PackageDexOptimizer#DEX_OPT_FAILED} + */ + private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex, + Supplier performDexOptWrapper) { + ArraySet sFailedPackageNames = + isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary; + synchronized (sFailedPackageNames) { + if (sFailedPackageNames.contains(pkg)) { + // Skip previously failing package + return PackageDexOptimizer.DEX_OPT_SKIPPED; + } + sFailedPackageNames.add(pkg); + } + int result = performDexOptWrapper.get(); + if (result != PackageDexOptimizer.DEX_OPT_FAILED) { + synchronized (sFailedPackageNames) { + sFailedPackageNames.remove(pkg); + } + } + return result; + } + // Evaluate whether or not idle optimizations should continue. private int abortIdleOptimizations(long lowStorageThreshold) { if (mAbortIdleOptimization.get()) { diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java index f7cc4432f9bcc..2700f9ddc203e 100644 --- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java +++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java @@ -24,7 +24,6 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.net.TrafficStats; import android.os.Binder; import android.os.Environment; import android.os.FileObserver; @@ -42,13 +41,13 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.DataUnit; import android.util.Slog; +import android.util.StatsLog; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.EventLogTags; -import com.android.server.IoThread; import com.android.server.SystemService; import com.android.server.pm.InstructionSets; import com.android.server.pm.PackageManagerService; @@ -499,9 +498,15 @@ public class DeviceStorageMonitorService extends SystemService { notification.flags |= Notification.FLAG_NO_CLEAR; mNotifManager.notifyAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE, notification, UserHandle.ALL); + StatsLog.write(StatsLog.LOW_STORAGE_STATE_CHANGED, + Objects.toString(vol.getDescription()), + StatsLog.LOW_STORAGE_STATE_CHANGED__STATE__ON); } else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) { mNotifManager.cancelAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE, UserHandle.ALL); + StatsLog.write(StatsLog.LOW_STORAGE_STATE_CHANGED, + Objects.toString(vol.getDescription()), + StatsLog.LOW_STORAGE_STATE_CHANGED__STATE__OFF); } }