From 9242fadf3a69146a37f458b3f1f97fa91252ca55 Mon Sep 17 00:00:00 2001 From: Ben Murdoch Date: Wed, 13 Mar 2019 16:03:23 +0000 Subject: [PATCH] Add app compaction throttling. Implement configurable flags that 1. Filters full app compaction by the anon RSS use of a process, i.e. do not compact processes that are using an amount of memory lower than the flag value. By default the flag is 12000kB, which from experimental data indicates we get 85% of the compaction gains with 50% of the number of compactions. The filter can be disabled entirely by setting a value of 0. The default will be later tuned with further experiments. 2. Filters full compaction by the delta in RSS usage since the effect of compaction last time the process was (fully) compacted. This is to try and not compact if we think that there are diminishing returns. The default is 8000kB, which from experimental data suggests we get 90% of the compaction gains with 50% of the compaction events. The filter can be disabled by setting a value of 0. The default will be later tuned with further experiments. 3. Filters all types of compaction for processes in certain procstate, by default PROCESS_STATE_BROADCAST_RECEIVER. The filter can be disabled by setting the empty string and the default will be tuned with further experiments. BUG: 128427978 Test: atest FrameworksMockingServicesTests:AppCompactorTest Change-Id: I71fffc52a232cd5608742d9f36965a4de6494b56 --- .../server/am/ActivityManagerDebugConfig.java | 1 + .../com/android/server/am/AppCompactor.java | 273 +++++++++++++++++- .../android/server/am/AppCompactorTest.java | 233 ++++++++++++++- 3 files changed, 492 insertions(+), 15 deletions(-) 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) {