diff options
author | 2021-01-29 18:31:19 +0000 | |
---|---|---|
committer | 2021-01-29 18:31:19 +0000 | |
commit | dd56fa93f65b38f47883e88300aacb53902b8857 (patch) | |
tree | 32e13f15bacd5dc9de6b8cb18289da554104701c | |
parent | ab913b2ef30f74e1be1a901cd16228d5dbea0c29 (diff) | |
parent | 1477559fc27a90b330690bf30f3816967e943441 (diff) |
Merge "Introduce concept of work types." into sc-dev
-rw-r--r-- | apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java | 671 | ||||
-rw-r--r-- | apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java | 182 | ||||
-rw-r--r-- | apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java | 32 | ||||
-rw-r--r-- | apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java | 1 | ||||
-rw-r--r-- | services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java (renamed from services/tests/servicestests/src/com/android/server/job/JobCountTrackerTest.java) | 66 | ||||
-rw-r--r-- | services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java (renamed from services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java) | 53 |
6 files changed, 534 insertions, 471 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 7cad4ab0183d..e05f0b062dbe 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -16,6 +16,8 @@ package com.android.server.job; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.job.JobInfo; import android.content.BroadcastReceiver; @@ -26,8 +28,11 @@ import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.provider.DeviceConfig; +import android.util.ArraySet; import android.util.IndentingPrintWriter; +import android.util.Pair; import android.util.Slog; +import android.util.SparseIntArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -36,16 +41,17 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.procstats.ProcessStats; import com.android.internal.util.StatLogger; import com.android.server.JobSchedulerBackgroundThread; -import com.android.server.job.JobSchedulerService.MaxJobCountsPerMemoryTrimLevel; import com.android.server.job.controllers.JobStatus; import com.android.server.job.controllers.StateController; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Iterator; import java.util.List; /** - * This class decides, given the various configuration and the system status, how many more jobs - * can start. + * This class decides, given the various configuration and the system status, which jobs can start + * and which {@link JobServiceContext} to run each job on. */ class JobConcurrencyManager { private static final String TAG = JobSchedulerService.TAG; @@ -56,9 +62,22 @@ class JobConcurrencyManager { CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms"; private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000; + static final int WORK_TYPE_NONE = 0; + static final int WORK_TYPE_TOP = 1 << 0; + static final int WORK_TYPE_BG = 1 << 1; + private static final int NUM_WORK_TYPES = 2; + + @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = { + WORK_TYPE_NONE, + WORK_TYPE_TOP, + WORK_TYPE_BG + }) + @Retention(RetentionPolicy.SOURCE) + public @interface WorkType { + } + private final Object mLock; private final JobSchedulerService mService; - private final JobSchedulerService.Constants mConstants; private final Context mContext; private final Handler mHandler; @@ -72,6 +91,53 @@ class JobConcurrencyManager { private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; + private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON = + new WorkConfigLimitsPerMemoryTrimLevel( + new WorkTypeConfig("screen_on_normal", 8, + // defaultMin + List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 2)), + // defaultMax + List.of(Pair.create(WORK_TYPE_BG, 6))), + new WorkTypeConfig("screen_on_moderate", 8, + // defaultMin + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)), + // defaultMax + List.of(Pair.create(WORK_TYPE_BG, 4))), + new WorkTypeConfig("screen_on_low", 5, + // defaultMin + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)), + // defaultMax + List.of(Pair.create(WORK_TYPE_BG, 1))), + new WorkTypeConfig("screen_on_critical", 5, + // defaultMin + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)), + // defaultMax + List.of(Pair.create(WORK_TYPE_BG, 1))) + ); + private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF = + new WorkConfigLimitsPerMemoryTrimLevel( + new WorkTypeConfig("screen_off_normal", 10, + // defaultMin + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)), + // defaultMax + List.of(Pair.create(WORK_TYPE_BG, 6))), + new WorkTypeConfig("screen_off_moderate", 10, + // defaultMin + List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)), + // defaultMax + List.of(Pair.create(WORK_TYPE_BG, 4))), + new WorkTypeConfig("screen_off_low", 5, + // defaultMin + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)), + // defaultMax + List.of(Pair.create(WORK_TYPE_BG, 1))), + new WorkTypeConfig("screen_off_critical", 5, + // defaultMin + List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)), + // defaultMax + List.of(Pair.create(WORK_TYPE_BG, 1))) + ); + /** * This array essentially stores the state of mActiveServices array. * The ith index stores the job present on the ith JobServiceContext. @@ -84,10 +150,11 @@ class JobConcurrencyManager { int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT]; - /** Max job counts according to the current system state. */ - private JobSchedulerService.MaxJobCounts mMaxJobCounts; + int[] mRecycledWorkTypeForContext = new int[MAX_JOB_CONTEXTS_COUNT]; + + private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>(); - private final JobCountTracker mJobCountTracker = new JobCountTracker(); + private final WorkCountTracker mWorkCountTracker = new WorkCountTracker(); /** Wait for this long after screen off before adjusting the job concurrency. */ private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS; @@ -114,7 +181,6 @@ class JobConcurrencyManager { JobConcurrencyManager(JobSchedulerService service) { mService = service; mLock = mService.mLock; - mConstants = service.mConstants; mContext = service.getContext(); mHandler = JobSchedulerBackgroundThread.getHandler(); @@ -209,12 +275,6 @@ class JobConcurrencyManager { } } - private boolean isFgJob(JobStatus job) { - // (It's super confusing PRIORITY_BOUND_FOREGROUND_SERVICE isn't FG here) - return job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP - || job.shouldTreatAsExpeditedJob(); - } - @GuardedBy("mLock") private void refreshSystemStateLocked() { final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis(); @@ -237,28 +297,29 @@ class JobConcurrencyManager { } @GuardedBy("mLock") - private void updateMaxCountsLocked() { + private void updateCounterConfigLocked() { refreshSystemStateLocked(); - final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState - ? mConstants.MAX_JOB_COUNTS_SCREEN_ON - : mConstants.MAX_JOB_COUNTS_SCREEN_OFF; - + final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState + ? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF; + WorkTypeConfig workTypeConfig; switch (mLastMemoryTrimLevel) { case ProcessStats.ADJ_MEM_FACTOR_MODERATE: - mMaxJobCounts = jobCounts.moderate; + workTypeConfig = workConfigs.moderate; break; case ProcessStats.ADJ_MEM_FACTOR_LOW: - mMaxJobCounts = jobCounts.low; + workTypeConfig = workConfigs.low; break; case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: - mMaxJobCounts = jobCounts.critical; + workTypeConfig = workConfigs.critical; break; default: - mMaxJobCounts = jobCounts.normal; + workTypeConfig = workConfigs.normal; break; } + + mWorkCountTracker.setConfig(workTypeConfig); } /** @@ -282,31 +343,26 @@ class JobConcurrencyManager { Slog.d(TAG, printPendingQueueLocked()); } - final JobPackageTracker tracker = mService.mJobPackageTracker; final List<JobStatus> pendingJobs = mService.mPendingJobs; final List<JobServiceContext> activeServices = mService.mActiveServices; - final List<StateController> controllers = mService.mControllers; - - updateMaxCountsLocked(); // To avoid GC churn, we recycle the arrays. JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap; boolean[] slotChanged = mRecycledSlotChanged; int[] preferredUidForContext = mRecycledPreferredUidForContext; + int[] workTypeForContext = mRecycledWorkTypeForContext; + updateCounterConfigLocked(); + // Reset everything since we'll re-evaluate the current state. + mWorkCountTracker.resetCounts(); - // Initialize the work variables and also count running jobs. - mJobCountTracker.reset( - mMaxJobCounts.getMaxTotal(), - mMaxJobCounts.getMaxBg(), - mMaxJobCounts.getMinBg()); - - for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) { + for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { final JobServiceContext js = mService.mActiveServices.get(i); final JobStatus status = js.getRunningJobLocked(); if ((contextIdToJobMap[i] = status) != null) { - mJobCountTracker.incrementRunningJobCount(isFgJob(status)); + mWorkCountTracker.incrementRunningJobCount(js.getRunningJobWorkType()); + workTypeForContext[i] = js.getRunningJobWorkType(); } slotChanged[i] = false; @@ -317,41 +373,25 @@ class JobConcurrencyManager { } // Next, update the job priorities, and also count the pending FG / BG jobs. - for (int i = 0; i < pendingJobs.size(); i++) { - final JobStatus pending = pendingJobs.get(i); + updateNonRunningPriorities(pendingJobs, true); - // If job is already running, go to next job. - int jobRunningContext = findJobContextIdFromMap(pending, contextIdToJobMap); - if (jobRunningContext != -1) { - continue; - } - - final int priority = mService.evaluateJobPriorityLocked(pending); - pending.lastEvaluatedPriority = priority; - - mJobCountTracker.incrementPendingJobCount(isFgJob(pending)); - } - - mJobCountTracker.onCountDone(); + mWorkCountTracker.onCountDone(); for (int i = 0; i < pendingJobs.size(); i++) { final JobStatus nextPending = pendingJobs.get(i); - // Unfortunately we need to repeat this relatively expensive check. - int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap); - if (jobRunningContext != -1) { + if (mRunningJobs.contains(nextPending)) { continue; } // TODO(171305774): make sure HPJs aren't pre-empted and add dedicated contexts for them - final boolean isPendingFg = isFgJob(nextPending); - // Find an available slot for nextPending. The context should be available OR // it should have lowest priority among all running jobs // (sharing the same Uid as nextPending) int minPriorityForPreemption = Integer.MAX_VALUE; int selectedContextId = -1; + int workType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending)); boolean startingJob = false; for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) { JobStatus job = contextIdToJobMap[j]; @@ -360,7 +400,7 @@ class JobConcurrencyManager { final boolean preferredUidOkay = (preferredUid == nextPending.getUid()) || (preferredUid == JobServiceContext.NO_PREFERRED_UID); - if (preferredUidOkay && mJobCountTracker.canJobStart(isPendingFg)) { + if (preferredUidOkay && workType != WORK_TYPE_NONE) { // This slot is free, and we haven't yet hit the limit on // concurrent jobs... we can just throw the job in to here. selectedContextId = j; @@ -396,19 +436,19 @@ class JobConcurrencyManager { } if (startingJob) { // Increase the counters when we're going to start a job. - mJobCountTracker.onStartingNewJob(isPendingFg); + workTypeForContext[selectedContextId] = workType; + mWorkCountTracker.stageJob(workType); } } if (DEBUG) { Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); } - mJobCountTracker.logStatus(); - - tracker.noteConcurrency(mJobCountTracker.getTotalRunningJobCountToNote(), - mJobCountTracker.getFgRunningJobCountToNote()); + if (DEBUG) { + Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString()); + } - for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) { + for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { boolean preservePreferredUid = false; if (slotChanged[i]) { JobStatus js = activeServices.get(i).getRunningJobLocked(); @@ -426,30 +466,58 @@ class JobConcurrencyManager { Slog.d(TAG, "About to run job on context " + i + ", job: " + pendingJob); } - for (int ic=0; ic<controllers.size(); ic++) { - controllers.get(ic).prepareForExecutionLocked(pendingJob); - } - if (!activeServices.get(i).executeRunnableJob(pendingJob)) { - Slog.d(TAG, "Error executing " + pendingJob); - } - if (pendingJobs.remove(pendingJob)) { - tracker.noteNonpending(pendingJob); - } + startJobLocked(activeServices.get(i), pendingJob, workTypeForContext[i]); } } if (!preservePreferredUid) { activeServices.get(i).clearPreferredUid(); } } + mWorkCountTracker.resetStagingCount(); + noteConcurrency(); } - private static int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) { - for (int i=0; i<map.length; i++) { - if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) { - return i; + private void noteConcurrency() { + mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(), + // TODO: log per type instead of only TOP + mWorkCountTracker.getRunningJobCount(WORK_TYPE_TOP)); + } + + private void updateNonRunningPriorities(@NonNull final List<JobStatus> pendingJobs, + boolean updateCounter) { + for (int i = 0; i < pendingJobs.size(); i++) { + final JobStatus pending = pendingJobs.get(i); + + // If job is already running, go to next job. + if (mRunningJobs.contains(pending)) { + continue; + } + + pending.lastEvaluatedPriority = mService.evaluateJobPriorityLocked(pending); + + if (updateCounter) { + mWorkCountTracker.incrementPendingJobCount(getJobWorkTypes(pending)); } } - return -1; + } + + private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus, + @WorkType final int workType) { + final List<StateController> controllers = mService.mControllers; + for (int ic = 0; ic < controllers.size(); ic++) { + controllers.get(ic).prepareForExecutionLocked(jobStatus); + } + if (!worker.executeRunnableJob(jobStatus, workType)) { + Slog.e(TAG, "Error executing " + jobStatus); + mWorkCountTracker.onStagedJobFailed(workType); + } else { + mRunningJobs.add(jobStatus); + mWorkCountTracker.onJobStarted(workType); + } + final List<JobStatus> pendingJobs = mService.mPendingJobs; + if (pendingJobs.remove(jobStatus)) { + mService.mJobPackageTracker.noteNonpending(jobStatus); + } } @GuardedBy("mLock") @@ -484,6 +552,16 @@ class JobConcurrencyManager { mScreenOffAdjustmentDelayMs = properties.getLong( KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS); + + CONFIG_LIMITS_SCREEN_ON.normal.update(properties); + CONFIG_LIMITS_SCREEN_ON.moderate.update(properties); + CONFIG_LIMITS_SCREEN_ON.low.update(properties); + CONFIG_LIMITS_SCREEN_ON.critical.update(properties); + + CONFIG_LIMITS_SCREEN_OFF.normal.update(properties); + CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties); + CONFIG_LIMITS_SCREEN_OFF.low.update(properties); + CONFIG_LIMITS_SCREEN_OFF.critical.update(properties); } public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) { @@ -494,6 +572,14 @@ class JobConcurrencyManager { pw.print("Configuration:"); pw.increaseIndent(); pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println(); + CONFIG_LIMITS_SCREEN_ON.normal.dump(pw); + CONFIG_LIMITS_SCREEN_ON.moderate.dump(pw); + CONFIG_LIMITS_SCREEN_ON.low.dump(pw); + CONFIG_LIMITS_SCREEN_ON.critical.dump(pw); + CONFIG_LIMITS_SCREEN_OFF.normal.dump(pw); + CONFIG_LIMITS_SCREEN_OFF.moderate.dump(pw); + CONFIG_LIMITS_SCREEN_OFF.low.dump(pw); + CONFIG_LIMITS_SCREEN_OFF.critical.dump(pw); pw.decreaseIndent(); pw.print("Screen state: current "); @@ -514,7 +600,7 @@ class JobConcurrencyManager { pw.println("Current max jobs:"); pw.println(" "); - pw.println(mJobCountTracker); + pw.println(mWorkCountTracker); pw.println(); @@ -540,8 +626,6 @@ class JobConcurrencyManager { proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS, nowRealtime - mLastScreenOffRealtime); - mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER); - proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, mLastMemoryTrimLevel); mStatLogger.dumpProto(proto, JobConcurrencyManagerProto.STATS); @@ -549,199 +633,312 @@ class JobConcurrencyManager { proto.end(token); } - /** - * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running / - * pending, how many more job can start. - * - * Extracted for testing and logging. - */ + int getJobWorkTypes(@NonNull JobStatus js) { + int classification = 0; + // TODO(171305774): create dedicated work type for EJ and FGS + if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP + || js.shouldTreatAsExpeditedJob()) { + classification |= WORK_TYPE_TOP; + } else { + classification |= WORK_TYPE_BG; + } + return classification; + } + @VisibleForTesting - static class JobCountTracker { - private int mConfigNumMaxTotalJobs; - private int mConfigNumMaxBgJobs; - private int mConfigNumMinBgJobs; + static class WorkTypeConfig { + 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_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_"; + private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_"; + private static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_"; + private final String mConfigIdentifier; + + private int mMaxTotal; + private final SparseIntArray mMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES); + private final SparseIntArray mMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES); + private final int mDefaultMaxTotal; + private final SparseIntArray mDefaultMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES); + private final SparseIntArray mDefaultMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES); + + WorkTypeConfig(@NonNull String configIdentifier, int defaultMaxTotal, + List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax) { + mConfigIdentifier = configIdentifier; + mDefaultMaxTotal = mMaxTotal = defaultMaxTotal; + for (int i = defaultMin.size() - 1; i >= 0; --i) { + mDefaultMinReservedSlots.put(defaultMin.get(i).first, defaultMin.get(i).second); + } + for (int i = defaultMax.size() - 1; i >= 0; --i) { + mDefaultMaxAllowedSlots.put(defaultMax.get(i).first, defaultMax.get(i).second); + } + update(new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_JOB_SCHEDULER).build()); + } - private int mNumRunningFgJobs; - private int mNumRunningBgJobs; + void update(@NonNull DeviceConfig.Properties properties) { + // Ensure total in the range [1, MAX_JOB_CONTEXTS_COUNT]. + mMaxTotal = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT, + properties.getInt(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mDefaultMaxTotal))); + + mMaxAllowedSlots.clear(); + // Ensure they're in the range [1, total]. + final int maxTop = Math.max(1, Math.min(mMaxTotal, + properties.getInt(KEY_PREFIX_MAX_TOP + mConfigIdentifier, + mDefaultMaxAllowedSlots.get(WORK_TYPE_TOP, mMaxTotal)))); + mMaxAllowedSlots.put(WORK_TYPE_TOP, maxTop); + final int maxBg = Math.max(1, Math.min(mMaxTotal, + properties.getInt(KEY_PREFIX_MAX_BG + mConfigIdentifier, + mDefaultMaxAllowedSlots.get(WORK_TYPE_BG, mMaxTotal)))); + mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg); + + int remaining = mMaxTotal; + mMinReservedSlots.clear(); + // Ensure top is in the range [1, min(maxTop, total)] + final int minTop = Math.max(1, Math.min(Math.min(maxTop, mMaxTotal), + properties.getInt(KEY_PREFIX_MIN_TOP + mConfigIdentifier, + mDefaultMinReservedSlots.get(WORK_TYPE_TOP)))); + mMinReservedSlots.put(WORK_TYPE_TOP, minTop); + remaining -= minTop; + // Ensure bg is in the range [0, min(maxBg, remaining)] + final int minBg = Math.max(0, Math.min(Math.min(maxBg, remaining), + properties.getInt(KEY_PREFIX_MIN_BG + mConfigIdentifier, + mDefaultMinReservedSlots.get(WORK_TYPE_BG)))); + mMinReservedSlots.put(WORK_TYPE_BG, minBg); + } - private int mNumPendingFgJobs; - private int mNumPendingBgJobs; + int getMaxTotal() { + return mMaxTotal; + } - private int mNumStartingFgJobs; - private int mNumStartingBgJobs; + int getMax(@WorkType int workType) { + return mMaxAllowedSlots.get(workType, mMaxTotal); + } - private int mNumReservedForBg; - private int mNumActualMaxFgJobs; - private int mNumActualMaxBgJobs; + int getMinReserved(@WorkType int workType) { + return mMinReservedSlots.get(workType); + } - void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) { - mConfigNumMaxTotalJobs = numTotalMaxJobs; - mConfigNumMaxBgJobs = numMaxBgJobs; - mConfigNumMinBgJobs = numMinBgJobs; + void dump(IndentingPrintWriter pw) { + pw.print(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mMaxTotal).println(); + pw.print(KEY_PREFIX_MIN_TOP + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_TOP)) + .println(); + pw.print(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_TOP)) + .println(); + pw.print(KEY_PREFIX_MIN_BG + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_BG)) + .println(); + pw.print(KEY_PREFIX_MAX_BG + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_BG)) + .println(); + } + } - mNumRunningFgJobs = 0; - mNumRunningBgJobs = 0; + /** {@link WorkTypeConfig} for each memory trim level. */ + static class WorkConfigLimitsPerMemoryTrimLevel { + public final WorkTypeConfig normal; + public final WorkTypeConfig moderate; + public final WorkTypeConfig low; + public final WorkTypeConfig critical; + + WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate, + WorkTypeConfig low, WorkTypeConfig critical) { + this.normal = normal; + this.moderate = moderate; + this.low = low; + this.critical = critical; + } + } - mNumPendingFgJobs = 0; - mNumPendingBgJobs = 0; + /** + * This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs + * are running/pending, how many more job can start. + * + * Extracted for testing and logging. + */ + @VisibleForTesting + static class WorkCountTracker { + private int mConfigMaxTotal; + private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES); + private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES); + + /** + * Numbers may be lower in this than in {@link #mConfigNumReservedSlots} if there aren't + * enough ready jobs of a type to take up all of the desired reserved slots. + */ + private final SparseIntArray mNumActuallyReservedSlots = new SparseIntArray(NUM_WORK_TYPES); + private final SparseIntArray mNumPendingJobs = new SparseIntArray(NUM_WORK_TYPES); + private final SparseIntArray mNumRunningJobs = new SparseIntArray(NUM_WORK_TYPES); + private final SparseIntArray mNumStartingJobs = new SparseIntArray(NUM_WORK_TYPES); + private int mNumUnspecialized = 0; + private int mNumUnspecializedRemaining = 0; + + void setConfig(@NonNull WorkTypeConfig workTypeConfig) { + mConfigMaxTotal = workTypeConfig.getMaxTotal(); + mConfigNumReservedSlots.put(WORK_TYPE_TOP, + workTypeConfig.getMinReserved(WORK_TYPE_TOP)); + mConfigNumReservedSlots.put(WORK_TYPE_BG, workTypeConfig.getMinReserved(WORK_TYPE_BG)); + mConfigAbsoluteMaxSlots.put(WORK_TYPE_TOP, workTypeConfig.getMax(WORK_TYPE_TOP)); + mConfigAbsoluteMaxSlots.put(WORK_TYPE_BG, workTypeConfig.getMax(WORK_TYPE_BG)); + + mNumUnspecialized = mConfigMaxTotal; + mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_TOP); + mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BG); + mNumUnspecialized -= mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP); + mNumUnspecialized -= mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG); + calculateUnspecializedRemaining(); + } - mNumStartingFgJobs = 0; - mNumStartingBgJobs = 0; + private void calculateUnspecializedRemaining() { + mNumUnspecializedRemaining = mNumUnspecialized; + for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) { + mNumUnspecializedRemaining -= mNumRunningJobs.valueAt(i); + } + } - mNumReservedForBg = 0; - mNumActualMaxFgJobs = 0; - mNumActualMaxBgJobs = 0; + void resetCounts() { + mNumActuallyReservedSlots.clear(); + mNumPendingJobs.clear(); + mNumRunningJobs.clear(); + resetStagingCount(); } - void incrementRunningJobCount(boolean isFg) { - if (isFg) { - mNumRunningFgJobs++; - } else { - mNumRunningBgJobs++; - } + void resetStagingCount() { + mNumStartingJobs.clear(); } - void incrementPendingJobCount(boolean isFg) { - if (isFg) { - mNumPendingFgJobs++; - } else { - mNumPendingBgJobs++; - } + void incrementRunningJobCount(@WorkType int workType) { + mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1); } - void onStartingNewJob(boolean isFg) { - if (isFg) { - mNumStartingFgJobs++; - } else { - mNumStartingBgJobs++; + void incrementPendingJobCount(int workTypes) { + // 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) + 1); + } + if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) { + mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + 1); } } - void onCountDone() { - // Note some variables are used only here but are made class members in order to have - // them on logcat / dumpsys. - - // How many slots should we allocate to BG jobs at least? - // That's basically "getMinBg()", but if there are less jobs, decrease it. - // (e.g. even if min-bg is 2, if there's only 1 running+pending job, this has to be 1.) - final int reservedForBg = Math.min( - mConfigNumMinBgJobs, - mNumRunningBgJobs + mNumPendingBgJobs); - - // However, if there are FG jobs already running, we have to adjust it. - mNumReservedForBg = Math.min(reservedForBg, - mConfigNumMaxTotalJobs - mNumRunningFgJobs); - - // Max FG is [total - [number needed for BG jobs]] - // [number needed for BG jobs] is the bigger one of [running BG] or [reserved BG] - final int maxFg = - mConfigNumMaxTotalJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg); - - // The above maxFg is the theoretical max. If there are less FG jobs, the actual - // max FG will be lower accordingly. - mNumActualMaxFgJobs = Math.min( - maxFg, - mNumRunningFgJobs + mNumPendingFgJobs); - - // Max BG is [total - actual max FG], but cap at [config max BG]. - final int maxBg = Math.min( - mConfigNumMaxBgJobs, - mConfigNumMaxTotalJobs - mNumActualMaxFgJobs); - - // If there are less BG jobs than maxBg, then reduce the actual max BG accordingly. - // This isn't needed for the logic to work, but this will give consistent output - // on logcat and dumpsys. - mNumActualMaxBgJobs = Math.min( - maxBg, - mNumRunningBgJobs + mNumPendingBgJobs); - } - - boolean canJobStart(boolean isFg) { - if (isFg) { - return mNumRunningFgJobs + mNumStartingFgJobs < mNumActualMaxFgJobs; - } else { - return mNumRunningBgJobs + mNumStartingBgJobs < mNumActualMaxBgJobs; + void stageJob(@WorkType int workType) { + final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1; + mNumStartingJobs.put(workType, newNumStartingJobs); + mNumPendingJobs.put(workType, Math.max(0, mNumPendingJobs.get(workType) - 1)); + if (newNumStartingJobs + mNumRunningJobs.get(workType) + > mNumActuallyReservedSlots.get(workType)) { + mNumUnspecializedRemaining--; } } - public int getNumStartingFgJobs() { - return mNumStartingFgJobs; + void onStagedJobFailed(@WorkType int workType) { + final int oldNumStartingJobs = mNumStartingJobs.get(workType); + if (oldNumStartingJobs == 0) { + Slog.e(TAG, "# staged jobs for " + workType + " went negative."); + // We are in a bad state. We will eventually recover when the pending list is + // regenerated. + return; + } + mNumStartingJobs.put(workType, oldNumStartingJobs - 1); + maybeAdjustReservations(workType); } - public int getNumStartingBgJobs() { - return mNumStartingBgJobs; + private void maybeAdjustReservations(@WorkType int workType) { + // Always make sure we reserve the minimum number of slots in case new jobs become ready + // soon. + final int numRemainingForType = Math.max(mConfigNumReservedSlots.get(workType), + mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType) + + mNumPendingJobs.get(workType)); + if (numRemainingForType < mNumActuallyReservedSlots.get(workType)) { + // We've run all jobs for this type. Let another type use it now. + mNumActuallyReservedSlots.put(workType, numRemainingForType); + mNumUnspecializedRemaining++; + } } - int getTotalRunningJobCountToNote() { - return mNumRunningFgJobs + mNumRunningBgJobs - + mNumStartingFgJobs + mNumStartingBgJobs; + void onJobStarted(@WorkType int workType) { + mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1); + final int oldNumStartingJobs = mNumStartingJobs.get(workType); + if (oldNumStartingJobs == 0) { + Slog.e(TAG, "# stated jobs for " + workType + " went negative."); + // We are in a bad state. We will eventually recover when the pending list is + // regenerated. For now, only modify the running count. + } else { + mNumStartingJobs.put(workType, oldNumStartingJobs - 1); + } } - int getFgRunningJobCountToNote() { - return mNumRunningFgJobs + mNumStartingFgJobs; + void onCountDone() { + // Calculate how many slots to reserve for each work type. "Unspecialized" slots will + // be reserved for higher importance types first (ie. top before bg). + mNumUnspecialized = mConfigMaxTotal; + final int numTop = mNumRunningJobs.get(WORK_TYPE_TOP) + + mNumPendingJobs.get(WORK_TYPE_TOP); + final int resTop = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_TOP), numTop); + mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop); + mNumUnspecialized -= resTop; + final int numBg = mNumRunningJobs.get(WORK_TYPE_BG) + mNumPendingJobs.get(WORK_TYPE_BG); + final int resBg = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BG), numBg); + mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg); + mNumUnspecialized -= resBg; + calculateUnspecializedRemaining(); + + // Assign remaining unspecialized based on ranking. + int unspecializedAssigned = Math.max(0, + Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP), + Math.min(mNumUnspecializedRemaining, numTop - resTop))); + mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop + unspecializedAssigned); + mNumUnspecializedRemaining -= unspecializedAssigned; + unspecializedAssigned = Math.max(0, + Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), + Math.min(mNumUnspecializedRemaining, numBg - resBg))); + mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned); + mNumUnspecializedRemaining -= unspecializedAssigned; } - void logStatus() { - if (DEBUG) { - Slog.d(TAG, "assignJobsToContexts: " + this); + 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_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; + } } + return WORK_TYPE_NONE; } - public String toString() { - final int totalFg = mNumRunningFgJobs + mNumStartingFgJobs; - final int totalBg = mNumRunningBgJobs + mNumStartingBgJobs; - return String.format( - "Config={tot=%d bg min/max=%d/%d}" - + " Running[FG/BG (total)]: %d / %d (%d)" - + " Pending: %d / %d (%d)" - + " Actual max: %d%s / %d%s (%d%s)" - + " Res BG: %d" - + " Starting: %d / %d (%d)" - + " Total: %d%s / %d%s (%d%s)", - mConfigNumMaxTotalJobs, mConfigNumMinBgJobs, mConfigNumMaxBgJobs, - - mNumRunningFgJobs, mNumRunningBgJobs, mNumRunningFgJobs + mNumRunningBgJobs, - - mNumPendingFgJobs, mNumPendingBgJobs, mNumPendingFgJobs + mNumPendingBgJobs, - - mNumActualMaxFgJobs, (totalFg <= mConfigNumMaxTotalJobs) ? "" : "*", - mNumActualMaxBgJobs, (totalBg <= mConfigNumMaxBgJobs) ? "" : "*", - mNumActualMaxFgJobs + mNumActualMaxBgJobs, - (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumMaxTotalJobs) - ? "" : "*", - - mNumReservedForBg, - - mNumStartingFgJobs, mNumStartingBgJobs, mNumStartingFgJobs + mNumStartingBgJobs, - - totalFg, (totalFg <= mNumActualMaxFgJobs) ? "" : "*", - totalBg, (totalBg <= mNumActualMaxBgJobs) ? "" : "*", - totalFg + totalBg, (totalFg + totalBg <= mConfigNumMaxTotalJobs) ? "" : "*" - ); + int getRunningJobCount(@WorkType final int workType) { + return mNumRunningJobs.get(workType, 0); } - public void dumpProto(ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - - proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_TOTAL_JOBS, mConfigNumMaxTotalJobs); - proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_BG_JOBS, mConfigNumMaxBgJobs); - proto.write(JobCountTrackerProto.CONFIG_NUM_MIN_BG_JOBS, mConfigNumMinBgJobs); - - proto.write(JobCountTrackerProto.NUM_RUNNING_FG_JOBS, mNumRunningFgJobs); - proto.write(JobCountTrackerProto.NUM_RUNNING_BG_JOBS, mNumRunningBgJobs); - - proto.write(JobCountTrackerProto.NUM_PENDING_FG_JOBS, mNumPendingFgJobs); - proto.write(JobCountTrackerProto.NUM_PENDING_BG_JOBS, mNumPendingBgJobs); - - proto.write(JobCountTrackerProto.NUM_ACTUAL_MAX_FG_JOBS, mNumActualMaxFgJobs); - proto.write(JobCountTrackerProto.NUM_ACTUAL_MAX_BG_JOBS, mNumActualMaxBgJobs); - - proto.write(JobCountTrackerProto.NUM_RESERVED_FOR_BG, mNumReservedForBg); - - proto.write(JobCountTrackerProto.NUM_STARTING_FG_JOBS, mNumStartingFgJobs); - proto.write(JobCountTrackerProto.NUM_STARTING_BG_JOBS, mNumStartingBgJobs); - - proto.end(token); + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append("Config={"); + sb.append("tot=").append(mConfigMaxTotal); + sb.append(" mins="); + sb.append(mConfigNumReservedSlots); + sb.append(" maxs="); + sb.append(mConfigAbsoluteMaxSlots); + sb.append("}"); + + sb.append(", act res=").append(mNumActuallyReservedSlots); + sb.append(", Pending=").append(mNumPendingJobs); + sb.append(", Running=").append(mNumRunningJobs); + sb.append(", Staged=").append(mNumStartingJobs); + sb.append(", # unspecialized remaining=").append(mNumUnspecializedRemaining); + + return sb.toString(); } } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 885662c24147..ba78bda0d2fa 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -380,7 +380,6 @@ public class JobSchedulerService extends com.android.server.SystemService default: if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY) && !concurrencyUpdated) { - mConstants.updateConcurrencyConstantsLocked(); mConcurrencyManager.updateConfigLocked(); concurrencyUpdated = true; } else { @@ -408,119 +407,6 @@ public class JobSchedulerService extends com.android.server.SystemService mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); } - static class MaxJobCounts { - private final int mTotalDefault; - private final String mTotalKey; - private final int mMaxBgDefault; - private final String mMaxBgKey; - private final int mMinBgDefault; - private final String mMinBgKey; - private int mTotal; - private int mMaxBg; - private int mMinBg; - - MaxJobCounts(int totalDefault, String totalKey, - int maxBgDefault, String maxBgKey, int minBgDefault, String minBgKey) { - mTotalKey = totalKey; - mTotal = mTotalDefault = totalDefault; - mMaxBgKey = maxBgKey; - mMaxBg = mMaxBgDefault = maxBgDefault; - mMinBgKey = minBgKey; - mMinBg = mMinBgDefault = minBgDefault; - } - - public void update() { - mTotal = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER, - mTotalKey, mTotalDefault); - mMaxBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER, - mMaxBgKey, mMaxBgDefault); - mMinBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER, - mMinBgKey, mMinBgDefault); - - // Ensure total in the range [1, MAX_JOB_CONTEXTS_COUNT]. - mTotal = Math.min(Math.max(1, mTotal), MAX_JOB_CONTEXTS_COUNT); - - // Ensure maxBg in the range [1, total]. - mMaxBg = Math.min(Math.max(1, mMaxBg), mTotal); - - // Ensure minBg in the range [0, min(maxBg, total - 1)] - mMinBg = Math.min(Math.max(0, mMinBg), Math.min(mMaxBg, mTotal - 1)); - } - - /** Total number of jobs to run simultaneously. */ - public int getMaxTotal() { - return mTotal; - } - - /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */ - public int getMaxBg() { - return mMaxBg; - } - - /** - * We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any - * pending, rather than always running the TOTAL number of FG jobs. - */ - public int getMinBg() { - return mMinBg; - } - - public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); - pw.print(mTotalKey); - pw.print("="); - pw.print(mTotal); - pw.println(); - - pw.print(prefix); - pw.print(mMaxBgKey); - pw.print("="); - pw.print(mMaxBg); - pw.println(); - - pw.print(prefix); - pw.print(mMinBgKey); - pw.print("="); - pw.print(mMinBg); - pw.println(); - } - - public void dumpProto(ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - proto.write(MaxJobCountsProto.TOTAL_JOBS, mTotal); - proto.write(MaxJobCountsProto.MAX_BG, mMaxBg); - proto.write(MaxJobCountsProto.MIN_BG, mMinBg); - proto.end(token); - } - } - - /** {@link MaxJobCounts} for each memory trim level. */ - static class MaxJobCountsPerMemoryTrimLevel { - public final MaxJobCounts normal; - public final MaxJobCounts moderate; - public final MaxJobCounts low; - public final MaxJobCounts critical; - - MaxJobCountsPerMemoryTrimLevel( - MaxJobCounts normal, - MaxJobCounts moderate, MaxJobCounts low, - MaxJobCounts critical) { - this.normal = normal; - this.moderate = moderate; - this.low = low; - this.critical = critical; - } - - public void dumpProto(ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - normal.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.NORMAL); - moderate.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.MODERATE); - low.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.LOW); - critical.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.CRITICAL); - proto.end(token); - } - } - /** * All times are in milliseconds. Any access to this class or its fields should be done while * holding the JobSchedulerService.mLock lock. @@ -580,49 +466,6 @@ public class JobSchedulerService extends com.android.server.SystemService */ float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR; - /** Prefix for all of the max_job constants. */ - private static final String KEY_PREFIX_MAX_JOB = - JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY + "max_job_"; - - // Max job counts for screen on / off, for each memory trim level. - final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON = - new MaxJobCountsPerMemoryTrimLevel( - new MaxJobCounts( - 8, KEY_PREFIX_MAX_JOB + "total_on_normal", - 6, KEY_PREFIX_MAX_JOB + "max_bg_on_normal", - 2, KEY_PREFIX_MAX_JOB + "min_bg_on_normal"), - new MaxJobCounts( - 8, KEY_PREFIX_MAX_JOB + "total_on_moderate", - 4, KEY_PREFIX_MAX_JOB + "max_bg_on_moderate", - 2, KEY_PREFIX_MAX_JOB + "min_bg_on_moderate"), - new MaxJobCounts( - 5, KEY_PREFIX_MAX_JOB + "total_on_low", - 1, KEY_PREFIX_MAX_JOB + "max_bg_on_low", - 1, KEY_PREFIX_MAX_JOB + "min_bg_on_low"), - new MaxJobCounts( - 5, KEY_PREFIX_MAX_JOB + "total_on_critical", - 1, KEY_PREFIX_MAX_JOB + "max_bg_on_critical", - 1, KEY_PREFIX_MAX_JOB + "min_bg_on_critical")); - - final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF = - new MaxJobCountsPerMemoryTrimLevel( - new MaxJobCounts( - 10, KEY_PREFIX_MAX_JOB + "total_off_normal", - 6, KEY_PREFIX_MAX_JOB + "max_bg_off_normal", - 2, KEY_PREFIX_MAX_JOB + "min_bg_off_normal"), - new MaxJobCounts( - 10, KEY_PREFIX_MAX_JOB + "total_off_moderate", - 4, KEY_PREFIX_MAX_JOB + "max_bg_off_moderate", - 2, KEY_PREFIX_MAX_JOB + "min_bg_off_moderate"), - new MaxJobCounts( - 5, KEY_PREFIX_MAX_JOB + "total_off_low", - 1, KEY_PREFIX_MAX_JOB + "max_bg_off_low", - 1, KEY_PREFIX_MAX_JOB + "min_bg_off_low"), - new MaxJobCounts( - 5, KEY_PREFIX_MAX_JOB + "total_off_critical", - 1, KEY_PREFIX_MAX_JOB + "max_bg_off_critical", - 1, KEY_PREFIX_MAX_JOB + "min_bg_off_critical")); - /** * The minimum backoff time to allow for linear backoff. */ @@ -686,18 +529,6 @@ public class JobSchedulerService extends com.android.server.SystemService DEFAULT_MODERATE_USE_FACTOR); } - void updateConcurrencyConstantsLocked() { - MAX_JOB_COUNTS_SCREEN_ON.normal.update(); - MAX_JOB_COUNTS_SCREEN_ON.moderate.update(); - MAX_JOB_COUNTS_SCREEN_ON.low.update(); - MAX_JOB_COUNTS_SCREEN_ON.critical.update(); - - MAX_JOB_COUNTS_SCREEN_OFF.normal.update(); - MAX_JOB_COUNTS_SCREEN_OFF.moderate.update(); - MAX_JOB_COUNTS_SCREEN_OFF.low.update(); - MAX_JOB_COUNTS_SCREEN_OFF.critical.update(); - } - private void updateBackoffConstantsLocked() { MIN_LINEAR_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_LINEAR_BACKOFF_TIME_MS, @@ -747,16 +578,6 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println(); pw.print(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println(); - MAX_JOB_COUNTS_SCREEN_ON.normal.dump(pw, ""); - MAX_JOB_COUNTS_SCREEN_ON.moderate.dump(pw, ""); - MAX_JOB_COUNTS_SCREEN_ON.low.dump(pw, ""); - MAX_JOB_COUNTS_SCREEN_ON.critical.dump(pw, ""); - - MAX_JOB_COUNTS_SCREEN_OFF.normal.dump(pw, ""); - MAX_JOB_COUNTS_SCREEN_OFF.moderate.dump(pw, ""); - MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, ""); - MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, ""); - pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println(); pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println(); pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println(); @@ -781,9 +602,6 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR); proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR); - MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON); - MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF); - proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS); proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS); proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index 26b5abed745c..247b4211fdf9 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -16,6 +16,7 @@ package com.android.server.job; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE; import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; @@ -124,9 +125,12 @@ public final class JobServiceContext implements ServiceConnection { * * Any reads (dereferences) not done from the handler thread must be synchronized on * {@link #mLock}. - * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}. + * Writes can only be done from the handler thread, + * or {@link #executeRunnableJob(JobStatus, int)}. */ private JobStatus mRunningJob; + @JobConcurrencyManager.WorkType + private int mRunningJobWorkType; private JobCallback mRunningCallback; /** Used to store next job to run when current job is to be preempted. */ private int mPreferredUid; @@ -181,30 +185,26 @@ public final class JobServiceContext implements ServiceConnection { JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) { - this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper); - } - - @VisibleForTesting - JobServiceContext(Context context, Object lock, IBatteryStats batteryStats, - JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) { - mContext = context; - mLock = lock; + mContext = service.getContext(); + mLock = service.getLock(); mBatteryStats = batteryStats; mJobPackageTracker = tracker; mCallbackHandler = new JobServiceHandler(looper); - mCompletedListener = completedListener; + mCompletedListener = service; mAvailable = true; mVerb = VERB_FINISHED; mPreferredUid = NO_PREFERRED_UID; } /** - * Give a job to this context for execution. Callers must first check {@link #getRunningJobLocked()} + * Give a job to this context for execution. Callers must first check {@link + * #getRunningJobLocked()} * and ensure it is null to make sure this is a valid context. + * * @param job The status of the job that we are going to run. * @return True if the job is valid and is running. False if the job cannot be executed. */ - boolean executeRunnableJob(JobStatus job) { + boolean executeRunnableJob(JobStatus job, @JobConcurrencyManager.WorkType int workType) { synchronized (mLock) { if (!mAvailable) { Slog.e(TAG, "Starting new runnable but context is unavailable > Error."); @@ -214,6 +214,7 @@ public final class JobServiceContext implements ServiceConnection { mPreferredUid = NO_PREFERRED_UID; mRunningJob = job; + mRunningJobWorkType = workType; mRunningCallback = new JobCallback(); final boolean isDeadlineExpired = job.hasDeadlineConstraint() && @@ -282,6 +283,7 @@ public final class JobServiceContext implements ServiceConnection { Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable."); } mRunningJob = null; + mRunningJobWorkType = WORK_TYPE_NONE; mRunningCallback = null; mParams = null; mExecutionStartTimeElapsed = 0L; @@ -326,6 +328,11 @@ public final class JobServiceContext implements ServiceConnection { return mRunningJob; } + @JobConcurrencyManager.WorkType + int getRunningJobWorkType() { + return mRunningJobWorkType; + } + /** * Used only for debugging. Will return <code>"<null>"</code> if there is no job running. */ @@ -831,6 +838,7 @@ public final class JobServiceContext implements ServiceConnection { mContext.unbindService(JobServiceContext.this); mWakeLock = null; mRunningJob = null; + mRunningJobWorkType = WORK_TYPE_NONE; mRunningCallback = null; mParams = null; mVerb = VERB_FINISHED; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 09dc7d281cb4..539c3c960f2e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -305,6 +305,7 @@ public final class JobStatus { public Network network; public ServiceInfo serviceInfo; + /** The evaluated priority of the job when it started running. */ public int lastEvaluatedPriority; // If non-null, this is work that has been enqueued for the job. diff --git a/services/tests/servicestests/src/com/android/server/job/JobCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java index e5529cb899cc..15a9bcf59b28 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobCountTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java @@ -16,36 +16,42 @@ package com.android.server.job; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP; + import static com.google.common.truth.Truth.assertThat; import android.util.Log; +import android.util.Pair; + +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; -import com.android.server.job.JobConcurrencyManager.JobCountTracker; +import com.android.server.job.JobConcurrencyManager.WorkCountTracker; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.List; import java.util.Random; -import androidx.test.filters.MediumTest; -import androidx.test.runner.AndroidJUnit4; - /** - * Test for {@link com.android.server.job.JobConcurrencyManager.JobCountTracker}. + * Test for {@link WorkCountTracker}. */ @RunWith(AndroidJUnit4.class) @MediumTest -public class JobCountTrackerTest { - private static final String TAG = "JobCountTrackerTest"; +public class WorkCountTrackerTest { + private static final String TAG = "WorkerCountTrackerTest"; private Random mRandom; - private JobCountTracker mJobCountTracker; + private WorkCountTracker mWorkCountTracker; @Before public void setUp() { mRandom = new Random(1); // Always use the same series of pseudo random values. - mJobCountTracker = new JobCountTracker(); + mWorkCountTracker = new WorkCountTracker(); } /** @@ -83,44 +89,57 @@ public class JobCountTrackerTest { private void startPendingJobs(Jobs jobs, int totalMax, int maxBg, int minBg) { - mJobCountTracker.reset(totalMax, maxBg, minBg); + mWorkCountTracker.setConfig(new JobConcurrencyManager.WorkTypeConfig("critical", + totalMax, + // defaultMin + List.of(Pair.create(WORK_TYPE_TOP, totalMax - maxBg), + Pair.create(WORK_TYPE_BG, minBg)), + // defaultMax + List.of(Pair.create(WORK_TYPE_BG, maxBg)))); + mWorkCountTracker.resetCounts(); for (int i = 0; i < jobs.runningFg; i++) { - mJobCountTracker.incrementRunningJobCount(true); + mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_TOP); } for (int i = 0; i < jobs.runningBg; i++) { - mJobCountTracker.incrementRunningJobCount(false); + mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_BG); } for (int i = 0; i < jobs.pendingFg; i++) { - mJobCountTracker.incrementPendingJobCount(true); + mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_TOP); } for (int i = 0; i < jobs.pendingBg; i++) { - mJobCountTracker.incrementPendingJobCount(false); + mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_BG); } - mJobCountTracker.onCountDone(); + mWorkCountTracker.onCountDone(); - while ((jobs.pendingFg > 0 && mJobCountTracker.canJobStart(true)) - || (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false))) { + while ((jobs.pendingFg > 0 + && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE) + || (jobs.pendingBg > 0 + && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)) { final boolean isStartingFg = mRandom.nextBoolean(); if (isStartingFg) { - if (jobs.pendingFg > 0 && mJobCountTracker.canJobStart(true)) { + if (jobs.pendingFg > 0 + && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE) { jobs.pendingFg--; jobs.runningFg++; - mJobCountTracker.onStartingNewJob(true); + mWorkCountTracker.stageJob(WORK_TYPE_TOP); + mWorkCountTracker.onJobStarted(WORK_TYPE_TOP); } } else { - if (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false)) { + if (jobs.pendingBg > 0 + && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE) { jobs.pendingBg--; jobs.runningBg++; - mJobCountTracker.onStartingNewJob(false); + mWorkCountTracker.stageJob(WORK_TYPE_BG); + mWorkCountTracker.onJobStarted(WORK_TYPE_BG); } } } - Log.i(TAG, "" + mJobCountTracker); + Log.i(TAG, "" + mWorkCountTracker); } /** @@ -277,6 +296,7 @@ public class JobCountTrackerTest { startPendingJobs(jobs, totalMax, maxBg, minBg); +// fail(mWorkerCountTracker.toString()); assertThat(jobs.runningFg).isEqualTo(resultRunningFg); assertThat(jobs.runningBg).isEqualTo(resultRunningBg); @@ -300,6 +320,8 @@ public class JobCountTrackerTest { checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 1, /*res run/pen=*/ 5, 1, 5, 0); checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 4, 2, 6, 1); + checkSimple(8, 6, 2, /*run=*/ 0, 0, /*pen=*/ 0, 49, /*res run/pen=*/ 0, 6, 0, 43); + checkSimple(6, 4, 2, /*run=*/ 6, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 6, 0, 10, 3); } } diff --git a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java index 4c3674767e97..fba36cb402a0 100644 --- a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java +++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java @@ -15,22 +15,34 @@ */ package com.android.server.job; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG; +import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP; + import android.annotation.Nullable; import android.provider.DeviceConfig; +import android.util.Pair; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.server.job.JobSchedulerService.MaxJobCounts; +import com.android.server.job.JobConcurrencyManager.WorkTypeConfig; import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.List; + @RunWith(AndroidJUnit4.class) @SmallTest -public class MaxJobCountsTest { +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_BG = "concurrency_max_bg_test"; + private static final String KEY_MIN_TOP = "concurrency_min_top_test"; + private static final String KEY_MIN_BG = "concurrency_min_bg_test"; + @After public void tearDown() throws Exception { resetConfig(); @@ -38,9 +50,11 @@ public class MaxJobCountsTest { private void resetConfig() { // DeviceConfig.resetToDefaults() doesn't work here. Need to reset constants manually. - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "total", "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "maxbg", "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "minbg", "", false); + 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_BG, "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, "", false); } private void check(@Nullable DeviceConfig.Properties config, @@ -51,16 +65,19 @@ public class MaxJobCountsTest { DeviceConfig.setProperties(config); } - final MaxJobCounts counts = new JobSchedulerService.MaxJobCounts( - defaultTotal, "total", - defaultMaxBg, "maxbg", - defaultMinBg, "minbg"); + final WorkTypeConfig counts = new WorkTypeConfig("test", + defaultTotal, + // defaultMin + List.of(Pair.create(WORK_TYPE_TOP, defaultTotal - defaultMaxBg), + Pair.create(WORK_TYPE_BG, defaultMinBg)), + // defaultMax + List.of(Pair.create(WORK_TYPE_BG, defaultMaxBg))); - counts.update(); + counts.update(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER)); Assert.assertEquals(expectedTotal, counts.getMaxTotal()); - Assert.assertEquals(expectedMaxBg, counts.getMaxBg()); - Assert.assertEquals(expectedMinBg, counts.getMinBg()); + Assert.assertEquals(expectedMaxBg, counts.getMax(WORK_TYPE_BG)); + Assert.assertEquals(expectedMinBg, counts.getMinReserved(WORK_TYPE_BG)); } @Test @@ -80,19 +97,19 @@ public class MaxJobCountsTest { // Test for overriding with a setting string. check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) - .setInt("total", 5) - .setInt("maxbg", 4) - .setInt("minbg", 3) + .setInt(KEY_MAX_TOTAL, 5) + .setInt(KEY_MAX_BG, 4) + .setInt(KEY_MIN_BG, 3) .build(), /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3); check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) - .setInt("total", 5).build(), + .setInt(KEY_MAX_TOTAL, 5).build(), /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4); check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) - .setInt("maxbg", 4).build(), + .setInt(KEY_MAX_BG, 4).build(), /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4); check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) - .setInt("minbg", 3).build(), + .setInt(KEY_MIN_BG, 3).build(), /*default*/ 9, 9, 9, /*expected*/ 9, 9, 3); } } |