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

This commit is contained in:
Kweku Adams
2021-01-29 18:31:19 +00:00
committed by Android (Google) Code Review
6 changed files with 537 additions and 474 deletions

View File

@@ -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();
}
}
}

View File

@@ -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);

View File

@@ -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>"&lt;null&gt;"</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;

View File

@@ -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.

View File

@@ -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);
}
}

View File

@@ -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);
}
}