Merge "Introduce concept of work types." into sc-dev
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server.job;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.job.JobInfo;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -26,8 +28,11 @@ import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.util.ArraySet;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseIntArray;
|
||||
import android.util.TimeUtils;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
@@ -36,16 +41,17 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.app.procstats.ProcessStats;
|
||||
import com.android.internal.util.StatLogger;
|
||||
import com.android.server.JobSchedulerBackgroundThread;
|
||||
import com.android.server.job.JobSchedulerService.MaxJobCountsPerMemoryTrimLevel;
|
||||
import com.android.server.job.controllers.JobStatus;
|
||||
import com.android.server.job.controllers.StateController;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class decides, given the various configuration and the system status, how many more jobs
|
||||
* can start.
|
||||
* This class decides, given the various configuration and the system status, which jobs can start
|
||||
* and which {@link JobServiceContext} to run each job on.
|
||||
*/
|
||||
class JobConcurrencyManager {
|
||||
private static final String TAG = JobSchedulerService.TAG;
|
||||
@@ -56,9 +62,22 @@ class JobConcurrencyManager {
|
||||
CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms";
|
||||
private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000;
|
||||
|
||||
static final int WORK_TYPE_NONE = 0;
|
||||
static final int WORK_TYPE_TOP = 1 << 0;
|
||||
static final int WORK_TYPE_BG = 1 << 1;
|
||||
private static final int NUM_WORK_TYPES = 2;
|
||||
|
||||
@IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
|
||||
WORK_TYPE_NONE,
|
||||
WORK_TYPE_TOP,
|
||||
WORK_TYPE_BG
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface WorkType {
|
||||
}
|
||||
|
||||
private final Object mLock;
|
||||
private final JobSchedulerService mService;
|
||||
private final JobSchedulerService.Constants mConstants;
|
||||
private final Context mContext;
|
||||
private final Handler mHandler;
|
||||
|
||||
@@ -72,6 +91,53 @@ class JobConcurrencyManager {
|
||||
|
||||
private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
|
||||
|
||||
private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON =
|
||||
new WorkConfigLimitsPerMemoryTrimLevel(
|
||||
new WorkTypeConfig("screen_on_normal", 8,
|
||||
// defaultMin
|
||||
List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 2)),
|
||||
// defaultMax
|
||||
List.of(Pair.create(WORK_TYPE_BG, 6))),
|
||||
new WorkTypeConfig("screen_on_moderate", 8,
|
||||
// defaultMin
|
||||
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
|
||||
// defaultMax
|
||||
List.of(Pair.create(WORK_TYPE_BG, 4))),
|
||||
new WorkTypeConfig("screen_on_low", 5,
|
||||
// defaultMin
|
||||
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
|
||||
// defaultMax
|
||||
List.of(Pair.create(WORK_TYPE_BG, 1))),
|
||||
new WorkTypeConfig("screen_on_critical", 5,
|
||||
// defaultMin
|
||||
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
|
||||
// defaultMax
|
||||
List.of(Pair.create(WORK_TYPE_BG, 1)))
|
||||
);
|
||||
private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF =
|
||||
new WorkConfigLimitsPerMemoryTrimLevel(
|
||||
new WorkTypeConfig("screen_off_normal", 10,
|
||||
// defaultMin
|
||||
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
|
||||
// defaultMax
|
||||
List.of(Pair.create(WORK_TYPE_BG, 6))),
|
||||
new WorkTypeConfig("screen_off_moderate", 10,
|
||||
// defaultMin
|
||||
List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
|
||||
// defaultMax
|
||||
List.of(Pair.create(WORK_TYPE_BG, 4))),
|
||||
new WorkTypeConfig("screen_off_low", 5,
|
||||
// defaultMin
|
||||
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
|
||||
// defaultMax
|
||||
List.of(Pair.create(WORK_TYPE_BG, 1))),
|
||||
new WorkTypeConfig("screen_off_critical", 5,
|
||||
// defaultMin
|
||||
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
|
||||
// defaultMax
|
||||
List.of(Pair.create(WORK_TYPE_BG, 1)))
|
||||
);
|
||||
|
||||
/**
|
||||
* This array essentially stores the state of mActiveServices array.
|
||||
* The ith index stores the job present on the ith JobServiceContext.
|
||||
@@ -84,10 +150,11 @@ class JobConcurrencyManager {
|
||||
|
||||
int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
|
||||
|
||||
/** Max job counts according to the current system state. */
|
||||
private JobSchedulerService.MaxJobCounts mMaxJobCounts;
|
||||
int[] mRecycledWorkTypeForContext = new int[MAX_JOB_CONTEXTS_COUNT];
|
||||
|
||||
private final 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 @@ class JobConcurrencyManager {
|
||||
JobConcurrencyManager(JobSchedulerService service) {
|
||||
mService = service;
|
||||
mLock = mService.mLock;
|
||||
mConstants = service.mConstants;
|
||||
mContext = service.getContext();
|
||||
|
||||
mHandler = JobSchedulerBackgroundThread.getHandler();
|
||||
@@ -209,12 +275,6 @@ class JobConcurrencyManager {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFgJob(JobStatus job) {
|
||||
// (It's super confusing PRIORITY_BOUND_FOREGROUND_SERVICE isn't FG here)
|
||||
return job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP
|
||||
|| job.shouldTreatAsExpeditedJob();
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void refreshSystemStateLocked() {
|
||||
final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
|
||||
@@ -237,28 +297,29 @@ class JobConcurrencyManager {
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void updateMaxCountsLocked() {
|
||||
private void updateCounterConfigLocked() {
|
||||
refreshSystemStateLocked();
|
||||
|
||||
final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState
|
||||
? mConstants.MAX_JOB_COUNTS_SCREEN_ON
|
||||
: mConstants.MAX_JOB_COUNTS_SCREEN_OFF;
|
||||
|
||||
final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState
|
||||
? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF;
|
||||
|
||||
WorkTypeConfig workTypeConfig;
|
||||
switch (mLastMemoryTrimLevel) {
|
||||
case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
|
||||
mMaxJobCounts = jobCounts.moderate;
|
||||
workTypeConfig = workConfigs.moderate;
|
||||
break;
|
||||
case ProcessStats.ADJ_MEM_FACTOR_LOW:
|
||||
mMaxJobCounts = jobCounts.low;
|
||||
workTypeConfig = workConfigs.low;
|
||||
break;
|
||||
case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
|
||||
mMaxJobCounts = jobCounts.critical;
|
||||
workTypeConfig = workConfigs.critical;
|
||||
break;
|
||||
default:
|
||||
mMaxJobCounts = jobCounts.normal;
|
||||
workTypeConfig = workConfigs.normal;
|
||||
break;
|
||||
}
|
||||
|
||||
mWorkCountTracker.setConfig(workTypeConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,31 +343,26 @@ class JobConcurrencyManager {
|
||||
Slog.d(TAG, printPendingQueueLocked());
|
||||
}
|
||||
|
||||
final JobPackageTracker tracker = mService.mJobPackageTracker;
|
||||
final List<JobStatus> pendingJobs = mService.mPendingJobs;
|
||||
final List<JobServiceContext> activeServices = mService.mActiveServices;
|
||||
final List<StateController> controllers = mService.mControllers;
|
||||
|
||||
updateMaxCountsLocked();
|
||||
|
||||
// To avoid GC churn, we recycle the arrays.
|
||||
JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap;
|
||||
boolean[] slotChanged = mRecycledSlotChanged;
|
||||
int[] preferredUidForContext = mRecycledPreferredUidForContext;
|
||||
int[] workTypeForContext = mRecycledWorkTypeForContext;
|
||||
|
||||
updateCounterConfigLocked();
|
||||
// Reset everything since we'll re-evaluate the current state.
|
||||
mWorkCountTracker.resetCounts();
|
||||
|
||||
// Initialize the work variables and also count running jobs.
|
||||
mJobCountTracker.reset(
|
||||
mMaxJobCounts.getMaxTotal(),
|
||||
mMaxJobCounts.getMaxBg(),
|
||||
mMaxJobCounts.getMinBg());
|
||||
|
||||
for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
|
||||
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
|
||||
final JobServiceContext js = mService.mActiveServices.get(i);
|
||||
final JobStatus status = js.getRunningJobLocked();
|
||||
|
||||
if ((contextIdToJobMap[i] = status) != null) {
|
||||
mJobCountTracker.incrementRunningJobCount(isFgJob(status));
|
||||
mWorkCountTracker.incrementRunningJobCount(js.getRunningJobWorkType());
|
||||
workTypeForContext[i] = js.getRunningJobWorkType();
|
||||
}
|
||||
|
||||
slotChanged[i] = false;
|
||||
@@ -317,41 +373,25 @@ class JobConcurrencyManager {
|
||||
}
|
||||
|
||||
// Next, update the job priorities, and also count the pending FG / BG jobs.
|
||||
for (int i = 0; i < pendingJobs.size(); i++) {
|
||||
final JobStatus pending = pendingJobs.get(i);
|
||||
updateNonRunningPriorities(pendingJobs, true);
|
||||
|
||||
// If job is already running, go to next job.
|
||||
int jobRunningContext = findJobContextIdFromMap(pending, contextIdToJobMap);
|
||||
if (jobRunningContext != -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final int priority = mService.evaluateJobPriorityLocked(pending);
|
||||
pending.lastEvaluatedPriority = priority;
|
||||
|
||||
mJobCountTracker.incrementPendingJobCount(isFgJob(pending));
|
||||
}
|
||||
|
||||
mJobCountTracker.onCountDone();
|
||||
mWorkCountTracker.onCountDone();
|
||||
|
||||
for (int i = 0; i < pendingJobs.size(); i++) {
|
||||
final JobStatus nextPending = pendingJobs.get(i);
|
||||
|
||||
// Unfortunately we need to repeat this relatively expensive check.
|
||||
int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
|
||||
if (jobRunningContext != -1) {
|
||||
if (mRunningJobs.contains(nextPending)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO(171305774): make sure HPJs aren't pre-empted and add dedicated contexts for them
|
||||
|
||||
final boolean isPendingFg = isFgJob(nextPending);
|
||||
|
||||
// Find an available slot for nextPending. The context should be available OR
|
||||
// it should have lowest priority among all running jobs
|
||||
// (sharing the same Uid as nextPending)
|
||||
int minPriorityForPreemption = Integer.MAX_VALUE;
|
||||
int selectedContextId = -1;
|
||||
int workType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
|
||||
boolean startingJob = false;
|
||||
for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
|
||||
JobStatus job = contextIdToJobMap[j];
|
||||
@@ -360,7 +400,7 @@ class JobConcurrencyManager {
|
||||
final boolean preferredUidOkay = (preferredUid == nextPending.getUid())
|
||||
|| (preferredUid == JobServiceContext.NO_PREFERRED_UID);
|
||||
|
||||
if (preferredUidOkay && mJobCountTracker.canJobStart(isPendingFg)) {
|
||||
if (preferredUidOkay && workType != WORK_TYPE_NONE) {
|
||||
// This slot is free, and we haven't yet hit the limit on
|
||||
// concurrent jobs... we can just throw the job in to here.
|
||||
selectedContextId = j;
|
||||
@@ -396,19 +436,19 @@ class JobConcurrencyManager {
|
||||
}
|
||||
if (startingJob) {
|
||||
// Increase the counters when we're going to start a job.
|
||||
mJobCountTracker.onStartingNewJob(isPendingFg);
|
||||
workTypeForContext[selectedContextId] = workType;
|
||||
mWorkCountTracker.stageJob(workType);
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
|
||||
}
|
||||
|
||||
mJobCountTracker.logStatus();
|
||||
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 @@ class JobConcurrencyManager {
|
||||
Slog.d(TAG, "About to run job on context "
|
||||
+ i + ", job: " + pendingJob);
|
||||
}
|
||||
for (int ic=0; ic<controllers.size(); ic++) {
|
||||
controllers.get(ic).prepareForExecutionLocked(pendingJob);
|
||||
}
|
||||
if (!activeServices.get(i).executeRunnableJob(pendingJob)) {
|
||||
Slog.d(TAG, "Error executing " + pendingJob);
|
||||
}
|
||||
if (pendingJobs.remove(pendingJob)) {
|
||||
tracker.noteNonpending(pendingJob);
|
||||
}
|
||||
startJobLocked(activeServices.get(i), pendingJob, workTypeForContext[i]);
|
||||
}
|
||||
}
|
||||
if (!preservePreferredUid) {
|
||||
activeServices.get(i).clearPreferredUid();
|
||||
}
|
||||
}
|
||||
mWorkCountTracker.resetStagingCount();
|
||||
noteConcurrency();
|
||||
}
|
||||
|
||||
private static int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
|
||||
for (int i=0; i<map.length; i++) {
|
||||
if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
|
||||
return i;
|
||||
private void noteConcurrency() {
|
||||
mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(),
|
||||
// TODO: log per type instead of only TOP
|
||||
mWorkCountTracker.getRunningJobCount(WORK_TYPE_TOP));
|
||||
}
|
||||
|
||||
private void updateNonRunningPriorities(@NonNull final List<JobStatus> pendingJobs,
|
||||
boolean updateCounter) {
|
||||
for (int i = 0; i < pendingJobs.size(); i++) {
|
||||
final JobStatus pending = pendingJobs.get(i);
|
||||
|
||||
// If job is already running, go to next job.
|
||||
if (mRunningJobs.contains(pending)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pending.lastEvaluatedPriority = mService.evaluateJobPriorityLocked(pending);
|
||||
|
||||
if (updateCounter) {
|
||||
mWorkCountTracker.incrementPendingJobCount(getJobWorkTypes(pending));
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
|
||||
@WorkType final int workType) {
|
||||
final List<StateController> controllers = mService.mControllers;
|
||||
for (int ic = 0; ic < controllers.size(); ic++) {
|
||||
controllers.get(ic).prepareForExecutionLocked(jobStatus);
|
||||
}
|
||||
if (!worker.executeRunnableJob(jobStatus, workType)) {
|
||||
Slog.e(TAG, "Error executing " + jobStatus);
|
||||
mWorkCountTracker.onStagedJobFailed(workType);
|
||||
} else {
|
||||
mRunningJobs.add(jobStatus);
|
||||
mWorkCountTracker.onJobStarted(workType);
|
||||
}
|
||||
final List<JobStatus> pendingJobs = mService.mPendingJobs;
|
||||
if (pendingJobs.remove(jobStatus)) {
|
||||
mService.mJobPackageTracker.noteNonpending(jobStatus);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@@ -484,6 +552,16 @@ class JobConcurrencyManager {
|
||||
|
||||
mScreenOffAdjustmentDelayMs = properties.getLong(
|
||||
KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS);
|
||||
|
||||
CONFIG_LIMITS_SCREEN_ON.normal.update(properties);
|
||||
CONFIG_LIMITS_SCREEN_ON.moderate.update(properties);
|
||||
CONFIG_LIMITS_SCREEN_ON.low.update(properties);
|
||||
CONFIG_LIMITS_SCREEN_ON.critical.update(properties);
|
||||
|
||||
CONFIG_LIMITS_SCREEN_OFF.normal.update(properties);
|
||||
CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties);
|
||||
CONFIG_LIMITS_SCREEN_OFF.low.update(properties);
|
||||
CONFIG_LIMITS_SCREEN_OFF.critical.update(properties);
|
||||
}
|
||||
|
||||
public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
|
||||
@@ -494,6 +572,14 @@ class JobConcurrencyManager {
|
||||
pw.print("Configuration:");
|
||||
pw.increaseIndent();
|
||||
pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
|
||||
CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
|
||||
CONFIG_LIMITS_SCREEN_ON.moderate.dump(pw);
|
||||
CONFIG_LIMITS_SCREEN_ON.low.dump(pw);
|
||||
CONFIG_LIMITS_SCREEN_ON.critical.dump(pw);
|
||||
CONFIG_LIMITS_SCREEN_OFF.normal.dump(pw);
|
||||
CONFIG_LIMITS_SCREEN_OFF.moderate.dump(pw);
|
||||
CONFIG_LIMITS_SCREEN_OFF.low.dump(pw);
|
||||
CONFIG_LIMITS_SCREEN_OFF.critical.dump(pw);
|
||||
pw.decreaseIndent();
|
||||
|
||||
pw.print("Screen state: current ");
|
||||
@@ -514,7 +600,7 @@ class JobConcurrencyManager {
|
||||
|
||||
pw.println("Current max jobs:");
|
||||
pw.println(" ");
|
||||
pw.println(mJobCountTracker);
|
||||
pw.println(mWorkCountTracker);
|
||||
|
||||
pw.println();
|
||||
|
||||
@@ -540,8 +626,6 @@ class JobConcurrencyManager {
|
||||
proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
|
||||
nowRealtime - mLastScreenOffRealtime);
|
||||
|
||||
mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER);
|
||||
|
||||
proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, mLastMemoryTrimLevel);
|
||||
|
||||
mStatLogger.dumpProto(proto, JobConcurrencyManagerProto.STATS);
|
||||
@@ -549,199 +633,312 @@ class JobConcurrencyManager {
|
||||
proto.end(token);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,7 +380,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
default:
|
||||
if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
|
||||
&& !concurrencyUpdated) {
|
||||
mConstants.updateConcurrencyConstantsLocked();
|
||||
mConcurrencyManager.updateConfigLocked();
|
||||
concurrencyUpdated = true;
|
||||
} else {
|
||||
@@ -408,119 +407,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
|
||||
}
|
||||
|
||||
static class MaxJobCounts {
|
||||
private final int mTotalDefault;
|
||||
private final String mTotalKey;
|
||||
private final int mMaxBgDefault;
|
||||
private final String mMaxBgKey;
|
||||
private final int mMinBgDefault;
|
||||
private final String mMinBgKey;
|
||||
private int mTotal;
|
||||
private int mMaxBg;
|
||||
private int mMinBg;
|
||||
|
||||
MaxJobCounts(int totalDefault, String totalKey,
|
||||
int maxBgDefault, String maxBgKey, int minBgDefault, String minBgKey) {
|
||||
mTotalKey = totalKey;
|
||||
mTotal = mTotalDefault = totalDefault;
|
||||
mMaxBgKey = maxBgKey;
|
||||
mMaxBg = mMaxBgDefault = maxBgDefault;
|
||||
mMinBgKey = minBgKey;
|
||||
mMinBg = mMinBgDefault = minBgDefault;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
mTotal = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
|
||||
mTotalKey, mTotalDefault);
|
||||
mMaxBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
|
||||
mMaxBgKey, mMaxBgDefault);
|
||||
mMinBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
|
||||
mMinBgKey, mMinBgDefault);
|
||||
|
||||
// Ensure total in the range [1, MAX_JOB_CONTEXTS_COUNT].
|
||||
mTotal = Math.min(Math.max(1, mTotal), MAX_JOB_CONTEXTS_COUNT);
|
||||
|
||||
// Ensure maxBg in the range [1, total].
|
||||
mMaxBg = Math.min(Math.max(1, mMaxBg), mTotal);
|
||||
|
||||
// Ensure minBg in the range [0, min(maxBg, total - 1)]
|
||||
mMinBg = Math.min(Math.max(0, mMinBg), Math.min(mMaxBg, mTotal - 1));
|
||||
}
|
||||
|
||||
/** Total number of jobs to run simultaneously. */
|
||||
public int getMaxTotal() {
|
||||
return mTotal;
|
||||
}
|
||||
|
||||
/** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */
|
||||
public int getMaxBg() {
|
||||
return mMaxBg;
|
||||
}
|
||||
|
||||
/**
|
||||
* We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any
|
||||
* pending, rather than always running the TOTAL number of FG jobs.
|
||||
*/
|
||||
public int getMinBg() {
|
||||
return mMinBg;
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw, String prefix) {
|
||||
pw.print(prefix);
|
||||
pw.print(mTotalKey);
|
||||
pw.print("=");
|
||||
pw.print(mTotal);
|
||||
pw.println();
|
||||
|
||||
pw.print(prefix);
|
||||
pw.print(mMaxBgKey);
|
||||
pw.print("=");
|
||||
pw.print(mMaxBg);
|
||||
pw.println();
|
||||
|
||||
pw.print(prefix);
|
||||
pw.print(mMinBgKey);
|
||||
pw.print("=");
|
||||
pw.print(mMinBg);
|
||||
pw.println();
|
||||
}
|
||||
|
||||
public void dumpProto(ProtoOutputStream proto, long fieldId) {
|
||||
final long token = proto.start(fieldId);
|
||||
proto.write(MaxJobCountsProto.TOTAL_JOBS, mTotal);
|
||||
proto.write(MaxJobCountsProto.MAX_BG, mMaxBg);
|
||||
proto.write(MaxJobCountsProto.MIN_BG, mMinBg);
|
||||
proto.end(token);
|
||||
}
|
||||
}
|
||||
|
||||
/** {@link MaxJobCounts} for each memory trim level. */
|
||||
static class MaxJobCountsPerMemoryTrimLevel {
|
||||
public final MaxJobCounts normal;
|
||||
public final MaxJobCounts moderate;
|
||||
public final MaxJobCounts low;
|
||||
public final MaxJobCounts critical;
|
||||
|
||||
MaxJobCountsPerMemoryTrimLevel(
|
||||
MaxJobCounts normal,
|
||||
MaxJobCounts moderate, MaxJobCounts low,
|
||||
MaxJobCounts critical) {
|
||||
this.normal = normal;
|
||||
this.moderate = moderate;
|
||||
this.low = low;
|
||||
this.critical = critical;
|
||||
}
|
||||
|
||||
public void dumpProto(ProtoOutputStream proto, long fieldId) {
|
||||
final long token = proto.start(fieldId);
|
||||
normal.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.NORMAL);
|
||||
moderate.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.MODERATE);
|
||||
low.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.LOW);
|
||||
critical.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.CRITICAL);
|
||||
proto.end(token);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All times are in milliseconds. Any access to this class or its fields should be done while
|
||||
* holding the JobSchedulerService.mLock lock.
|
||||
@@ -580,49 +466,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
*/
|
||||
float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
|
||||
|
||||
/** Prefix for all of the max_job constants. */
|
||||
private static final String KEY_PREFIX_MAX_JOB =
|
||||
JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY + "max_job_";
|
||||
|
||||
// Max job counts for screen on / off, for each memory trim level.
|
||||
final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON =
|
||||
new MaxJobCountsPerMemoryTrimLevel(
|
||||
new MaxJobCounts(
|
||||
8, KEY_PREFIX_MAX_JOB + "total_on_normal",
|
||||
6, KEY_PREFIX_MAX_JOB + "max_bg_on_normal",
|
||||
2, KEY_PREFIX_MAX_JOB + "min_bg_on_normal"),
|
||||
new MaxJobCounts(
|
||||
8, KEY_PREFIX_MAX_JOB + "total_on_moderate",
|
||||
4, KEY_PREFIX_MAX_JOB + "max_bg_on_moderate",
|
||||
2, KEY_PREFIX_MAX_JOB + "min_bg_on_moderate"),
|
||||
new MaxJobCounts(
|
||||
5, KEY_PREFIX_MAX_JOB + "total_on_low",
|
||||
1, KEY_PREFIX_MAX_JOB + "max_bg_on_low",
|
||||
1, KEY_PREFIX_MAX_JOB + "min_bg_on_low"),
|
||||
new MaxJobCounts(
|
||||
5, KEY_PREFIX_MAX_JOB + "total_on_critical",
|
||||
1, KEY_PREFIX_MAX_JOB + "max_bg_on_critical",
|
||||
1, KEY_PREFIX_MAX_JOB + "min_bg_on_critical"));
|
||||
|
||||
final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF =
|
||||
new MaxJobCountsPerMemoryTrimLevel(
|
||||
new MaxJobCounts(
|
||||
10, KEY_PREFIX_MAX_JOB + "total_off_normal",
|
||||
6, KEY_PREFIX_MAX_JOB + "max_bg_off_normal",
|
||||
2, KEY_PREFIX_MAX_JOB + "min_bg_off_normal"),
|
||||
new MaxJobCounts(
|
||||
10, KEY_PREFIX_MAX_JOB + "total_off_moderate",
|
||||
4, KEY_PREFIX_MAX_JOB + "max_bg_off_moderate",
|
||||
2, KEY_PREFIX_MAX_JOB + "min_bg_off_moderate"),
|
||||
new MaxJobCounts(
|
||||
5, KEY_PREFIX_MAX_JOB + "total_off_low",
|
||||
1, KEY_PREFIX_MAX_JOB + "max_bg_off_low",
|
||||
1, KEY_PREFIX_MAX_JOB + "min_bg_off_low"),
|
||||
new MaxJobCounts(
|
||||
5, KEY_PREFIX_MAX_JOB + "total_off_critical",
|
||||
1, KEY_PREFIX_MAX_JOB + "max_bg_off_critical",
|
||||
1, KEY_PREFIX_MAX_JOB + "min_bg_off_critical"));
|
||||
|
||||
/**
|
||||
* The minimum backoff time to allow for linear backoff.
|
||||
*/
|
||||
@@ -686,18 +529,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
DEFAULT_MODERATE_USE_FACTOR);
|
||||
}
|
||||
|
||||
void updateConcurrencyConstantsLocked() {
|
||||
MAX_JOB_COUNTS_SCREEN_ON.normal.update();
|
||||
MAX_JOB_COUNTS_SCREEN_ON.moderate.update();
|
||||
MAX_JOB_COUNTS_SCREEN_ON.low.update();
|
||||
MAX_JOB_COUNTS_SCREEN_ON.critical.update();
|
||||
|
||||
MAX_JOB_COUNTS_SCREEN_OFF.normal.update();
|
||||
MAX_JOB_COUNTS_SCREEN_OFF.moderate.update();
|
||||
MAX_JOB_COUNTS_SCREEN_OFF.low.update();
|
||||
MAX_JOB_COUNTS_SCREEN_OFF.critical.update();
|
||||
}
|
||||
|
||||
private void updateBackoffConstantsLocked() {
|
||||
MIN_LINEAR_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
|
||||
KEY_MIN_LINEAR_BACKOFF_TIME_MS,
|
||||
@@ -747,16 +578,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
pw.print(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
|
||||
pw.print(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println();
|
||||
|
||||
MAX_JOB_COUNTS_SCREEN_ON.normal.dump(pw, "");
|
||||
MAX_JOB_COUNTS_SCREEN_ON.moderate.dump(pw, "");
|
||||
MAX_JOB_COUNTS_SCREEN_ON.low.dump(pw, "");
|
||||
MAX_JOB_COUNTS_SCREEN_ON.critical.dump(pw, "");
|
||||
|
||||
MAX_JOB_COUNTS_SCREEN_OFF.normal.dump(pw, "");
|
||||
MAX_JOB_COUNTS_SCREEN_OFF.moderate.dump(pw, "");
|
||||
MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, "");
|
||||
MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, "");
|
||||
|
||||
pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println();
|
||||
pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
|
||||
pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
|
||||
@@ -781,9 +602,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR);
|
||||
proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR);
|
||||
|
||||
MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON);
|
||||
MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF);
|
||||
|
||||
proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS);
|
||||
proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS);
|
||||
proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.server.job;
|
||||
|
||||
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
|
||||
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
|
||||
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
||||
|
||||
@@ -124,9 +125,12 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
*
|
||||
* Any reads (dereferences) not done from the handler thread must be synchronized on
|
||||
* {@link #mLock}.
|
||||
* Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
|
||||
* Writes can only be done from the handler thread,
|
||||
* or {@link #executeRunnableJob(JobStatus, int)}.
|
||||
*/
|
||||
private JobStatus mRunningJob;
|
||||
@JobConcurrencyManager.WorkType
|
||||
private int mRunningJobWorkType;
|
||||
private JobCallback mRunningCallback;
|
||||
/** Used to store next job to run when current job is to be preempted. */
|
||||
private int mPreferredUid;
|
||||
@@ -181,30 +185,26 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
|
||||
JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
|
||||
JobPackageTracker tracker, Looper looper) {
|
||||
this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
|
||||
JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) {
|
||||
mContext = context;
|
||||
mLock = lock;
|
||||
mContext = service.getContext();
|
||||
mLock = service.getLock();
|
||||
mBatteryStats = batteryStats;
|
||||
mJobPackageTracker = tracker;
|
||||
mCallbackHandler = new JobServiceHandler(looper);
|
||||
mCompletedListener = completedListener;
|
||||
mCompletedListener = service;
|
||||
mAvailable = true;
|
||||
mVerb = VERB_FINISHED;
|
||||
mPreferredUid = NO_PREFERRED_UID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give a job to this context for execution. Callers must first check {@link #getRunningJobLocked()}
|
||||
* Give a job to this context for execution. Callers must first check {@link
|
||||
* #getRunningJobLocked()}
|
||||
* and ensure it is null to make sure this is a valid context.
|
||||
*
|
||||
* @param job The status of the job that we are going to run.
|
||||
* @return True if the job is valid and is running. False if the job cannot be executed.
|
||||
*/
|
||||
boolean executeRunnableJob(JobStatus job) {
|
||||
boolean executeRunnableJob(JobStatus job, @JobConcurrencyManager.WorkType int workType) {
|
||||
synchronized (mLock) {
|
||||
if (!mAvailable) {
|
||||
Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
|
||||
@@ -214,6 +214,7 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
mPreferredUid = NO_PREFERRED_UID;
|
||||
|
||||
mRunningJob = job;
|
||||
mRunningJobWorkType = workType;
|
||||
mRunningCallback = new JobCallback();
|
||||
final boolean isDeadlineExpired =
|
||||
job.hasDeadlineConstraint() &&
|
||||
@@ -282,6 +283,7 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
|
||||
}
|
||||
mRunningJob = null;
|
||||
mRunningJobWorkType = WORK_TYPE_NONE;
|
||||
mRunningCallback = null;
|
||||
mParams = null;
|
||||
mExecutionStartTimeElapsed = 0L;
|
||||
@@ -326,6 +328,11 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
return mRunningJob;
|
||||
}
|
||||
|
||||
@JobConcurrencyManager.WorkType
|
||||
int getRunningJobWorkType() {
|
||||
return mRunningJobWorkType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used only for debugging. Will return <code>"<null>"</code> if there is no job running.
|
||||
*/
|
||||
@@ -831,6 +838,7 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
mContext.unbindService(JobServiceContext.this);
|
||||
mWakeLock = null;
|
||||
mRunningJob = null;
|
||||
mRunningJobWorkType = WORK_TYPE_NONE;
|
||||
mRunningCallback = null;
|
||||
mParams = null;
|
||||
mVerb = VERB_FINISHED;
|
||||
|
||||
@@ -305,6 +305,7 @@ public final class JobStatus {
|
||||
public Network network;
|
||||
public ServiceInfo serviceInfo;
|
||||
|
||||
/** The evaluated priority of the job when it started running. */
|
||||
public int lastEvaluatedPriority;
|
||||
|
||||
// If non-null, this is work that has been enqueued for the job.
|
||||
|
||||
@@ -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 @@ public class JobCountTrackerTest {
|
||||
|
||||
|
||||
private void startPendingJobs(Jobs jobs, int totalMax, int maxBg, int minBg) {
|
||||
mJobCountTracker.reset(totalMax, maxBg, minBg);
|
||||
mWorkCountTracker.setConfig(new JobConcurrencyManager.WorkTypeConfig("critical",
|
||||
totalMax,
|
||||
// defaultMin
|
||||
List.of(Pair.create(WORK_TYPE_TOP, totalMax - maxBg),
|
||||
Pair.create(WORK_TYPE_BG, minBg)),
|
||||
// defaultMax
|
||||
List.of(Pair.create(WORK_TYPE_BG, maxBg))));
|
||||
mWorkCountTracker.resetCounts();
|
||||
|
||||
for (int i = 0; i < jobs.runningFg; i++) {
|
||||
mJobCountTracker.incrementRunningJobCount(true);
|
||||
mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_TOP);
|
||||
}
|
||||
for (int i = 0; i < jobs.runningBg; i++) {
|
||||
mJobCountTracker.incrementRunningJobCount(false);
|
||||
mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_BG);
|
||||
}
|
||||
|
||||
for (int i = 0; i < jobs.pendingFg; i++) {
|
||||
mJobCountTracker.incrementPendingJobCount(true);
|
||||
mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_TOP);
|
||||
}
|
||||
for (int i = 0; i < jobs.pendingBg; i++) {
|
||||
mJobCountTracker.incrementPendingJobCount(false);
|
||||
mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_BG);
|
||||
}
|
||||
|
||||
mJobCountTracker.onCountDone();
|
||||
mWorkCountTracker.onCountDone();
|
||||
|
||||
while ((jobs.pendingFg > 0 && mJobCountTracker.canJobStart(true))
|
||||
|| (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false))) {
|
||||
while ((jobs.pendingFg > 0
|
||||
&& mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE)
|
||||
|| (jobs.pendingBg > 0
|
||||
&& mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)) {
|
||||
final boolean isStartingFg = mRandom.nextBoolean();
|
||||
|
||||
if (isStartingFg) {
|
||||
if (jobs.pendingFg > 0 && mJobCountTracker.canJobStart(true)) {
|
||||
if (jobs.pendingFg > 0
|
||||
&& mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE) {
|
||||
jobs.pendingFg--;
|
||||
jobs.runningFg++;
|
||||
mJobCountTracker.onStartingNewJob(true);
|
||||
mWorkCountTracker.stageJob(WORK_TYPE_TOP);
|
||||
mWorkCountTracker.onJobStarted(WORK_TYPE_TOP);
|
||||
}
|
||||
} else {
|
||||
if (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false)) {
|
||||
if (jobs.pendingBg > 0
|
||||
&& mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE) {
|
||||
jobs.pendingBg--;
|
||||
jobs.runningBg++;
|
||||
mJobCountTracker.onStartingNewJob(false);
|
||||
mWorkCountTracker.stageJob(WORK_TYPE_BG);
|
||||
mWorkCountTracker.onJobStarted(WORK_TYPE_BG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "" + mJobCountTracker);
|
||||
Log.i(TAG, "" + mWorkCountTracker);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,6 +296,7 @@ public class JobCountTrackerTest {
|
||||
|
||||
startPendingJobs(jobs, totalMax, maxBg, minBg);
|
||||
|
||||
// fail(mWorkerCountTracker.toString());
|
||||
assertThat(jobs.runningFg).isEqualTo(resultRunningFg);
|
||||
assertThat(jobs.runningBg).isEqualTo(resultRunningBg);
|
||||
|
||||
@@ -300,6 +320,8 @@ public class JobCountTrackerTest {
|
||||
checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 1, /*res run/pen=*/ 5, 1, 5, 0);
|
||||
checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 4, 2, 6, 1);
|
||||
|
||||
checkSimple(8, 6, 2, /*run=*/ 0, 0, /*pen=*/ 0, 49, /*res run/pen=*/ 0, 6, 0, 43);
|
||||
|
||||
checkSimple(6, 4, 2, /*run=*/ 6, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 6, 0, 10, 3);
|
||||
}
|
||||
}
|
||||
@@ -15,22 +15,34 @@
|
||||
*/
|
||||
package com.android.server.job;
|
||||
|
||||
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
|
||||
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.job.JobSchedulerService.MaxJobCounts;
|
||||
import com.android.server.job.JobConcurrencyManager.WorkTypeConfig;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class MaxJobCountsTest {
|
||||
public class WorkTypeConfigTest {
|
||||
private static final String KEY_MAX_TOTAL = "concurrency_max_total_test";
|
||||
private static final String KEY_MAX_TOP = "concurrency_max_top_test";
|
||||
private static final String KEY_MAX_BG = "concurrency_max_bg_test";
|
||||
private static final String KEY_MIN_TOP = "concurrency_min_top_test";
|
||||
private static final String KEY_MIN_BG = "concurrency_min_bg_test";
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
resetConfig();
|
||||
@@ -38,9 +50,11 @@ public class MaxJobCountsTest {
|
||||
|
||||
private void resetConfig() {
|
||||
// DeviceConfig.resetToDefaults() doesn't work here. Need to reset constants manually.
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "total", "", false);
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "maxbg", "", false);
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "minbg", "", false);
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, "", false);
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, "", false);
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, "", false);
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, "", false);
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, "", false);
|
||||
}
|
||||
|
||||
private void check(@Nullable DeviceConfig.Properties config,
|
||||
@@ -51,16 +65,19 @@ public class MaxJobCountsTest {
|
||||
DeviceConfig.setProperties(config);
|
||||
}
|
||||
|
||||
final MaxJobCounts counts = new JobSchedulerService.MaxJobCounts(
|
||||
defaultTotal, "total",
|
||||
defaultMaxBg, "maxbg",
|
||||
defaultMinBg, "minbg");
|
||||
final WorkTypeConfig counts = new WorkTypeConfig("test",
|
||||
defaultTotal,
|
||||
// defaultMin
|
||||
List.of(Pair.create(WORK_TYPE_TOP, defaultTotal - defaultMaxBg),
|
||||
Pair.create(WORK_TYPE_BG, defaultMinBg)),
|
||||
// defaultMax
|
||||
List.of(Pair.create(WORK_TYPE_BG, defaultMaxBg)));
|
||||
|
||||
counts.update();
|
||||
counts.update(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER));
|
||||
|
||||
Assert.assertEquals(expectedTotal, counts.getMaxTotal());
|
||||
Assert.assertEquals(expectedMaxBg, counts.getMaxBg());
|
||||
Assert.assertEquals(expectedMinBg, counts.getMinBg());
|
||||
Assert.assertEquals(expectedMaxBg, counts.getMax(WORK_TYPE_BG));
|
||||
Assert.assertEquals(expectedMinBg, counts.getMinReserved(WORK_TYPE_BG));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,19 +97,19 @@ public class MaxJobCountsTest {
|
||||
|
||||
// Test for overriding with a setting string.
|
||||
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
|
||||
.setInt("total", 5)
|
||||
.setInt("maxbg", 4)
|
||||
.setInt("minbg", 3)
|
||||
.setInt(KEY_MAX_TOTAL, 5)
|
||||
.setInt(KEY_MAX_BG, 4)
|
||||
.setInt(KEY_MIN_BG, 3)
|
||||
.build(),
|
||||
/*default*/ 9, 9, 9, /*expected*/ 5, 4, 3);
|
||||
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
|
||||
.setInt("total", 5).build(),
|
||||
.setInt(KEY_MAX_TOTAL, 5).build(),
|
||||
/*default*/ 9, 9, 9, /*expected*/ 5, 5, 4);
|
||||
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
|
||||
.setInt("maxbg", 4).build(),
|
||||
.setInt(KEY_MAX_BG, 4).build(),
|
||||
/*default*/ 9, 9, 9, /*expected*/ 9, 4, 4);
|
||||
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
|
||||
.setInt("minbg", 3).build(),
|
||||
.setInt(KEY_MIN_BG, 3).build(),
|
||||
/*default*/ 9, 9, 9, /*expected*/ 9, 9, 3);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user