diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index e42666c6a6377..7ab70fad70d4b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -49,6 +49,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false; static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false; static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false; + static final boolean DEBUG_COMPACTION = DEBUG_ALL || false; static final boolean DEBUG_LRU = DEBUG_ALL || false; static final boolean DEBUG_MU = DEBUG_ALL || false; static final boolean DEBUG_NETWORK = DEBUG_ALL || false; diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java index ac1002689dc05..043dc8d000810 100644 --- a/services/core/java/com/android/server/am/AppCompactor.java +++ b/services/core/java/com/android/server/am/AppCompactor.java @@ -18,6 +18,9 @@ package com.android.server.am; import static android.os.Process.THREAD_PRIORITY_FOREGROUND; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; + import android.app.ActivityManager; import android.app.ActivityThread; import android.os.Debug; @@ -30,6 +33,7 @@ import android.provider.DeviceConfig; import android.provider.DeviceConfig.OnPropertyChangedListener; import android.text.TextUtils; import android.util.EventLog; +import android.util.Slog; import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; @@ -39,7 +43,12 @@ import com.android.server.ServiceThread; import java.io.FileOutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Random; +import java.util.Set; public final class AppCompactor { @@ -55,6 +64,12 @@ public final class AppCompactor { @VisibleForTesting static final String KEY_COMPACT_THROTTLE_6 = "compact_throttle_6"; @VisibleForTesting static final String KEY_COMPACT_STATSD_SAMPLE_RATE = "compact_statsd_sample_rate"; + @VisibleForTesting static final String KEY_COMPACT_FULL_RSS_THROTTLE_KB = + "compact_full_rss_throttle_kb"; + @VisibleForTesting static final String KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB = + "compact_full_delta_rss_throttle_kb"; + @VisibleForTesting static final String KEY_COMPACT_PROC_STATE_THROTTLE = + "compact_proc_state_throttle"; // Phenotype sends int configurations and we map them to the strings we'll use on device, // preventing a weird string value entering the kernel. @@ -79,6 +94,11 @@ public final class AppCompactor { @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_6 = 10 * 60 * 1000; // The sampling rate to push app compaction events into statsd for upload. @VisibleForTesting static final float DEFAULT_STATSD_SAMPLE_RATE = 0.1f; + @VisibleForTesting static final long DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB = 12_000L; + @VisibleForTesting static final long DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB = 8_000L; + // Format of this string should be a comma separated list of integers. + @VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE = + String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER); @VisibleForTesting interface PropertyChangedCallbackForTest { @@ -123,6 +143,12 @@ public final class AppCompactor { updateCompactionThrottles(); } else if (KEY_COMPACT_STATSD_SAMPLE_RATE.equals(name)) { updateStatsdSampleRate(); + } else if (KEY_COMPACT_FULL_RSS_THROTTLE_KB.equals(name)) { + updateFullRssThrottle(); + } else if (KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB.equals(name)) { + updateFullDeltaRssThrottle(); + } else if (KEY_COMPACT_PROC_STATE_THROTTLE.equals(name)) { + updateProcStateThrottle(); } } if (mTestCallback != null) { @@ -154,18 +180,42 @@ public final class AppCompactor { @VisibleForTesting volatile long mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6; @GuardedBy("mPhenotypeFlagLock") private volatile boolean mUseCompaction = DEFAULT_USE_COMPACTION; - private final Random mRandom = new Random(); @GuardedBy("mPhenotypeFlagLock") @VisibleForTesting volatile float mStatsdSampleRate = DEFAULT_STATSD_SAMPLE_RATE; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting volatile long mFullAnonRssThrottleKb = + DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB; + @GuardedBy("mPhenoypeFlagLock") + @VisibleForTesting volatile long mFullDeltaRssThrottleKb = + DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB; + @GuardedBy("mPhenoypeFlagLock") + @VisibleForTesting final Set mProcStateThrottle; // Handler on which compaction runs. private Handler mCompactionHandler; + // Maps process ID to last compaction statistics for processes that we've fully compacted. Used + // when evaluating throttles that we only consider for "full" compaction, so we don't store + // data for "some" compactions. + private Map mLastCompactionStats = + new LinkedHashMap() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > 100; + } + }; + + private int mSomeCompactionCount; + private int mFullCompactionCount; + private int mPersistentCompactionCount; + private int mBfgsCompactionCount; + public AppCompactor(ActivityManagerService am) { mAm = am; mCompactionThread = new ServiceThread("CompactionThread", THREAD_PRIORITY_FOREGROUND, true); + mProcStateThrottle = new HashSet<>(); } @VisibleForTesting @@ -186,10 +236,12 @@ public final class AppCompactor { updateCompactionActions(); updateCompactionThrottles(); updateStatsdSampleRate(); + updateFullRssThrottle(); + updateFullDeltaRssThrottle(); + updateProcStateThrottle(); } Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(), Process.THREAD_GROUP_SYSTEM); - } /** @@ -212,7 +264,33 @@ public final class AppCompactor { pw.println(" " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull); pw.println(" " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome); pw.println(" " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull); + pw.println(" " + KEY_COMPACT_THROTTLE_5 + "=" + mCompactThrottleBFGS); + pw.println(" " + KEY_COMPACT_THROTTLE_6 + "=" + mCompactThrottlePersistent); pw.println(" " + KEY_COMPACT_STATSD_SAMPLE_RATE + "=" + mStatsdSampleRate); + pw.println(" " + KEY_COMPACT_FULL_RSS_THROTTLE_KB + "=" + + mFullAnonRssThrottleKb); + pw.println(" " + KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + "=" + + mFullDeltaRssThrottleKb); + pw.println(" " + KEY_COMPACT_PROC_STATE_THROTTLE + "=" + + Arrays.toString(mProcStateThrottle.toArray(new Integer[0]))); + + pw.println(" " + mSomeCompactionCount + " some, " + mFullCompactionCount + + " full, " + mPersistentCompactionCount + " persistent, " + + mBfgsCompactionCount + " BFGS compactions."); + + if (mLastCompactionStats != null) { + pw.println(" Tracking last compaction stats for " + mLastCompactionStats.size() + + " processes."); + if (DEBUG_COMPACTION) { + for (Map.Entry entry + : mLastCompactionStats.entrySet()) { + int pid = entry.getKey(); + LastCompactionStats stats = entry.getValue(); + pw.println(" " + pid + ": " + + Arrays.toString(stats.getRssAfterCompaction())); + } + } + } } } @@ -277,7 +355,7 @@ public final class AppCompactor { /** * Reads the flag value from DeviceConfig to determine whether app compaction - * should be enabled, and starts/stops the compaction thread as needed. + * should be enabled, and starts the compaction thread if needed. */ @GuardedBy("mPhenotypeFlagLock") private void updateUseCompaction() { @@ -360,6 +438,58 @@ public final class AppCompactor { mStatsdSampleRate = Math.min(1.0f, Math.max(0.0f, mStatsdSampleRate)); } + @GuardedBy("mPhenotypeFlagLock") + private void updateFullRssThrottle() { + mFullAnonRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_COMPACT_FULL_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + + // Don't allow negative values. 0 means don't apply the throttle. + if (mFullAnonRssThrottleKb < 0) { + mFullAnonRssThrottleKb = DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB; + } + } + + @GuardedBy("mPhenotypeFlagLock") + private void updateFullDeltaRssThrottle() { + mFullDeltaRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); + + if (mFullDeltaRssThrottleKb < 0) { + mFullDeltaRssThrottleKb = DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB; + } + } + + @GuardedBy("mPhenotypeFlagLock") + private void updateProcStateThrottle() { + String procStateThrottleString = DeviceConfig.getString( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_COMPACT_PROC_STATE_THROTTLE, + DEFAULT_COMPACT_PROC_STATE_THROTTLE); + if (!parseProcStateThrottle(procStateThrottleString)) { + Slog.w(TAG_AM, "Unable to parse app compact proc state throttle \"" + + procStateThrottleString + "\" falling back to default."); + if (!parseProcStateThrottle(DEFAULT_COMPACT_PROC_STATE_THROTTLE)) { + Slog.wtf(TAG_AM, + "Unable to parse default app compact proc state throttle " + + DEFAULT_COMPACT_PROC_STATE_THROTTLE); + } + } + } + + private boolean parseProcStateThrottle(String procStateThrottleString) { + String[] procStates = TextUtils.split(procStateThrottleString, ","); + mProcStateThrottle.clear(); + for (String procState : procStates) { + try { + mProcStateThrottle.add(Integer.parseInt(procState)); + } catch (NumberFormatException e) { + Slog.e(TAG_AM, "Failed to parse default app compaction proc state: " + + procState); + return false; + } + } + return true; + } + @VisibleForTesting static String compactActionIntToString(int action) { switch(action) { @@ -376,6 +506,22 @@ public final class AppCompactor { } } + @VisibleForTesting static String procStateListToString(Integer... processStates) { + return Arrays.toString(processStates); + } + + private static final class LastCompactionStats { + private final long[] mRssAfterCompaction; + + LastCompactionStats(long[] rss) { + mRssAfterCompaction = rss; + } + + long[] getRssAfterCompaction() { + return mRssAfterCompaction; + } + } + private final class MemCompactionHandler extends Handler { private MemCompactionHandler() { super(mCompactionThread.getLooper()); @@ -392,24 +538,34 @@ public final class AppCompactor { final String name; int pendingAction, lastCompactAction; long lastCompactTime; + LastCompactionStats lastCompactionStats; + int lastOomAdj = msg.arg1; + int procState = msg.arg2; synchronized (mAm) { proc = mPendingCompactionProcesses.remove(0); pendingAction = proc.reqCompactAction; + pid = proc.pid; + name = proc.processName; // don't compact if the process has returned to perceptible // and this is only a cached/home/prev compaction if ((pendingAction == COMPACT_PROCESS_SOME || pendingAction == COMPACT_PROCESS_FULL) && (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ)) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, + "Skipping compaction as process " + name + " is " + + "now perceptible."); + } return; } - pid = proc.pid; - name = proc.processName; - lastCompactAction = proc.lastCompactAction; lastCompactTime = proc.lastCompactTime; + // remove rather than get so that insertion order will be updated when we + // put the post-compaction stats back into the map. + lastCompactionStats = mLastCompactionStats.remove(pid); } if (pid == 0) { @@ -431,6 +587,12 @@ public final class AppCompactor { || (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < mCompactThrottleSomeFull))) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping some compaction for " + name + + ": too soon. throttle=" + mCompactThrottleSomeSome + + "/" + mCompactThrottleSomeFull + " last=" + + (start - lastCompactTime) + "ms ago"); + } return; } } else if (pendingAction == COMPACT_PROCESS_FULL) { @@ -439,18 +601,35 @@ public final class AppCompactor { || (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < mCompactThrottleFullFull))) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping full compaction for " + name + + ": too soon. throttle=" + mCompactThrottleFullSome + + "/" + mCompactThrottleFullFull + " last=" + + (start - lastCompactTime) + "ms ago"); + } return; } } else if (pendingAction == COMPACT_PROCESS_PERSISTENT) { if (start - lastCompactTime < mCompactThrottlePersistent) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping persistent compaction for " + name + + ": too soon. throttle=" + mCompactThrottlePersistent + + " last=" + (start - lastCompactTime) + "ms ago"); + } return; } } else if (pendingAction == COMPACT_PROCESS_BFGS) { if (start - lastCompactTime < mCompactThrottleBFGS) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping bfgs compaction for " + name + + ": too soon. throttle=" + mCompactThrottleBFGS + + " last=" + (start - lastCompactTime) + "ms ago"); + } return; } } } + switch (pendingAction) { case COMPACT_PROCESS_SOME: action = mCompactActionSome; @@ -470,12 +649,77 @@ public final class AppCompactor { return; } + if (mProcStateThrottle.contains(procState)) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping full compaction for process " + name + + "; proc state is " + procState); + } + return; + } + + long[] rssBefore = Process.getRss(pid); + long anonRssBefore = rssBefore[2]; + + if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0 + && rssBefore[3] == 0) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping compaction for" + "process " + pid + + " with no memory usage. Dead?"); + } + return; + } + + if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) { + if (mFullAnonRssThrottleKb > 0L + && anonRssBefore < mFullAnonRssThrottleKb) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping full compaction for process " + + name + "; anon RSS is too small: " + anonRssBefore + + "KB."); + } + return; + } + + if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) { + long[] lastRss = lastCompactionStats.getRssAfterCompaction(); + long absDelta = Math.abs(rssBefore[1] - lastRss[1]) + + Math.abs(rssBefore[2] - lastRss[2]) + + Math.abs(rssBefore[3] - lastRss[3]); + if (absDelta <= mFullDeltaRssThrottleKb) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping full compaction for process " + + name + "; abs delta is too small: " + absDelta + + "KB."); + } + return; + } + } + } + + // Now we've passed through all the throttles and are going to compact, update + // bookkeeping. + switch (pendingAction) { + case COMPACT_PROCESS_SOME: + mSomeCompactionCount++; + break; + case COMPACT_PROCESS_FULL: + mFullCompactionCount++; + break; + case COMPACT_PROCESS_PERSISTENT: + mPersistentCompactionCount++; + break; + case COMPACT_PROCESS_BFGS: + mBfgsCompactionCount++; + break; + default: + break; + } + try { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") + ": " + name); long zramFreeKbBefore = Debug.getZramFreeKb(); - long[] rssBefore = Process.getRss(pid); FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim"); fos.write(action.getBytes()); fos.close(); @@ -487,8 +731,9 @@ public final class AppCompactor { rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], rssAfter[0] - rssBefore[0], rssAfter[1] - rssBefore[1], rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time, - lastCompactAction, lastCompactTime, msg.arg1, msg.arg2, + lastCompactAction, lastCompactTime, lastOomAdj, procState, zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore); + // Note that as above not taking mPhenoTypeFlagLock here to avoid locking // on every single compaction for a flag that will seldom change and the // impact of reading the wrong value here is low. @@ -496,17 +741,23 @@ public final class AppCompactor { StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction, rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, - lastCompactAction, lastCompactTime, msg.arg1, - ActivityManager.processStateAmToProto(msg.arg2), + lastCompactAction, lastCompactTime, lastOomAdj, + ActivityManager.processStateAmToProto(procState), zramFreeKbBefore, zramFreeKbAfter); } + synchronized (mAm) { proc.lastCompactTime = end; proc.lastCompactAction = pendingAction; } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + if (action.equals(COMPACT_ACTION_FULL) + || action.equals(COMPACT_ACTION_ANON)) { + mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter)); + } } catch (Exception e) { // nothing to do, presumably the process died + } finally { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java index b0c97d1b4f485..475901a75d0dd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; +import android.text.TextUtils; import com.android.server.appop.AppOpsService; import com.android.server.testables.TestableDeviceConfig; @@ -38,6 +39,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.io.File; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -103,6 +106,22 @@ public final class AppCompactorTest { AppCompactor.DEFAULT_COMPACT_THROTTLE_4); assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo( AppCompactor.DEFAULT_STATSD_SAMPLE_RATE); + assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); + assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); + + Set expected = new HashSet<>(); + for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) { + expected.add(Integer.parseInt(s)); + } + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); } @Test @@ -139,6 +158,14 @@ public final class AppCompactorTest { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, + Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, + Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false); // Then calling init will read and set that flag. mCompactorUnderTest.init(); @@ -157,12 +184,19 @@ public final class AppCompactorTest { AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1); assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1); assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo( AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f); assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1); assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1); + assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3); } @Test @@ -321,6 +355,10 @@ public final class AppCompactorTest { AppCompactor.DEFAULT_COMPACT_THROTTLE_3); assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); // Repeat for each of the throttle keys. mCountDown = new CountDownLatch(1); @@ -335,6 +373,10 @@ public final class AppCompactorTest { AppCompactor.DEFAULT_COMPACT_THROTTLE_3); assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); mCountDown = new CountDownLatch(1); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -348,6 +390,10 @@ public final class AppCompactorTest { AppCompactor.DEFAULT_COMPACT_THROTTLE_3); assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); mCountDown = new CountDownLatch(1); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -361,13 +407,51 @@ public final class AppCompactorTest { AppCompactor.DEFAULT_COMPACT_THROTTLE_3); assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_THROTTLE_5, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_1); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_2); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_3); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_THROTTLE_6, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_1); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_2); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_3); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); } @Test public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException { mCompactorUnderTest.init(); - // When we override mStatsdSampleRate with a reasonable values ... + // When we override mStatsdSampleRate with a reasonable value ... mCountDown = new CountDownLatch(1); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, @@ -380,11 +464,11 @@ public final class AppCompactorTest { } @Test - public void statsdSanokeRate_listensToDeviceConfigChangesBadValues() + public void statsdSampleRate_listensToDeviceConfigChangesBadValues() throws InterruptedException { mCompactorUnderTest.init(); - // When we override mStatsdSampleRate with a reasonable values ... + // When we override mStatsdSampleRate with an unreasonable value ... mCountDown = new CountDownLatch(1); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false); @@ -396,7 +480,7 @@ public final class AppCompactorTest { } @Test - public void statsdSanokeRate_listensToDeviceConfigChangesOutOfRangeValues() + public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues() throws InterruptedException { mCompactorUnderTest.init(); @@ -420,6 +504,147 @@ public final class AppCompactorTest { assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(1.0f); } + @Test + public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges() + throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override mStatsdSampleRate with a reasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, + Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1); + } + + @Test + public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues() + throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override mStatsdSampleRate with an unreasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + } + + @Test + public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges() + throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override mStatsdSampleRate with a reasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, + Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1); + } + + @Test + public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues() + throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override mStatsdSampleRate with an unreasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); + } + + @Test + public void procStateThrottle_listensToDeviceConfigChanges() + throws InterruptedException { + mCompactorUnderTest.init(); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).isEmpty(); + } + + @Test + public void procStateThrottle_listensToDeviceConfigChangesBadValues() + throws InterruptedException { + mCompactorUnderTest.init(); + + Set expected = new HashSet<>(); + for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) { + expected.add(Integer.parseInt(s)); + } + + // Not numbers + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); + + // Empty splits + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); + } + private class TestInjector extends Injector { @Override public AppOpsService getAppOpsService(File file, Handler handler) {