Merge "Introduce concept of work types." into sc-dev
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 7cad4ab..e05f0b0 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.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.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 @@
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 @@
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 @@
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 JobCountTracker mJobCountTracker = new JobCountTracker();
+ private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
+
+ 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 @@
JobConcurrencyManager(JobSchedulerService service) {
mService = service;
mLock = mService.mLock;
- mConstants = service.mConstants;
mContext = service.getContext();
mHandler = JobSchedulerBackgroundThread.getHandler();
@@ -209,12 +275,6 @@
}
}
- 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 @@
}
@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 @@
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 @@
}
// 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 @@
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 @@
}
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();
+ if (DEBUG) {
+ Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
+ }
- tracker.noteConcurrency(mJobCountTracker.getTotalRunningJobCountToNote(),
- mJobCountTracker.getFgRunningJobCountToNote());
-
- 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 @@
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 @@
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 @@
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 @@
pw.println("Current max jobs:");
pw.println(" ");
- pw.println(mJobCountTracker);
+ pw.println(mWorkCountTracker);
pw.println();
@@ -540,8 +626,6 @@
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 @@
proto.end(token);
}
+ 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 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());
+ }
+
+ 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);
+ }
+
+ int getMaxTotal() {
+ return mMaxTotal;
+ }
+
+ int getMax(@WorkType int workType) {
+ return mMaxAllowedSlots.get(workType, mMaxTotal);
+ }
+
+ int getMinReserved(@WorkType int workType) {
+ return mMinReservedSlots.get(workType);
+ }
+
+ 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();
+ }
+ }
+
+ /** {@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;
+ }
+ }
+
/**
- * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running /
- * pending, how many more job can start.
+ * 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 JobCountTracker {
- private int mConfigNumMaxTotalJobs;
- private int mConfigNumMaxBgJobs;
- private int mConfigNumMinBgJobs;
+ static class WorkCountTracker {
+ private int mConfigMaxTotal;
+ private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
+ private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES);
- private int mNumRunningFgJobs;
- private int mNumRunningBgJobs;
+ /**
+ * 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;
- private int mNumPendingFgJobs;
- private int mNumPendingBgJobs;
+ 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));
- private int mNumStartingFgJobs;
- private int mNumStartingBgJobs;
-
- private int mNumReservedForBg;
- private int mNumActualMaxFgJobs;
- private int mNumActualMaxBgJobs;
-
- void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) {
- mConfigNumMaxTotalJobs = numTotalMaxJobs;
- mConfigNumMaxBgJobs = numMaxBgJobs;
- mConfigNumMinBgJobs = numMinBgJobs;
-
- mNumRunningFgJobs = 0;
- mNumRunningBgJobs = 0;
-
- mNumPendingFgJobs = 0;
- mNumPendingBgJobs = 0;
-
- mNumStartingFgJobs = 0;
- mNumStartingBgJobs = 0;
-
- mNumReservedForBg = 0;
- mNumActualMaxFgJobs = 0;
- mNumActualMaxBgJobs = 0;
+ 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();
}
- void incrementRunningJobCount(boolean isFg) {
- if (isFg) {
- mNumRunningFgJobs++;
- } else {
- mNumRunningBgJobs++;
+ private void calculateUnspecializedRemaining() {
+ mNumUnspecializedRemaining = mNumUnspecialized;
+ for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
+ mNumUnspecializedRemaining -= mNumRunningJobs.valueAt(i);
}
}
- void incrementPendingJobCount(boolean isFg) {
- if (isFg) {
- mNumPendingFgJobs++;
- } else {
- mNumPendingBgJobs++;
+ void resetCounts() {
+ mNumActuallyReservedSlots.clear();
+ mNumPendingJobs.clear();
+ mNumRunningJobs.clear();
+ resetStagingCount();
+ }
+
+ void resetStagingCount() {
+ mNumStartingJobs.clear();
+ }
+
+ void incrementRunningJobCount(@WorkType int workType) {
+ mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1);
+ }
+
+ 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 onStartingNewJob(boolean isFg) {
- if (isFg) {
- mNumStartingFgJobs++;
+ 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--;
+ }
+ }
+
+ 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);
+ }
+
+ 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++;
+ }
+ }
+
+ 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 {
- mNumStartingBgJobs++;
+ mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
}
}
void onCountDone() {
- // Note some variables are used only here but are made class members in order to have
- // them on logcat / dumpsys.
+ // 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();
- // 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);
+ // 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;
}
- boolean canJobStart(boolean isFg) {
- if (isFg) {
- return mNumRunningFgJobs + mNumStartingFgJobs < mNumActualMaxFgJobs;
- } else {
- return mNumRunningBgJobs + mNumStartingBgJobs < mNumActualMaxBgJobs;
+ 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;
+ }
}
- }
-
- public int getNumStartingFgJobs() {
- return mNumStartingFgJobs;
- }
-
- public int getNumStartingBgJobs() {
- return mNumStartingBgJobs;
- }
-
- int getTotalRunningJobCountToNote() {
- return mNumRunningFgJobs + mNumRunningBgJobs
- + mNumStartingFgJobs + mNumStartingBgJobs;
- }
-
- int getFgRunningJobCountToNote() {
- return mNumRunningFgJobs + mNumStartingFgJobs;
- }
-
- void logStatus() {
- if (DEBUG) {
- Slog.d(TAG, "assignJobsToContexts: " + this);
+ 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;
+ }
+
+ int getRunningJobCount(@WorkType final int workType) {
+ return mNumRunningJobs.get(workType, 0);
}
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,
+ StringBuilder sb = new StringBuilder();
- mNumRunningFgJobs, mNumRunningBgJobs, mNumRunningFgJobs + mNumRunningBgJobs,
+ sb.append("Config={");
+ sb.append("tot=").append(mConfigMaxTotal);
+ sb.append(" mins=");
+ sb.append(mConfigNumReservedSlots);
+ sb.append(" maxs=");
+ sb.append(mConfigAbsoluteMaxSlots);
+ sb.append("}");
- mNumPendingFgJobs, mNumPendingBgJobs, mNumPendingFgJobs + mNumPendingBgJobs,
+ 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);
- 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) ? "" : "*"
- );
- }
-
- 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);
+ 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 885662c..ba78bda 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 @@
default:
if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
&& !concurrencyUpdated) {
- mConstants.updateConcurrencyConstantsLocked();
mConcurrencyManager.updateConfigLocked();
concurrencyUpdated = true;
} else {
@@ -408,119 +407,6 @@
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 @@
*/
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 @@
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 @@
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 @@
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 26b5abe..247b421 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 @@
*
* 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 @@
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 @@
mPreferredUid = NO_PREFERRED_UID;
mRunningJob = job;
+ mRunningJobWorkType = workType;
mRunningCallback = new JobCallback();
final boolean isDeadlineExpired =
job.hasDeadlineConstraint() &&
@@ -282,6 +283,7 @@
Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
}
mRunningJob = null;
+ mRunningJobWorkType = WORK_TYPE_NONE;
mRunningCallback = null;
mParams = null;
mExecutionStartTimeElapsed = 0L;
@@ -326,6 +328,11 @@
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 @@
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 09dc7d2..539c3c9 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 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
similarity index 79%
rename from services/tests/servicestests/src/com/android/server/job/JobCountTrackerTest.java
rename to services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index e5529cb..15a9bcf 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 com.android.server.job.JobConcurrencyManager.JobCountTracker;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+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 @@
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 @@
startPendingJobs(jobs, totalMax, maxBg, minBg);
+// fail(mWorkerCountTracker.toString());
assertThat(jobs.runningFg).isEqualTo(resultRunningFg);
assertThat(jobs.runningBg).isEqualTo(resultRunningBg);
@@ -300,6 +320,8 @@
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
similarity index 63%
rename from services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
rename to services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
index 4c36747..fba36cb 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 @@
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 @@
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 @@
// 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);
}
}