Merge "Introduce concept of work types." into sc-dev am: dd56fa93f6

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13431814

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: Ib9983cbd420cf856014b62ec76160255baff0dad
This commit is contained in:
Kweku Adams
2021-01-29 18:36:54 +00:00
committed by Automerger Merge Worker
6 changed files with 537 additions and 474 deletions

View File

@@ -16,6 +16,8 @@
package com.android.server.job; package com.android.server.job;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.job.JobInfo; import android.app.job.JobInfo;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
@@ -26,8 +28,11 @@ import android.os.Handler;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.RemoteException; import android.os.RemoteException;
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog; import android.util.Slog;
import android.util.SparseIntArray;
import android.util.TimeUtils; import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream; 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.app.procstats.ProcessStats;
import com.android.internal.util.StatLogger; import com.android.internal.util.StatLogger;
import com.android.server.JobSchedulerBackgroundThread; 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.JobStatus;
import com.android.server.job.controllers.StateController; import com.android.server.job.controllers.StateController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
/** /**
* This class decides, given the various configuration and the system status, how many more jobs * This class decides, given the various configuration and the system status, which jobs can start
* can start. * and which {@link JobServiceContext} to run each job on.
*/ */
class JobConcurrencyManager { class JobConcurrencyManager {
private static final String TAG = JobSchedulerService.TAG; private static final String TAG = JobSchedulerService.TAG;
@@ -56,9 +62,22 @@ class JobConcurrencyManager {
CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms"; CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms";
private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000; 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 Object mLock;
private final JobSchedulerService mService; private final JobSchedulerService mService;
private final JobSchedulerService.Constants mConstants;
private final Context mContext; private final Context mContext;
private final Handler mHandler; 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 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. * This array essentially stores the state of mActiveServices array.
* The ith index stores the job present on the ith JobServiceContext. * 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]; int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
/** Max job counts according to the current system state. */ int[] mRecycledWorkTypeForContext = new int[MAX_JOB_CONTEXTS_COUNT];
private JobSchedulerService.MaxJobCounts mMaxJobCounts;
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. */ /** Wait for this long after screen off before adjusting the job concurrency. */
private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS; private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS;
@@ -114,7 +181,6 @@ class JobConcurrencyManager {
JobConcurrencyManager(JobSchedulerService service) { JobConcurrencyManager(JobSchedulerService service) {
mService = service; mService = service;
mLock = mService.mLock; mLock = mService.mLock;
mConstants = service.mConstants;
mContext = service.getContext(); mContext = service.getContext();
mHandler = JobSchedulerBackgroundThread.getHandler(); 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") @GuardedBy("mLock")
private void refreshSystemStateLocked() { private void refreshSystemStateLocked() {
final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis(); final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
@@ -237,28 +297,29 @@ class JobConcurrencyManager {
} }
@GuardedBy("mLock") @GuardedBy("mLock")
private void updateMaxCountsLocked() { private void updateCounterConfigLocked() {
refreshSystemStateLocked(); refreshSystemStateLocked();
final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState
? mConstants.MAX_JOB_COUNTS_SCREEN_ON ? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF;
: mConstants.MAX_JOB_COUNTS_SCREEN_OFF;
WorkTypeConfig workTypeConfig;
switch (mLastMemoryTrimLevel) { switch (mLastMemoryTrimLevel) {
case ProcessStats.ADJ_MEM_FACTOR_MODERATE: case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
mMaxJobCounts = jobCounts.moderate; workTypeConfig = workConfigs.moderate;
break; break;
case ProcessStats.ADJ_MEM_FACTOR_LOW: case ProcessStats.ADJ_MEM_FACTOR_LOW:
mMaxJobCounts = jobCounts.low; workTypeConfig = workConfigs.low;
break; break;
case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
mMaxJobCounts = jobCounts.critical; workTypeConfig = workConfigs.critical;
break; break;
default: default:
mMaxJobCounts = jobCounts.normal; workTypeConfig = workConfigs.normal;
break; break;
} }
mWorkCountTracker.setConfig(workTypeConfig);
} }
/** /**
@@ -282,31 +343,26 @@ class JobConcurrencyManager {
Slog.d(TAG, printPendingQueueLocked()); Slog.d(TAG, printPendingQueueLocked());
} }
final JobPackageTracker tracker = mService.mJobPackageTracker;
final List<JobStatus> pendingJobs = mService.mPendingJobs; final List<JobStatus> pendingJobs = mService.mPendingJobs;
final List<JobServiceContext> activeServices = mService.mActiveServices; final List<JobServiceContext> activeServices = mService.mActiveServices;
final List<StateController> controllers = mService.mControllers;
updateMaxCountsLocked();
// To avoid GC churn, we recycle the arrays. // To avoid GC churn, we recycle the arrays.
JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap; JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap;
boolean[] slotChanged = mRecycledSlotChanged; boolean[] slotChanged = mRecycledSlotChanged;
int[] preferredUidForContext = mRecycledPreferredUidForContext; 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. for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
mJobCountTracker.reset(
mMaxJobCounts.getMaxTotal(),
mMaxJobCounts.getMaxBg(),
mMaxJobCounts.getMinBg());
for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
final JobServiceContext js = mService.mActiveServices.get(i); final JobServiceContext js = mService.mActiveServices.get(i);
final JobStatus status = js.getRunningJobLocked(); final JobStatus status = js.getRunningJobLocked();
if ((contextIdToJobMap[i] = status) != null) { if ((contextIdToJobMap[i] = status) != null) {
mJobCountTracker.incrementRunningJobCount(isFgJob(status)); mWorkCountTracker.incrementRunningJobCount(js.getRunningJobWorkType());
workTypeForContext[i] = js.getRunningJobWorkType();
} }
slotChanged[i] = false; slotChanged[i] = false;
@@ -317,41 +373,25 @@ class JobConcurrencyManager {
} }
// Next, update the job priorities, and also count the pending FG / BG jobs. // Next, update the job priorities, and also count the pending FG / BG jobs.
for (int i = 0; i < pendingJobs.size(); i++) { updateNonRunningPriorities(pendingJobs, true);
final JobStatus pending = pendingJobs.get(i);
// If job is already running, go to next job. mWorkCountTracker.onCountDone();
int jobRunningContext = findJobContextIdFromMap(pending, contextIdToJobMap);
if (jobRunningContext != -1) {
continue;
}
final int priority = mService.evaluateJobPriorityLocked(pending);
pending.lastEvaluatedPriority = priority;
mJobCountTracker.incrementPendingJobCount(isFgJob(pending));
}
mJobCountTracker.onCountDone();
for (int i = 0; i < pendingJobs.size(); i++) { for (int i = 0; i < pendingJobs.size(); i++) {
final JobStatus nextPending = pendingJobs.get(i); final JobStatus nextPending = pendingJobs.get(i);
// Unfortunately we need to repeat this relatively expensive check. if (mRunningJobs.contains(nextPending)) {
int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
if (jobRunningContext != -1) {
continue; continue;
} }
// TODO(171305774): make sure HPJs aren't pre-empted and add dedicated contexts for them // 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 // Find an available slot for nextPending. The context should be available OR
// it should have lowest priority among all running jobs // it should have lowest priority among all running jobs
// (sharing the same Uid as nextPending) // (sharing the same Uid as nextPending)
int minPriorityForPreemption = Integer.MAX_VALUE; int minPriorityForPreemption = Integer.MAX_VALUE;
int selectedContextId = -1; int selectedContextId = -1;
int workType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
boolean startingJob = false; boolean startingJob = false;
for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) { for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
JobStatus job = contextIdToJobMap[j]; JobStatus job = contextIdToJobMap[j];
@@ -360,7 +400,7 @@ class JobConcurrencyManager {
final boolean preferredUidOkay = (preferredUid == nextPending.getUid()) final boolean preferredUidOkay = (preferredUid == nextPending.getUid())
|| (preferredUid == JobServiceContext.NO_PREFERRED_UID); || (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 // This slot is free, and we haven't yet hit the limit on
// concurrent jobs... we can just throw the job in to here. // concurrent jobs... we can just throw the job in to here.
selectedContextId = j; selectedContextId = j;
@@ -396,19 +436,19 @@ class JobConcurrencyManager {
} }
if (startingJob) { if (startingJob) {
// Increase the counters when we're going to start a job. // Increase the counters when we're going to start a job.
mJobCountTracker.onStartingNewJob(isPendingFg); workTypeForContext[selectedContextId] = workType;
mWorkCountTracker.stageJob(workType);
} }
} }
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
} }
mJobCountTracker.logStatus(); if (DEBUG) {
Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
}
tracker.noteConcurrency(mJobCountTracker.getTotalRunningJobCountToNote(), for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
mJobCountTracker.getFgRunningJobCountToNote());
for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
boolean preservePreferredUid = false; boolean preservePreferredUid = false;
if (slotChanged[i]) { if (slotChanged[i]) {
JobStatus js = activeServices.get(i).getRunningJobLocked(); JobStatus js = activeServices.get(i).getRunningJobLocked();
@@ -426,30 +466,58 @@ class JobConcurrencyManager {
Slog.d(TAG, "About to run job on context " Slog.d(TAG, "About to run job on context "
+ i + ", job: " + pendingJob); + i + ", job: " + pendingJob);
} }
for (int ic=0; ic<controllers.size(); ic++) { startJobLocked(activeServices.get(i), pendingJob, workTypeForContext[i]);
controllers.get(ic).prepareForExecutionLocked(pendingJob);
}
if (!activeServices.get(i).executeRunnableJob(pendingJob)) {
Slog.d(TAG, "Error executing " + pendingJob);
}
if (pendingJobs.remove(pendingJob)) {
tracker.noteNonpending(pendingJob);
}
} }
} }
if (!preservePreferredUid) { if (!preservePreferredUid) {
activeServices.get(i).clearPreferredUid(); activeServices.get(i).clearPreferredUid();
} }
} }
mWorkCountTracker.resetStagingCount();
noteConcurrency();
} }
private static int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) { private void noteConcurrency() {
for (int i=0; i<map.length; i++) { mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(),
if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) { // TODO: log per type instead of only TOP
return i; 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") @GuardedBy("mLock")
@@ -484,6 +552,16 @@ class JobConcurrencyManager {
mScreenOffAdjustmentDelayMs = properties.getLong( mScreenOffAdjustmentDelayMs = properties.getLong(
KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS); 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) { public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
@@ -494,6 +572,14 @@ class JobConcurrencyManager {
pw.print("Configuration:"); pw.print("Configuration:");
pw.increaseIndent(); pw.increaseIndent();
pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println(); 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.decreaseIndent();
pw.print("Screen state: current "); pw.print("Screen state: current ");
@@ -514,7 +600,7 @@ class JobConcurrencyManager {
pw.println("Current max jobs:"); pw.println("Current max jobs:");
pw.println(" "); pw.println(" ");
pw.println(mJobCountTracker); pw.println(mWorkCountTracker);
pw.println(); pw.println();
@@ -540,8 +626,6 @@ class JobConcurrencyManager {
proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS, proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
nowRealtime - mLastScreenOffRealtime); nowRealtime - mLastScreenOffRealtime);
mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER);
proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, mLastMemoryTrimLevel); proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, mLastMemoryTrimLevel);
mStatLogger.dumpProto(proto, JobConcurrencyManagerProto.STATS); mStatLogger.dumpProto(proto, JobConcurrencyManagerProto.STATS);
@@ -549,199 +633,312 @@ class JobConcurrencyManager {
proto.end(token); 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 / * This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs
* pending, how many more job can start. * are running/pending, how many more job can start.
* *
* Extracted for testing and logging. * Extracted for testing and logging.
*/ */
@VisibleForTesting @VisibleForTesting
static class JobCountTracker { static class WorkCountTracker {
private int mConfigNumMaxTotalJobs; private int mConfigMaxTotal;
private int mConfigNumMaxBgJobs; private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
private int mConfigNumMinBgJobs; 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; void setConfig(@NonNull WorkTypeConfig workTypeConfig) {
private int mNumPendingBgJobs; 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; mNumUnspecialized = mConfigMaxTotal;
private int mNumStartingBgJobs; mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_TOP);
mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BG);
private int mNumReservedForBg; mNumUnspecialized -= mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP);
private int mNumActualMaxFgJobs; mNumUnspecialized -= mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG);
private int mNumActualMaxBgJobs; calculateUnspecializedRemaining();
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;
} }
void incrementRunningJobCount(boolean isFg) { private void calculateUnspecializedRemaining() {
if (isFg) { mNumUnspecializedRemaining = mNumUnspecialized;
mNumRunningFgJobs++; for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
} else { mNumUnspecializedRemaining -= mNumRunningJobs.valueAt(i);
mNumRunningBgJobs++;
} }
} }
void incrementPendingJobCount(boolean isFg) { void resetCounts() {
if (isFg) { mNumActuallyReservedSlots.clear();
mNumPendingFgJobs++; mNumPendingJobs.clear();
} else { mNumRunningJobs.clear();
mNumPendingBgJobs++; 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) { void stageJob(@WorkType int workType) {
if (isFg) { final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1;
mNumStartingFgJobs++; 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 { } else {
mNumStartingBgJobs++; mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
} }
} }
void onCountDone() { void onCountDone() {
// Note some variables are used only here but are made class members in order to have // Calculate how many slots to reserve for each work type. "Unspecialized" slots will
// them on logcat / dumpsys. // 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? // Assign remaining unspecialized based on ranking.
// That's basically "getMinBg()", but if there are less jobs, decrease it. int unspecializedAssigned = Math.max(0,
// (e.g. even if min-bg is 2, if there's only 1 running+pending job, this has to be 1.) Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP),
final int reservedForBg = Math.min( Math.min(mNumUnspecializedRemaining, numTop - resTop)));
mConfigNumMinBgJobs, mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop + unspecializedAssigned);
mNumRunningBgJobs + mNumPendingBgJobs); mNumUnspecializedRemaining -= unspecializedAssigned;
unspecializedAssigned = Math.max(0,
// However, if there are FG jobs already running, we have to adjust it. Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG),
mNumReservedForBg = Math.min(reservedForBg, Math.min(mNumUnspecializedRemaining, numBg - resBg)));
mConfigNumMaxTotalJobs - mNumRunningFgJobs); mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned);
mNumUnspecializedRemaining -= unspecializedAssigned;
// 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) { int canJobStart(int workTypes) {
if (isFg) { if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
return mNumRunningFgJobs + mNumStartingFgJobs < mNumActualMaxFgJobs; final int maxAllowed = Math.min(
} else { mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP),
return mNumRunningBgJobs + mNumStartingBgJobs < mNumActualMaxBgJobs; 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(
public int getNumStartingFgJobs() { mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG),
return mNumStartingFgJobs; mNumActuallyReservedSlots.get(WORK_TYPE_BG) + mNumUnspecializedRemaining);
} if (mNumRunningJobs.get(WORK_TYPE_BG) + mNumStartingJobs.get(WORK_TYPE_BG)
< maxAllowed) {
public int getNumStartingBgJobs() { return WORK_TYPE_BG;
return mNumStartingBgJobs; }
}
int getTotalRunningJobCountToNote() {
return mNumRunningFgJobs + mNumRunningBgJobs
+ mNumStartingFgJobs + mNumStartingBgJobs;
}
int getFgRunningJobCountToNote() {
return mNumRunningFgJobs + mNumStartingFgJobs;
}
void logStatus() {
if (DEBUG) {
Slog.d(TAG, "assignJobsToContexts: " + this);
} }
return WORK_TYPE_NONE;
}
int getRunningJobCount(@WorkType final int workType) {
return mNumRunningJobs.get(workType, 0);
} }
public String toString() { public String toString() {
final int totalFg = mNumRunningFgJobs + mNumStartingFgJobs; StringBuilder sb = new StringBuilder();
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, 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) ? "" : "*", return sb.toString();
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);
} }
} }
} }

View File

@@ -380,7 +380,6 @@ public class JobSchedulerService extends com.android.server.SystemService
default: default:
if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY) if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
&& !concurrencyUpdated) { && !concurrencyUpdated) {
mConstants.updateConcurrencyConstantsLocked();
mConcurrencyManager.updateConfigLocked(); mConcurrencyManager.updateConfigLocked();
concurrencyUpdated = true; concurrencyUpdated = true;
} else { } else {
@@ -408,119 +407,6 @@ public class JobSchedulerService extends com.android.server.SystemService
mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); 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 * All times are in milliseconds. Any access to this class or its fields should be done while
* holding the JobSchedulerService.mLock lock. * 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; 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. * 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); 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() { private void updateBackoffConstantsLocked() {
MIN_LINEAR_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER, MIN_LINEAR_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_MIN_LINEAR_BACKOFF_TIME_MS, 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_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
pw.print(KEY_MODERATE_USE_FACTOR, MODERATE_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_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_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).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.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR);
proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_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_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.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS);
proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC); proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);

View File

@@ -16,6 +16,7 @@
package com.android.server.job; 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.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 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 * Any reads (dereferences) not done from the handler thread must be synchronized on
* {@link #mLock}. * {@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; private JobStatus mRunningJob;
@JobConcurrencyManager.WorkType
private int mRunningJobWorkType;
private JobCallback mRunningCallback; private JobCallback mRunningCallback;
/** Used to store next job to run when current job is to be preempted. */ /** Used to store next job to run when current job is to be preempted. */
private int mPreferredUid; private int mPreferredUid;
@@ -181,30 +185,26 @@ public final class JobServiceContext implements ServiceConnection {
JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
JobPackageTracker tracker, Looper looper) { JobPackageTracker tracker, Looper looper) {
this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper); mContext = service.getContext();
} mLock = service.getLock();
@VisibleForTesting
JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) {
mContext = context;
mLock = lock;
mBatteryStats = batteryStats; mBatteryStats = batteryStats;
mJobPackageTracker = tracker; mJobPackageTracker = tracker;
mCallbackHandler = new JobServiceHandler(looper); mCallbackHandler = new JobServiceHandler(looper);
mCompletedListener = completedListener; mCompletedListener = service;
mAvailable = true; mAvailable = true;
mVerb = VERB_FINISHED; mVerb = VERB_FINISHED;
mPreferredUid = NO_PREFERRED_UID; 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. * 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. * @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. * @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) { synchronized (mLock) {
if (!mAvailable) { if (!mAvailable) {
Slog.e(TAG, "Starting new runnable but context is unavailable > Error."); 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; mPreferredUid = NO_PREFERRED_UID;
mRunningJob = job; mRunningJob = job;
mRunningJobWorkType = workType;
mRunningCallback = new JobCallback(); mRunningCallback = new JobCallback();
final boolean isDeadlineExpired = final boolean isDeadlineExpired =
job.hasDeadlineConstraint() && job.hasDeadlineConstraint() &&
@@ -282,6 +283,7 @@ public final class JobServiceContext implements ServiceConnection {
Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable."); Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
} }
mRunningJob = null; mRunningJob = null;
mRunningJobWorkType = WORK_TYPE_NONE;
mRunningCallback = null; mRunningCallback = null;
mParams = null; mParams = null;
mExecutionStartTimeElapsed = 0L; mExecutionStartTimeElapsed = 0L;
@@ -326,6 +328,11 @@ public final class JobServiceContext implements ServiceConnection {
return mRunningJob; return mRunningJob;
} }
@JobConcurrencyManager.WorkType
int getRunningJobWorkType() {
return mRunningJobWorkType;
}
/** /**
* Used only for debugging. Will return <code>"&lt;null&gt;"</code> if there is no job running. * Used only for debugging. Will return <code>"&lt;null&gt;"</code> if there is no job running.
*/ */
@@ -831,6 +838,7 @@ public final class JobServiceContext implements ServiceConnection {
mContext.unbindService(JobServiceContext.this); mContext.unbindService(JobServiceContext.this);
mWakeLock = null; mWakeLock = null;
mRunningJob = null; mRunningJob = null;
mRunningJobWorkType = WORK_TYPE_NONE;
mRunningCallback = null; mRunningCallback = null;
mParams = null; mParams = null;
mVerb = VERB_FINISHED; mVerb = VERB_FINISHED;

View File

@@ -305,6 +305,7 @@ public final class JobStatus {
public Network network; public Network network;
public ServiceInfo serviceInfo; public ServiceInfo serviceInfo;
/** The evaluated priority of the job when it started running. */
public int lastEvaluatedPriority; public int lastEvaluatedPriority;
// If non-null, this is work that has been enqueued for the job. // If non-null, this is work that has been enqueued for the job.

View File

@@ -16,36 +16,42 @@
package com.android.server.job; 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 static com.google.common.truth.Truth.assertThat;
import android.util.Log; 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.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.util.List;
import java.util.Random; 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) @RunWith(AndroidJUnit4.class)
@MediumTest @MediumTest
public class JobCountTrackerTest { public class WorkCountTrackerTest {
private static final String TAG = "JobCountTrackerTest"; private static final String TAG = "WorkerCountTrackerTest";
private Random mRandom; private Random mRandom;
private JobCountTracker mJobCountTracker; private WorkCountTracker mWorkCountTracker;
@Before @Before
public void setUp() { public void setUp() {
mRandom = new Random(1); // Always use the same series of pseudo random values. 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) { 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++) { for (int i = 0; i < jobs.runningFg; i++) {
mJobCountTracker.incrementRunningJobCount(true); mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_TOP);
} }
for (int i = 0; i < jobs.runningBg; i++) { for (int i = 0; i < jobs.runningBg; i++) {
mJobCountTracker.incrementRunningJobCount(false); mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_BG);
} }
for (int i = 0; i < jobs.pendingFg; i++) { for (int i = 0; i < jobs.pendingFg; i++) {
mJobCountTracker.incrementPendingJobCount(true); mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_TOP);
} }
for (int i = 0; i < jobs.pendingBg; i++) { 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)) while ((jobs.pendingFg > 0
|| (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false))) { && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE)
|| (jobs.pendingBg > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)) {
final boolean isStartingFg = mRandom.nextBoolean(); final boolean isStartingFg = mRandom.nextBoolean();
if (isStartingFg) { if (isStartingFg) {
if (jobs.pendingFg > 0 && mJobCountTracker.canJobStart(true)) { if (jobs.pendingFg > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE) {
jobs.pendingFg--; jobs.pendingFg--;
jobs.runningFg++; jobs.runningFg++;
mJobCountTracker.onStartingNewJob(true); mWorkCountTracker.stageJob(WORK_TYPE_TOP);
mWorkCountTracker.onJobStarted(WORK_TYPE_TOP);
} }
} else { } else {
if (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false)) { if (jobs.pendingBg > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE) {
jobs.pendingBg--; jobs.pendingBg--;
jobs.runningBg++; 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); startPendingJobs(jobs, totalMax, maxBg, minBg);
// fail(mWorkerCountTracker.toString());
assertThat(jobs.runningFg).isEqualTo(resultRunningFg); assertThat(jobs.runningFg).isEqualTo(resultRunningFg);
assertThat(jobs.runningBg).isEqualTo(resultRunningBg); 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, 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(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); checkSimple(6, 4, 2, /*run=*/ 6, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 6, 0, 10, 3);
} }
} }

View File

@@ -15,22 +15,34 @@
*/ */
package com.android.server.job; 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.annotation.Nullable;
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
import android.util.Pair;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4; 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.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.util.List;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @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 @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
resetConfig(); resetConfig();
@@ -38,9 +50,11 @@ public class MaxJobCountsTest {
private void resetConfig() { private void resetConfig() {
// DeviceConfig.resetToDefaults() doesn't work here. Need to reset constants manually. // 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, KEY_MAX_TOTAL, "", false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "maxbg", "", false); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, "", false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "minbg", "", 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, private void check(@Nullable DeviceConfig.Properties config,
@@ -51,16 +65,19 @@ public class MaxJobCountsTest {
DeviceConfig.setProperties(config); DeviceConfig.setProperties(config);
} }
final MaxJobCounts counts = new JobSchedulerService.MaxJobCounts( final WorkTypeConfig counts = new WorkTypeConfig("test",
defaultTotal, "total", defaultTotal,
defaultMaxBg, "maxbg", // defaultMin
defaultMinBg, "minbg"); 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(expectedTotal, counts.getMaxTotal());
Assert.assertEquals(expectedMaxBg, counts.getMaxBg()); Assert.assertEquals(expectedMaxBg, counts.getMax(WORK_TYPE_BG));
Assert.assertEquals(expectedMinBg, counts.getMinBg()); Assert.assertEquals(expectedMinBg, counts.getMinReserved(WORK_TYPE_BG));
} }
@Test @Test
@@ -80,19 +97,19 @@ public class MaxJobCountsTest {
// Test for overriding with a setting string. // Test for overriding with a setting string.
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt("total", 5) .setInt(KEY_MAX_TOTAL, 5)
.setInt("maxbg", 4) .setInt(KEY_MAX_BG, 4)
.setInt("minbg", 3) .setInt(KEY_MIN_BG, 3)
.build(), .build(),
/*default*/ 9, 9, 9, /*expected*/ 5, 4, 3); /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3);
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) 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); /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4);
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) 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); /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4);
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) 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); /*default*/ 9, 9, 9, /*expected*/ 9, 9, 3);
} }
} }