From 9ded0ab7dcd7edab179ca1975db8b2e8eb921e39 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Thu, 25 Feb 2021 10:41:55 -0800 Subject: [PATCH] Reserve execution slots for FGS jobs. Explicitly reserve execution slots for FGS apps so we don't force their jobs to wait for truly background app jobs to finish first. Also simplify the work count tracking code. Bug: 181241779 Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/job Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/job Test: atest CtsJobSchedulerTestCases Change-Id: I196f6dfc0666550e9dac4c443fda27394544ea3f --- .../server/job/JobConcurrencyManager.java | 263 ++++++++---------- .../server/job/WorkCountTrackerTest.java | 73 +++-- .../server/job/WorkTypeConfigTest.java | 63 ++++- 3 files changed, 217 insertions(+), 182 deletions(-) diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index e8e2c27f1554c..0308d68d6a565 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -73,20 +73,48 @@ class JobConcurrencyManager { CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms"; private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000; - // Try to give higher priority types lower values. + /** + * Set of possible execution types that a job can have. The actual type(s) of a job are based + * on the {@link JobStatus#lastEvaluatedPriority}, which is typically evaluated right before + * execution (when we're trying to determine which jobs to run next) and won't change after the + * job has started executing. + * + * Try to give higher priority types lower values. + * + * @see #getJobWorkTypes(JobStatus) + */ + + /** Job shouldn't run or qualify as any other work type. */ static final int WORK_TYPE_NONE = 0; + /** The job is for an app in the TOP state for a currently active user. */ static final int WORK_TYPE_TOP = 1 << 0; - static final int WORK_TYPE_EJ = 1 << 1; - static final int WORK_TYPE_BG = 1 << 2; - static final int WORK_TYPE_BGUSER = 1 << 3; + /** + * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher + * state (excluding {@link ActivityManager#PROCESS_STATE_TOP} for a currently active user. + */ + static final int WORK_TYPE_FGS = 1 << 1; + /** The job is allowed to run as an expedited job for a currently active user. */ + static final int WORK_TYPE_EJ = 1 << 2; + /** + * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP}, + * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a currently active user, so + * can run as a background job. + */ + static final int WORK_TYPE_BG = 1 << 3; + /** + * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP}, + * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a completely background user, + * so can run as a background user job. + */ + static final int WORK_TYPE_BGUSER = 1 << 4; @VisibleForTesting - static final int NUM_WORK_TYPES = 4; - private static final int ALL_WORK_TYPES = - WORK_TYPE_TOP | WORK_TYPE_EJ | WORK_TYPE_BG | WORK_TYPE_BGUSER; + static final int NUM_WORK_TYPES = 5; + private static final int ALL_WORK_TYPES = (1 << NUM_WORK_TYPES) - 1; @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = { WORK_TYPE_NONE, WORK_TYPE_TOP, + WORK_TYPE_FGS, WORK_TYPE_EJ, WORK_TYPE_BG, WORK_TYPE_BGUSER @@ -95,12 +123,15 @@ class JobConcurrencyManager { public @interface WorkType { } - private static String workTypeToString(@WorkType int workType) { + @VisibleForTesting + static String workTypeToString(@WorkType int workType) { switch (workType) { case WORK_TYPE_NONE: return "NONE"; case WORK_TYPE_TOP: return "TOP"; + case WORK_TYPE_FGS: + return "FGS"; case WORK_TYPE_EJ: return "EJ"; case WORK_TYPE_BG: @@ -131,58 +162,60 @@ class JobConcurrencyManager { new WorkConfigLimitsPerMemoryTrimLevel( new WorkTypeConfig("screen_on_normal", 11, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_EJ, 3), - Pair.create(WORK_TYPE_BG, 2)), + List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4)) ), new WorkTypeConfig("screen_on_moderate", 9, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 2), - Pair.create(WORK_TYPE_BG, 2)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)) ), new WorkTypeConfig("screen_on_low", 6, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 1), - Pair.create(WORK_TYPE_BG, 1)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 1)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)) ), - new WorkTypeConfig("screen_on_critical", 5, + new WorkTypeConfig("screen_on_critical", 6, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 1)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 1)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)) ) ); private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF = new WorkConfigLimitsPerMemoryTrimLevel( - new WorkTypeConfig("screen_off_normal", 13, + new WorkTypeConfig("screen_off_normal", 15, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 3), - Pair.create(WORK_TYPE_BG, 2)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4)) ), - new WorkTypeConfig("screen_off_moderate", 13, + new WorkTypeConfig("screen_off_moderate", 15, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_EJ, 3), - Pair.create(WORK_TYPE_BG, 2)), + List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)) ), - new WorkTypeConfig("screen_off_low", 7, + new WorkTypeConfig("screen_off_low", 9, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 2), - Pair.create(WORK_TYPE_BG, 1)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)) ), - new WorkTypeConfig("screen_off_critical", 5, + new WorkTypeConfig("screen_off_critical", 6, // defaultMin - List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 1)), + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), + Pair.create(WORK_TYPE_EJ, 1)), // defaultMax List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)) ) @@ -977,10 +1010,11 @@ class JobConcurrencyManager { int getJobWorkTypes(@NonNull JobStatus js) { int classification = 0; - // TODO: create dedicated work type for FGS if (shouldRunAsFgUserJob(js)) { if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { classification |= WORK_TYPE_TOP; + } else if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_FOREGROUND_SERVICE) { + classification |= WORK_TYPE_FGS; } else { classification |= WORK_TYPE_BG; } @@ -1001,11 +1035,13 @@ class JobConcurrencyManager { private static final String KEY_PREFIX_MAX_TOTAL = CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_"; private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_"; + private static final String KEY_PREFIX_MAX_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "max_fgs_"; private static final String KEY_PREFIX_MAX_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "max_ej_"; private static final String KEY_PREFIX_MAX_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_"; private static final String KEY_PREFIX_MAX_BGUSER = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_"; private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_"; + private static final String KEY_PREFIX_MIN_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "min_fgs_"; private static final String KEY_PREFIX_MIN_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "min_ej_"; private static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_"; private static final String KEY_PREFIX_MIN_BGUSER = @@ -1053,6 +1089,10 @@ class JobConcurrencyManager { properties.getInt(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mDefaultMaxAllowedSlots.get(WORK_TYPE_TOP, mMaxTotal)))); mMaxAllowedSlots.put(WORK_TYPE_TOP, maxTop); + final int maxFgs = Math.max(1, Math.min(mMaxTotal, + properties.getInt(KEY_PREFIX_MAX_FGS + mConfigIdentifier, + mDefaultMaxAllowedSlots.get(WORK_TYPE_FGS, mMaxTotal)))); + mMaxAllowedSlots.put(WORK_TYPE_FGS, maxFgs); final int maxEj = Math.max(1, Math.min(mMaxTotal, properties.getInt(KEY_PREFIX_MAX_EJ + mConfigIdentifier, mDefaultMaxAllowedSlots.get(WORK_TYPE_EJ, mMaxTotal)))); @@ -1074,6 +1114,12 @@ class JobConcurrencyManager { mDefaultMinReservedSlots.get(WORK_TYPE_TOP)))); mMinReservedSlots.put(WORK_TYPE_TOP, minTop); remaining -= minTop; + // Ensure fgs is in the range [0, min(maxFgs, remaining)] + final int minFgs = Math.max(0, Math.min(Math.min(maxFgs, remaining), + properties.getInt(KEY_PREFIX_MIN_FGS + mConfigIdentifier, + mDefaultMinReservedSlots.get(WORK_TYPE_FGS)))); + mMinReservedSlots.put(WORK_TYPE_FGS, minFgs); + remaining -= minFgs; // Ensure ej is in the range [0, min(maxEj, remaining)] final int minEj = Math.max(0, Math.min(Math.min(maxEj, remaining), properties.getInt(KEY_PREFIX_MIN_EJ + mConfigIdentifier, @@ -1111,6 +1157,10 @@ class JobConcurrencyManager { .println(); pw.print(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_TOP)) .println(); + pw.print(KEY_PREFIX_MIN_FGS + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_FGS)) + .println(); + pw.print(KEY_PREFIX_MAX_FGS + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_FGS)) + .println(); pw.print(KEY_PREFIX_MIN_EJ + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_EJ)) .println(); pw.print(KEY_PREFIX_MAX_EJ + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_EJ)) @@ -1205,6 +1255,7 @@ class JobConcurrencyManager { private int mConfigMaxTotal; private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES); private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES); + private final SparseIntArray mRecycledReserved = new SparseIntArray(NUM_WORK_TYPES); /** * Numbers may be lower in this than in {@link #mConfigNumReservedSlots} if there aren't @@ -1220,11 +1271,14 @@ class JobConcurrencyManager { mConfigMaxTotal = workTypeConfig.getMaxTotal(); mConfigNumReservedSlots.put(WORK_TYPE_TOP, workTypeConfig.getMinReserved(WORK_TYPE_TOP)); + mConfigNumReservedSlots.put(WORK_TYPE_FGS, + workTypeConfig.getMinReserved(WORK_TYPE_FGS)); mConfigNumReservedSlots.put(WORK_TYPE_EJ, workTypeConfig.getMinReserved(WORK_TYPE_EJ)); mConfigNumReservedSlots.put(WORK_TYPE_BG, workTypeConfig.getMinReserved(WORK_TYPE_BG)); mConfigNumReservedSlots.put(WORK_TYPE_BGUSER, workTypeConfig.getMinReserved(WORK_TYPE_BGUSER)); mConfigAbsoluteMaxSlots.put(WORK_TYPE_TOP, workTypeConfig.getMax(WORK_TYPE_TOP)); + mConfigAbsoluteMaxSlots.put(WORK_TYPE_FGS, workTypeConfig.getMax(WORK_TYPE_FGS)); mConfigAbsoluteMaxSlots.put(WORK_TYPE_EJ, workTypeConfig.getMax(WORK_TYPE_EJ)); mConfigAbsoluteMaxSlots.put(WORK_TYPE_BG, workTypeConfig.getMax(WORK_TYPE_BG)); mConfigAbsoluteMaxSlots.put(WORK_TYPE_BGUSER, workTypeConfig.getMax(WORK_TYPE_BGUSER)); @@ -1260,15 +1314,10 @@ class JobConcurrencyManager { // We don't need to adjust reservations if only one work type was modified // because that work type is the one we're using. - // 0 is WORK_TYPE_NONE. - int workType = 1; - int rem = workTypes; - while (rem > 0) { - if ((rem & 1) != 0) { + for (int workType = 1; workType <= workTypes; workType <<= 1) { + if ((workType & workTypes) == workType) { maybeAdjustReservations(workType); } - rem = rem >>> 1; - workType = workType << 1; } } } @@ -1280,21 +1329,11 @@ class JobConcurrencyManager { int numAdj = 0; // We don't know which type we'll classify the job as when we run it yet, so make sure // we have space in all applicable slots. - if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) { - mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + adj); - numAdj++; - } - if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) { - mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + adj); - numAdj++; - } - if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) { - mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + adj); - numAdj++; - } - if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) { - mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + adj); - numAdj++; + for (int workType = 1; workType <= workTypes; workType <<= 1) { + if ((workTypes & workType) == workType) { + mNumPendingJobs.put(workType, mNumPendingJobs.get(workType) + adj); + numAdj++; + } } return numAdj; @@ -1388,105 +1427,45 @@ class JobConcurrencyManager { mNumUnspecializedRemaining = mConfigMaxTotal; // Step 1 - int runTop = mNumRunningJobs.get(WORK_TYPE_TOP); - int resTop = runTop; - mNumUnspecializedRemaining -= resTop; - int runEj = mNumRunningJobs.get(WORK_TYPE_EJ); - int resEj = runEj; - mNumUnspecializedRemaining -= resEj; - int runBg = mNumRunningJobs.get(WORK_TYPE_BG); - int resBg = runBg; - mNumUnspecializedRemaining -= resBg; - int runBgUser = mNumRunningJobs.get(WORK_TYPE_BGUSER); - int resBgUser = runBgUser; - mNumUnspecializedRemaining -= resBgUser; + for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { + int run = mNumRunningJobs.get(workType); + mRecycledReserved.put(workType, run); + mNumUnspecializedRemaining -= run; + } // Step 2 - final int numTop = runTop + mNumPendingJobs.get(WORK_TYPE_TOP); - int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, - Math.min(numTop, mConfigNumReservedSlots.get(WORK_TYPE_TOP) - resTop))); - resTop += fillUp; - mNumUnspecializedRemaining -= fillUp; - final int numEj = runEj + mNumPendingJobs.get(WORK_TYPE_EJ); - fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, - Math.min(numEj, mConfigNumReservedSlots.get(WORK_TYPE_EJ) - resEj))); - resEj += fillUp; - mNumUnspecializedRemaining -= fillUp; - final int numBg = runBg + mNumPendingJobs.get(WORK_TYPE_BG); - fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, - Math.min(numBg, mConfigNumReservedSlots.get(WORK_TYPE_BG) - resBg))); - resBg += fillUp; - mNumUnspecializedRemaining -= fillUp; - final int numBgUser = runBgUser + mNumPendingJobs.get(WORK_TYPE_BGUSER); - fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, - Math.min(numBgUser, - mConfigNumReservedSlots.get(WORK_TYPE_BGUSER) - resBgUser))); - resBgUser += fillUp; - mNumUnspecializedRemaining -= fillUp; + for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { + int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType); + int res = mRecycledReserved.get(workType); + int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, + Math.min(num, mConfigNumReservedSlots.get(workType) - res))); + res += fillUp; + mRecycledReserved.put(workType, res); + mNumUnspecializedRemaining -= fillUp; + } // Step 3 - int unspecializedAssigned = Math.max(0, - Math.min(mNumUnspecializedRemaining, - Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP), numTop) - resTop)); - mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop + unspecializedAssigned); - mNumUnspecializedRemaining -= unspecializedAssigned; - - unspecializedAssigned = Math.max(0, - Math.min(mNumUnspecializedRemaining, - Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_EJ), numEj) - resEj)); - mNumActuallyReservedSlots.put(WORK_TYPE_EJ, resEj + unspecializedAssigned); - mNumUnspecializedRemaining -= unspecializedAssigned; - - unspecializedAssigned = Math.max(0, - Math.min(mNumUnspecializedRemaining, - Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), numBg) - resBg)); - mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned); - mNumUnspecializedRemaining -= unspecializedAssigned; - - unspecializedAssigned = Math.max(0, - Math.min(mNumUnspecializedRemaining, - Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER), numBgUser) - - resBgUser)); - mNumActuallyReservedSlots.put(WORK_TYPE_BGUSER, resBgUser + unspecializedAssigned); - mNumUnspecializedRemaining -= unspecializedAssigned; + for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { + int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType); + int res = mRecycledReserved.get(workType); + int unspecializedAssigned = Math.max(0, + Math.min(mNumUnspecializedRemaining, + Math.min(mConfigAbsoluteMaxSlots.get(workType), num) - res)); + mNumActuallyReservedSlots.put(workType, res + unspecializedAssigned); + mNumUnspecializedRemaining -= unspecializedAssigned; + } } int canJobStart(int workTypes) { - if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) { - final int maxAllowed = Math.min( - mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP), - mNumActuallyReservedSlots.get(WORK_TYPE_TOP) + mNumUnspecializedRemaining); - if (mNumRunningJobs.get(WORK_TYPE_TOP) + mNumStartingJobs.get(WORK_TYPE_TOP) - < maxAllowed) { - return WORK_TYPE_TOP; - } - } - if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) { - final int maxAllowed = Math.min( - mConfigAbsoluteMaxSlots.get(WORK_TYPE_EJ), - mNumActuallyReservedSlots.get(WORK_TYPE_EJ) + mNumUnspecializedRemaining); - if (mNumRunningJobs.get(WORK_TYPE_EJ) + mNumStartingJobs.get(WORK_TYPE_EJ) - < maxAllowed) { - return WORK_TYPE_EJ; - } - } - if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) { - final int maxAllowed = Math.min( - mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), - mNumActuallyReservedSlots.get(WORK_TYPE_BG) + mNumUnspecializedRemaining); - if (mNumRunningJobs.get(WORK_TYPE_BG) + mNumStartingJobs.get(WORK_TYPE_BG) - < maxAllowed) { - return WORK_TYPE_BG; - } - } - if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) { - final int maxAllowed = Math.min( - mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER), - mNumActuallyReservedSlots.get(WORK_TYPE_BGUSER) - + mNumUnspecializedRemaining); - if (mNumRunningJobs.get(WORK_TYPE_BGUSER) + mNumStartingJobs.get(WORK_TYPE_BGUSER) - < maxAllowed) { - return WORK_TYPE_BGUSER; + for (int workType = 1; workType <= workTypes; workType <<= 1) { + if ((workTypes & workType) == workType) { + final int maxAllowed = Math.min( + mConfigAbsoluteMaxSlots.get(workType), + mNumActuallyReservedSlots.get(workType) + mNumUnspecializedRemaining); + if (mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType) + < maxAllowed) { + return workType; + } } } return WORK_TYPE_NONE; diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java index f87d5993c1b55..7d9ab3772733c 100644 --- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java @@ -20,8 +20,10 @@ import static com.android.server.job.JobConcurrencyManager.NUM_WORK_TYPES; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP; +import static com.android.server.job.JobConcurrencyManager.workTypeToString; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -54,7 +56,7 @@ public class WorkCountTrackerTest { private static final double[] EQUAL_PROBABILITY_CDF = buildWorkTypeCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, - 1.0 / NUM_WORK_TYPES); + 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES); private Random mRandom; private WorkCountTracker mWorkCountTracker; @@ -66,8 +68,9 @@ public class WorkCountTrackerTest { } @NonNull - private static double[] buildWorkTypeCdf(double pTop, double pEj, double pBg, double pBgUser) { - return buildCdf(pTop, pEj, pBg, pBgUser); + private static double[] buildWorkTypeCdf( + double pTop, double pFgs, double pEj, double pBg, double pBgUser) { + return buildCdf(pTop, pFgs, pEj, pBg, pBgUser); } @NonNull @@ -102,10 +105,12 @@ public class WorkCountTrackerTest { case 0: return WORK_TYPE_TOP; case 1: - return WORK_TYPE_EJ; + return WORK_TYPE_FGS; case 2: - return WORK_TYPE_BG; + return WORK_TYPE_EJ; case 3: + return WORK_TYPE_BG; + case 4: return WORK_TYPE_BGUSER; default: throw new IllegalStateException("Unknown work type"); @@ -224,12 +229,15 @@ public class WorkCountTrackerTest { private void startPendingJobs(Jobs jobs) { while (hasStartablePendingJob(jobs)) { - final int startingWorkType = - getRandomWorkType(EQUAL_PROBABILITY_CDF, mRandom.nextDouble()); + final int workType = getRandomWorkType(EQUAL_PROBABILITY_CDF, mRandom.nextDouble()); + + if (jobs.pending.get(workType) > 0) { + final int pendingMultiType = getPendingMultiType(jobs, workType); + final int startingWorkType = mWorkCountTracker.canJobStart(pendingMultiType); + if (startingWorkType == WORK_TYPE_NONE) { + continue; + } - if (jobs.pending.get(startingWorkType) > 0 - && mWorkCountTracker.canJobStart(startingWorkType) != WORK_TYPE_NONE) { - final int pendingMultiType = getPendingMultiType(jobs, startingWorkType); jobs.removePending(pendingMultiType); jobs.running.put(startingWorkType, jobs.running.get(startingWorkType) + 1); mWorkCountTracker.stageJob(startingWorkType, pendingMultiType); @@ -304,7 +312,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final List> minLimits = List.of(); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0); + final double[] cdf = buildWorkTypeCdf(0.5, 0, 0, 0.5, 0); final double[] numTypesCdf = buildCdf(.5, .3, .15, .05); final double probStart = 0.5; @@ -322,7 +330,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final List> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3); + final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 1.0 / 3); final double[] numTypesCdf = buildCdf(.75, .2, .05); final double probStart = 0.5; @@ -340,7 +348,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final List> minLimits = List.of(); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3); + final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 1.0 / 3); final double[] numTypesCdf = buildCdf(.05, .95); final double probStart = 0.5; @@ -358,7 +366,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)); final List> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.8, .1); + final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.8, .1); final double[] numTypesCdf = buildCdf(.5, .3, .15, .05); final double probStart = 0.5; @@ -376,7 +384,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)); final List> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.1, 0); + final double[] cdf = buildWorkTypeCdf(0.85, 0.05, 0, 0.1, 0); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -394,7 +402,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)); final List> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.1, .8); + final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.1, .8); final double[] numTypesCdf = buildCdf(0.5, 0.5); final double probStart = 0.5; @@ -413,7 +421,7 @@ public class WorkCountTrackerTest { final List> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.05, 0.05); + final double[] cdf = buildWorkTypeCdf(0.8, 0.1, 0, 0.05, 0.05); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -432,7 +440,7 @@ public class WorkCountTrackerTest { final List> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0, 0, 0.5, 0.5); + final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.5, 0.5); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -451,7 +459,7 @@ public class WorkCountTrackerTest { final List> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0, 0, 0.1, 0.9); + final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.1, 0.9); final double[] numTypesCdf = buildCdf(0.9, 0.1); final double probStart = 0.5; @@ -470,7 +478,7 @@ public class WorkCountTrackerTest { final List> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1)); final double probStop = 0.5; - final double[] cdf = buildWorkTypeCdf(0, 0, 0.9, 0.1); + final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.9, 0.1); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -488,7 +496,7 @@ public class WorkCountTrackerTest { final List> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(0.5, 0.5, 0, 0); + final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0, 0); final double[] numTypesCdf = buildCdf(0.1, 0.7, 0.2); final double probStart = 0.5; @@ -511,7 +519,7 @@ public class WorkCountTrackerTest { final List> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)); final double probStop = 0.13; - final double[] numTypesCdf = buildCdf(0, 0.05, 0.1, 0.85); + final double[] numTypesCdf = buildCdf(0, 0.05, 0.1, 0.8, 0.05); final double probStart = 0.87; checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, @@ -528,7 +536,7 @@ public class WorkCountTrackerTest { List.of(Pair.create(WORK_TYPE_EJ, 5), Pair.create(WORK_TYPE_BG, 4)); final List> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(.1, 0.5, 0.35, 0.05); + final double[] cdf = buildWorkTypeCdf(.1, 0, 0.5, 0.35, 0.05); final double[] numTypesCdf = buildCdf(1); final double probStart = 0.5; @@ -548,7 +556,7 @@ public class WorkCountTrackerTest { final List> minLimits = List.of(Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2)); final double probStop = 0.4; - final double[] cdf = buildWorkTypeCdf(0.01, 0.49, 0.1, 0.4); + final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.4, 0.1, 0.4); final double[] numTypesCdf = buildCdf(0.7, 0.3); final double probStart = 0.5; @@ -576,11 +584,13 @@ public class WorkCountTrackerTest { startPendingJobs(jobs); for (Pair run : resultRunning) { - assertWithMessage("Incorrect running result for work type " + run.first) + assertWithMessage( + "Incorrect running result for work type " + workTypeToString(run.first)) .that(jobs.running.get(run.first)).isEqualTo(run.second); } for (Pair pend : resultPending) { - assertWithMessage("Incorrect pending result for work type " + pend.first) + assertWithMessage( + "Incorrect pending result for work type " + workTypeToString(pend.first)) .that(jobs.pending.get(pend.first)).isEqualTo(pend.second); } } @@ -938,10 +948,15 @@ public class WorkCountTrackerTest { assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6); assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1); assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1); - assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4); + // If run the TOP jobs as TOP first, and a TOP|EJ job as EJ, then we'll have 4 TOP jobs + // remaining. + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isAtLeast(4); + // If we end up running the TOP|EJ jobs as TOP first, then we'll have 5 TOP jobs remaining. + assertThat(jobs.pending.get(WORK_TYPE_TOP)).isAtMost(5); // Can't equate pending EJ since some could be running as TOP and BG assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2); - assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(8); + assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(9); // Stop all jobs jobs.maybeFinishJobs(1); @@ -975,7 +990,7 @@ public class WorkCountTrackerTest { assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1); assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1); assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0); - assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2); + assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(1); assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(4); } } diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java index 2288a89255619..cc18317d0529b 100644 --- a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java @@ -18,7 +18,9 @@ package com.android.server.job; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP; +import static com.android.server.job.JobConcurrencyManager.workTypeToString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -44,10 +46,12 @@ import java.util.List; public class WorkTypeConfigTest { private static final String KEY_MAX_TOTAL = "concurrency_max_total_test"; private static final String KEY_MAX_TOP = "concurrency_max_top_test"; + private static final String KEY_MAX_FGS = "concurrency_max_fgs_test"; private static final String KEY_MAX_EJ = "concurrency_max_ej_test"; private static final String KEY_MAX_BG = "concurrency_max_bg_test"; private static final String KEY_MAX_BGUSER = "concurrency_max_bguser_test"; private static final String KEY_MIN_TOP = "concurrency_min_top_test"; + private static final String KEY_MIN_FGS = "concurrency_min_fgs_test"; private static final String KEY_MIN_EJ = "concurrency_min_ej_test"; private static final String KEY_MIN_BG = "concurrency_min_bg_test"; private static final String KEY_MIN_BGUSER = "concurrency_min_bguser_test"; @@ -59,15 +63,17 @@ public class WorkTypeConfigTest { private void resetConfig() { // DeviceConfig.resetToDefaults() doesn't work here. Need to reset constants manually. - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_EJ, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BGUSER, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_EJ, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BGUSER, "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_FGS, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_EJ, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BGUSER, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_FGS, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_EJ, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, null, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BGUSER, null, false); } private void check(@Nullable DeviceConfig.Properties config, @@ -103,10 +109,12 @@ public class WorkTypeConfigTest { assertEquals(expectedTotal, counts.getMaxTotal()); for (Pair min : expectedMinLimits) { - assertEquals((int) min.second, counts.getMinReserved(min.first)); + assertEquals("Incorrect min value for " + workTypeToString(min.first), + (int) min.second, counts.getMinReserved(min.first)); } for (Pair max : expectedMaxLimits) { - assertEquals((int) max.second, counts.getMax(max.first)); + assertEquals("Incorrect max value for " + workTypeToString(max.first), + (int) max.second, counts.getMax(max.first)); } } @@ -193,6 +201,14 @@ public class WorkTypeConfigTest { /* min */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 1)), /* max */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 1))); + check(null, /*default*/ 10, + /* min */ List.of(Pair.create(WORK_TYPE_TOP, 3), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1)), + /* max */ List.of(Pair.create(WORK_TYPE_FGS, 3)), + /*expected*/ true, 10, + /* min */ List.of(Pair.create(WORK_TYPE_TOP, 3), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1)), + /* max */ List.of(Pair.create(WORK_TYPE_FGS, 3))); check(null, /*default*/ 15, /* min */ List.of(Pair.create(WORK_TYPE_BG, 15)), /* max */ List.of(Pair.create(WORK_TYPE_BG, 15)), @@ -289,5 +305,30 @@ public class WorkTypeConfigTest { /*expected*/ true, 16, /* min */ List.of(Pair.create(WORK_TYPE_TOP, 1), Pair.create(WORK_TYPE_BG, 8)), /* max */ List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_BG, 16))); + + check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) + .setInt(KEY_MAX_TOTAL, 16) + .setInt(KEY_MAX_TOP, 16) + .setInt(KEY_MIN_TOP, 1) + .setInt(KEY_MAX_FGS, 15) + .setInt(KEY_MIN_FGS, 2) + .setInt(KEY_MAX_EJ, 14) + .setInt(KEY_MIN_EJ, 3) + .setInt(KEY_MAX_BG, 13) + .setInt(KEY_MIN_BG, 4) + .setInt(KEY_MAX_BGUSER, 12) + .setInt(KEY_MIN_BGUSER, 5) + .build(), + /*default*/ 9, + /* min */ List.of(Pair.create(WORK_TYPE_BG, 9)), + /* max */ List.of(Pair.create(WORK_TYPE_BG, 9)), + /*expected*/ true, 16, + /* min */ List.of(Pair.create(WORK_TYPE_TOP, 1), Pair.create(WORK_TYPE_FGS, 2), + Pair.create(WORK_TYPE_EJ, 3), + Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 5)), + /* max */ + List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_FGS, 15), + Pair.create(WORK_TYPE_EJ, 14), + Pair.create(WORK_TYPE_BG, 13), Pair.create(WORK_TYPE_BGUSER, 12))); } }