Merge "Add concurrency restriction at the USER level" into sc-dev

This commit is contained in:
Kweku Adams
2021-02-08 23:53:04 +00:00
committed by Android (Google) Code Review
8 changed files with 774 additions and 85 deletions

View File

@@ -16,33 +16,43 @@
package com.android.server.job; package com.android.server.job;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.UserSwitchObserver;
import android.app.job.JobInfo; import android.app.job.JobInfo;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.UserInfo;
import android.os.Handler; import android.os.Handler;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter;
import android.util.Pair; import android.util.Pair;
import android.util.Slog; import android.util.Slog;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TimeUtils; import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.procstats.ProcessStats; import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.util.StatLogger; import com.android.internal.util.StatLogger;
import com.android.server.JobSchedulerBackgroundThread; import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
import com.android.server.job.controllers.JobStatus; import com.android.server.job.controllers.JobStatus;
import com.android.server.job.controllers.StateController; import com.android.server.job.controllers.StateController;
import com.android.server.pm.UserManagerInternal;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@@ -66,12 +76,14 @@ class JobConcurrencyManager {
static final int WORK_TYPE_NONE = 0; static final int WORK_TYPE_NONE = 0;
static final int WORK_TYPE_TOP = 1 << 0; static final int WORK_TYPE_TOP = 1 << 0;
static final int WORK_TYPE_BG = 1 << 1; static final int WORK_TYPE_BG = 1 << 1;
private static final int NUM_WORK_TYPES = 2; static final int WORK_TYPE_BGUSER = 1 << 2;
private static final int NUM_WORK_TYPES = 3;
@IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = { @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
WORK_TYPE_NONE, WORK_TYPE_NONE,
WORK_TYPE_TOP, WORK_TYPE_TOP,
WORK_TYPE_BG WORK_TYPE_BG,
WORK_TYPE_BGUSER
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface WorkType { public @interface WorkType {
@@ -98,22 +110,26 @@ class JobConcurrencyManager {
// defaultMin // defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 2)), List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 2)),
// defaultMax // defaultMax
List.of(Pair.create(WORK_TYPE_BG, 6))), List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4))
),
new WorkTypeConfig("screen_on_moderate", 8, new WorkTypeConfig("screen_on_moderate", 8,
// defaultMin // defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)), List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
// defaultMax // defaultMax
List.of(Pair.create(WORK_TYPE_BG, 4))), List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2))
),
new WorkTypeConfig("screen_on_low", 5, new WorkTypeConfig("screen_on_low", 5,
// defaultMin // defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)), List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
// defaultMax // defaultMax
List.of(Pair.create(WORK_TYPE_BG, 1))), List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
),
new WorkTypeConfig("screen_on_critical", 5, new WorkTypeConfig("screen_on_critical", 5,
// defaultMin // defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)), List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
// defaultMax // defaultMax
List.of(Pair.create(WORK_TYPE_BG, 1))) List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
)
); );
private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF = private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF =
new WorkConfigLimitsPerMemoryTrimLevel( new WorkConfigLimitsPerMemoryTrimLevel(
@@ -121,22 +137,26 @@ class JobConcurrencyManager {
// defaultMin // defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)), List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
// defaultMax // defaultMax
List.of(Pair.create(WORK_TYPE_BG, 6))), List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4))
),
new WorkTypeConfig("screen_off_moderate", 10, new WorkTypeConfig("screen_off_moderate", 10,
// defaultMin // defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)), List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
// defaultMax // defaultMax
List.of(Pair.create(WORK_TYPE_BG, 4))), List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2))
),
new WorkTypeConfig("screen_off_low", 5, new WorkTypeConfig("screen_off_low", 5,
// defaultMin // defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)), List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
// defaultMax // defaultMax
List.of(Pair.create(WORK_TYPE_BG, 1))), List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
),
new WorkTypeConfig("screen_off_critical", 5, new WorkTypeConfig("screen_off_critical", 5,
// defaultMin // defaultMin
List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)), List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
// defaultMax // defaultMax
List.of(Pair.create(WORK_TYPE_BG, 1))) List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
)
); );
/** /**
@@ -171,6 +191,10 @@ class JobConcurrencyManager {
"assignJobsToContexts", "assignJobsToContexts",
"refreshSystemState", "refreshSystemState",
}); });
@VisibleForTesting
GracePeriodObserver mGracePeriodObserver;
@VisibleForTesting
boolean mShouldRestrictBgUser;
interface Stats { interface Stats {
int ASSIGN_JOBS_TO_CONTEXTS = 0; int ASSIGN_JOBS_TO_CONTEXTS = 0;
@@ -182,9 +206,13 @@ class JobConcurrencyManager {
JobConcurrencyManager(JobSchedulerService service) { JobConcurrencyManager(JobSchedulerService service) {
mService = service; mService = service;
mLock = mService.mLock; mLock = mService.mLock;
mContext = service.getContext(); mContext = service.getTestableContext();
mHandler = JobSchedulerBackgroundThread.getHandler(); mHandler = JobSchedulerBackgroundThread.getHandler();
mGracePeriodObserver = new GracePeriodObserver(mContext);
mShouldRestrictBgUser = mContext.getResources().getBoolean(
R.bool.config_jobSchedulerRestrictBackgroundUser);
} }
public void onSystemReady() { public void onSystemReady() {
@@ -193,10 +221,18 @@ class JobConcurrencyManager {
final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mReceiver, filter); mContext.registerReceiver(mReceiver, filter);
try {
ActivityManager.getService().registerUserSwitchObserver(mGracePeriodObserver, TAG);
} catch (RemoteException e) {
}
onInteractiveStateChanged(mPowerManager.isInteractive()); onInteractiveStateChanged(mPowerManager.isInteractive());
} }
void onUserRemoved(int userId) {
mGracePeriodObserver.onUserRemoved(userId);
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@@ -224,7 +260,7 @@ class JobConcurrencyManager {
Slog.d(TAG, "Interactive: " + interactive); Slog.d(TAG, "Interactive: " + interactive);
} }
final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis(); final long nowRealtime = sElapsedRealtimeClock.millis();
if (interactive) { if (interactive) {
mLastScreenOnRealtime = nowRealtime; mLastScreenOnRealtime = nowRealtime;
mEffectiveInteractiveState = true; mEffectiveInteractiveState = true;
@@ -261,7 +297,7 @@ class JobConcurrencyManager {
if (mLastScreenOnRealtime > mLastScreenOffRealtime) { if (mLastScreenOnRealtime > mLastScreenOffRealtime) {
return; return;
} }
final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); final long now = sElapsedRealtimeClock.millis();
if ((mLastScreenOffRealtime + mScreenOffAdjustmentDelayMs) > now) { if ((mLastScreenOffRealtime + mScreenOffAdjustmentDelayMs) > now) {
return; return;
} }
@@ -723,6 +759,10 @@ class JobConcurrencyManager {
pw.print(mLastMemoryTrimLevel); pw.print(mLastMemoryTrimLevel);
pw.println(); pw.println();
pw.print("User Grace Period: ");
pw.print(mGracePeriodObserver.mGracePeriodExpiration);
pw.println();
mStatLogger.dump(pw); mStatLogger.dump(pw);
} finally { } finally {
pw.decreaseIndent(); pw.decreaseIndent();
@@ -748,14 +788,44 @@ class JobConcurrencyManager {
proto.end(token); proto.end(token);
} }
/**
* Decides whether a job is from the current foreground user or the equivalent.
*/
@VisibleForTesting
boolean shouldRunAsFgUserJob(JobStatus job) {
if (!mShouldRestrictBgUser) return true;
int userId = job.getSourceUserId();
UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
UserInfo userInfo = um.getUserInfo(userId);
// If the user has a parent user (e.g. a work profile of another user), the user should be
// treated equivalent as its parent user.
if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
&& userInfo.profileGroupId != userId) {
userId = userInfo.profileGroupId;
userInfo = um.getUserInfo(userId);
}
int currentUser = LocalServices.getService(ActivityManagerInternal.class)
.getCurrentUserId();
// A user is treated as foreground user if any of the followings is true:
// 1. The user is current user
// 2. The user is primary user
// 3. The user's grace period has not expired
return currentUser == userId || userInfo.isPrimary()
|| mGracePeriodObserver.isWithinGracePeriodForUser(userId);
}
int getJobWorkTypes(@NonNull JobStatus js) { int getJobWorkTypes(@NonNull JobStatus js) {
int classification = 0; int classification = 0;
// TODO(171305774): create dedicated work type for EJ and FGS // TODO(171305774): create dedicated work type for EJ and FGS
if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP
|| js.shouldTreatAsExpeditedJob()) { || js.shouldTreatAsExpeditedJob()) {
classification |= WORK_TYPE_TOP; classification |= WORK_TYPE_TOP;
} else { } else if (shouldRunAsFgUserJob(js)) {
classification |= WORK_TYPE_BG; classification |= WORK_TYPE_BG;
} else {
classification |= WORK_TYPE_BGUSER;
} }
return classification; return classification;
} }
@@ -766,8 +836,12 @@ class JobConcurrencyManager {
CONFIG_KEY_PREFIX_CONCURRENCY + "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_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_MAX_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_";
private static final String KEY_PREFIX_MAX_BGUSER =
CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_";
private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_"; 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 static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_";
private static final String KEY_PREFIX_MIN_BGUSER =
CONFIG_KEY_PREFIX_CONCURRENCY + "min_bguser_";
private final String mConfigIdentifier; private final String mConfigIdentifier;
private int mMaxTotal; private int mMaxTotal;
@@ -815,6 +889,10 @@ class JobConcurrencyManager {
properties.getInt(KEY_PREFIX_MAX_BG + mConfigIdentifier, properties.getInt(KEY_PREFIX_MAX_BG + mConfigIdentifier,
mDefaultMaxAllowedSlots.get(WORK_TYPE_BG, mMaxTotal)))); mDefaultMaxAllowedSlots.get(WORK_TYPE_BG, mMaxTotal))));
mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg); mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg);
final int maxBgUser = Math.max(1, Math.min(mMaxTotal,
properties.getInt(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier,
mDefaultMaxAllowedSlots.get(WORK_TYPE_BGUSER, mMaxTotal))));
mMaxAllowedSlots.put(WORK_TYPE_BGUSER, maxBgUser);
int remaining = mMaxTotal; int remaining = mMaxTotal;
mMinReservedSlots.clear(); mMinReservedSlots.clear();
@@ -829,6 +907,12 @@ class JobConcurrencyManager {
properties.getInt(KEY_PREFIX_MIN_BG + mConfigIdentifier, properties.getInt(KEY_PREFIX_MIN_BG + mConfigIdentifier,
mDefaultMinReservedSlots.get(WORK_TYPE_BG)))); mDefaultMinReservedSlots.get(WORK_TYPE_BG))));
mMinReservedSlots.put(WORK_TYPE_BG, minBg); mMinReservedSlots.put(WORK_TYPE_BG, minBg);
remaining -= minBg;
// Ensure bg user is in the range [0, min(maxBgUser, remaining)]
final int minBgUser = Math.max(0, Math.min(Math.min(maxBgUser, remaining),
properties.getInt(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier,
mDefaultMinReservedSlots.get(WORK_TYPE_BGUSER, 0))));
mMinReservedSlots.put(WORK_TYPE_BGUSER, minBgUser);
} }
int getMaxTotal() { int getMaxTotal() {
@@ -853,6 +937,10 @@ class JobConcurrencyManager {
.println(); .println();
pw.print(KEY_PREFIX_MAX_BG + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_BG)) pw.print(KEY_PREFIX_MAX_BG + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_BG))
.println(); .println();
pw.print(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier,
mMinReservedSlots.get(WORK_TYPE_BGUSER)).println();
pw.print(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier,
mMaxAllowedSlots.get(WORK_TYPE_BGUSER)).println();
} }
} }
@@ -872,6 +960,58 @@ class JobConcurrencyManager {
} }
} }
/**
* This class keeps the track of when a user's grace period expires.
*/
@VisibleForTesting
static class GracePeriodObserver extends UserSwitchObserver {
// Key is UserId and Value is the time when grace period expires
@VisibleForTesting
final SparseLongArray mGracePeriodExpiration = new SparseLongArray();
private int mCurrentUserId;
@VisibleForTesting
int mGracePeriod;
private final UserManagerInternal mUserManagerInternal;
final Object mLock = new Object();
GracePeriodObserver(Context context) {
mCurrentUserId = LocalServices.getService(ActivityManagerInternal.class)
.getCurrentUserId();
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mGracePeriod = Math.max(0, context.getResources().getInteger(
R.integer.config_jobSchedulerUserGracePeriod));
}
@Override
public void onUserSwitchComplete(int newUserId) {
final long expiration = sElapsedRealtimeClock.millis() + mGracePeriod;
synchronized (mLock) {
if (mCurrentUserId != UserHandle.USER_NULL
&& mUserManagerInternal.exists(mCurrentUserId)) {
mGracePeriodExpiration.append(mCurrentUserId, expiration);
}
mGracePeriodExpiration.delete(newUserId);
mCurrentUserId = newUserId;
}
}
void onUserRemoved(int userId) {
synchronized (mLock) {
mGracePeriodExpiration.delete(userId);
}
}
@VisibleForTesting
public boolean isWithinGracePeriodForUser(int userId) {
synchronized (mLock) {
return userId == mCurrentUserId
|| sElapsedRealtimeClock.millis()
< mGracePeriodExpiration.get(userId, Long.MAX_VALUE);
}
}
}
/** /**
* This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs * This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs
* are running/pending, how many more job can start. * are running/pending, how many more job can start.
@@ -900,12 +1040,16 @@ class JobConcurrencyManager {
mConfigNumReservedSlots.put(WORK_TYPE_TOP, mConfigNumReservedSlots.put(WORK_TYPE_TOP,
workTypeConfig.getMinReserved(WORK_TYPE_TOP)); workTypeConfig.getMinReserved(WORK_TYPE_TOP));
mConfigNumReservedSlots.put(WORK_TYPE_BG, workTypeConfig.getMinReserved(WORK_TYPE_BG)); mConfigNumReservedSlots.put(WORK_TYPE_BG, workTypeConfig.getMinReserved(WORK_TYPE_BG));
mConfigNumReservedSlots.put(WORK_TYPE_BGUSER,
workTypeConfig.getMinReserved(WORK_TYPE_BGUSER));
mConfigAbsoluteMaxSlots.put(WORK_TYPE_TOP, workTypeConfig.getMax(WORK_TYPE_TOP)); mConfigAbsoluteMaxSlots.put(WORK_TYPE_TOP, workTypeConfig.getMax(WORK_TYPE_TOP));
mConfigAbsoluteMaxSlots.put(WORK_TYPE_BG, workTypeConfig.getMax(WORK_TYPE_BG)); mConfigAbsoluteMaxSlots.put(WORK_TYPE_BG, workTypeConfig.getMax(WORK_TYPE_BG));
mConfigAbsoluteMaxSlots.put(WORK_TYPE_BGUSER, workTypeConfig.getMax(WORK_TYPE_BGUSER));
mNumUnspecialized = mConfigMaxTotal; mNumUnspecialized = mConfigMaxTotal;
mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_TOP); mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_TOP);
mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BG); mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BG);
mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BGUSER);
mNumUnspecializedRemaining = mConfigMaxTotal; mNumUnspecializedRemaining = mConfigMaxTotal;
for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) { for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i), mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i),
@@ -937,6 +1081,9 @@ class JobConcurrencyManager {
if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) { if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + 1); mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + 1);
} }
if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + 1);
}
} }
void stageJob(@WorkType int workType) { void stageJob(@WorkType int workType) {
@@ -1029,6 +1176,11 @@ class JobConcurrencyManager {
int resBg = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BG), numBg); int resBg = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BG), numBg);
mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg); mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg);
mNumUnspecialized -= resBg; mNumUnspecialized -= resBg;
final int numBgUser = mNumRunningJobs.get(WORK_TYPE_BGUSER)
+ mNumPendingJobs.get(WORK_TYPE_BGUSER);
int resBgUser = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BGUSER), numBgUser);
mNumActuallyReservedSlots.put(WORK_TYPE_BGUSER, resBgUser);
mNumUnspecialized -= resBgUser;
mNumUnspecializedRemaining = mNumUnspecialized; mNumUnspecializedRemaining = mNumUnspecialized;
// Account for already running jobs after we've assigned the minimum number of slots. // Account for already running jobs after we've assigned the minimum number of slots.
@@ -1048,6 +1200,14 @@ class JobConcurrencyManager {
resBg += unspecializedAssigned; resBg += unspecializedAssigned;
mNumUnspecializedRemaining -= extraRunning; mNumUnspecializedRemaining -= extraRunning;
} }
extraRunning = (mNumRunningJobs.get(WORK_TYPE_BGUSER) - resBgUser);
if (extraRunning > 0) {
unspecializedAssigned = Math.max(0,
Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER) - resBgUser,
extraRunning));
resBgUser += unspecializedAssigned;
mNumUnspecializedRemaining -= extraRunning;
}
// Assign remaining unspecialized based on ranking. // Assign remaining unspecialized based on ranking.
unspecializedAssigned = Math.max(0, unspecializedAssigned = Math.max(0,
@@ -1060,6 +1220,12 @@ class JobConcurrencyManager {
Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), numBg) - resBg)); Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), numBg) - resBg));
mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned); mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned);
mNumUnspecializedRemaining -= unspecializedAssigned; mNumUnspecializedRemaining -= unspecializedAssigned;
unspecializedAssigned = Math.max(0,
Math.min(mNumUnspecializedRemaining,
Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER), numBgUser)
- resBgUser));
mNumActuallyReservedSlots.put(WORK_TYPE_BGUSER, resBgUser + unspecializedAssigned);
mNumUnspecializedRemaining -= unspecializedAssigned;
} }
int canJobStart(int workTypes) { int canJobStart(int workTypes) {
@@ -1081,6 +1247,16 @@ class JobConcurrencyManager {
return WORK_TYPE_BG; return WORK_TYPE_BG;
} }
} }
if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
final int maxAllowed = Math.min(
mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER),
mNumActuallyReservedSlots.get(WORK_TYPE_BGUSER)
+ mNumUnspecializedRemaining);
if (mNumRunningJobs.get(WORK_TYPE_BGUSER) + mNumStartingJobs.get(WORK_TYPE_BGUSER)
< maxAllowed) {
return WORK_TYPE_BGUSER;
}
}
return WORK_TYPE_NONE; return WORK_TYPE_NONE;
} }

View File

@@ -743,6 +743,7 @@ public class JobSchedulerService extends com.android.server.SystemService
mControllers.get(c).onUserRemovedLocked(userId); mControllers.get(c).onUserRemovedLocked(userId);
} }
} }
mConcurrencyManager.onUserRemoved(userId);
} else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) { } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
// Has this package scheduled any jobs, such that we will take action // Has this package scheduled any jobs, such that we will take action
// if it were to be force-stopped? // if it were to be force-stopped?

View File

@@ -3477,6 +3477,11 @@
<!-- The alarm window (in milliseconds) that JobScheduler uses to enter the idle state --> <!-- The alarm window (in milliseconds) that JobScheduler uses to enter the idle state -->
<integer name="config_jobSchedulerIdleWindowSlop">300000</integer> <integer name="config_jobSchedulerIdleWindowSlop">300000</integer>
<!-- If true, jobs from background user will be restricted -->
<bool name="config_jobSchedulerRestrictBackgroundUser">false</bool>
<!-- The length of grace period after user becomes background user -->
<integer name="config_jobSchedulerUserGracePeriod">60000</integer>
<!-- If true, all guest users created on the device will be ephemeral. --> <!-- If true, all guest users created on the device will be ephemeral. -->
<bool name="config_guestUserEphemeral">false</bool> <bool name="config_guestUserEphemeral">false</bool>

View File

@@ -2680,6 +2680,8 @@
<java-symbol type="integer" name="config_jobSchedulerInactivityIdleThreshold" /> <java-symbol type="integer" name="config_jobSchedulerInactivityIdleThreshold" />
<java-symbol type="integer" name="config_jobSchedulerIdleWindowSlop" /> <java-symbol type="integer" name="config_jobSchedulerIdleWindowSlop" />
<java-symbol type="bool" name="config_jobSchedulerRestrictBackgroundUser" />
<java-symbol type="integer" name="config_jobSchedulerUserGracePeriod" />
<java-symbol type="style" name="Animation.ImmersiveModeConfirmation" /> <java-symbol type="style" name="Animation.ImmersiveModeConfirmation" />

View File

@@ -0,0 +1,188 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.job;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.ActivityManagerInternal;
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.os.UserHandle;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.server.LocalServices;
import com.android.server.job.JobConcurrencyManager.GracePeriodObserver;
import com.android.server.job.controllers.JobStatus;
import com.android.server.pm.UserManagerInternal;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public final class JobConcurrencyManagerTest {
private static final int UNAVAILABLE_USER = 0;
private JobConcurrencyManager mJobConcurrencyManager;
private UserManagerInternal mUserManagerInternal;
private ActivityManagerInternal mActivityManagerInternal;
private int mNextUserId;
private GracePeriodObserver mGracePeriodObserver;
private Context mContext;
private Resources mResources;
@BeforeClass
public static void setUpOnce() {
LocalServices.addService(UserManagerInternal.class, mock(UserManagerInternal.class));
LocalServices.addService(
ActivityManagerInternal.class, mock(ActivityManagerInternal.class));
}
@AfterClass
public static void tearDownOnce() {
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
}
@Before
public void setUp() {
final JobSchedulerService jobSchedulerService = mock(JobSchedulerService.class);
mContext = mock(Context.class);
mResources = mock(Resources.class);
doReturn(true).when(mResources).getBoolean(
R.bool.config_jobSchedulerRestrictBackgroundUser);
when(mContext.getResources()).thenReturn(mResources);
doReturn(mContext).when(jobSchedulerService).getTestableContext();
mJobConcurrencyManager = new JobConcurrencyManager(jobSchedulerService);
mGracePeriodObserver = mock(GracePeriodObserver.class);
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mNextUserId = 10;
mJobConcurrencyManager.mGracePeriodObserver = mGracePeriodObserver;
}
@Test
public void testShouldRunAsFgUserJob_currentUser() {
assertTrue(mJobConcurrencyManager.shouldRunAsFgUserJob(
createJob(createCurrentUser(false))));
}
@Test
public void testShouldRunAsFgUserJob_currentProfile() {
assertTrue(mJobConcurrencyManager.shouldRunAsFgUserJob(
createJob(createCurrentUser(true))));
}
@Test
public void testShouldRunAsFgUserJob_primaryUser() {
assertTrue(mJobConcurrencyManager.shouldRunAsFgUserJob(
createJob(createPrimaryUser(false))));
}
@Test
public void testShouldRunAsFgUserJob_primaryProfile() {
assertTrue(mJobConcurrencyManager.shouldRunAsFgUserJob(
createJob(createPrimaryUser(true))));
}
@Test
public void testShouldRunAsFgUserJob_UnexpiredUser() {
assertTrue(mJobConcurrencyManager.shouldRunAsFgUserJob(
createJob(createUnexpiredUser(false))));
}
@Test
public void testShouldRunAsFgUserJob_UnexpiredProfile() {
assertTrue(mJobConcurrencyManager.shouldRunAsFgUserJob(
createJob(createUnexpiredUser(true))));
}
@Test
public void testShouldRunAsFgUserJob_restrictedUser() {
assertFalse(mJobConcurrencyManager.shouldRunAsFgUserJob(
createJob(createRestrictedUser(false))));
}
@Test
public void testShouldRunAsFgUserJob_restrictedProfile() {
assertFalse(mJobConcurrencyManager.shouldRunAsFgUserJob(
createJob(createRestrictedUser(true))));
}
private UserInfo createCurrentUser(boolean isProfile) {
final UserInfo ui = createNewUser();
doReturn(ui.id).when(mActivityManagerInternal).getCurrentUserId();
return isProfile ? createNewProfile(ui) : ui;
}
private UserInfo createPrimaryUser(boolean isProfile) {
final UserInfo ui = createNewUser();
doReturn(true).when(ui).isPrimary();
return isProfile ? createNewProfile(ui) : ui;
}
private UserInfo createUnexpiredUser(boolean isProfile) {
final UserInfo ui = createNewUser();
doReturn(true).when(mGracePeriodObserver).isWithinGracePeriodForUser(ui.id);
return isProfile ? createNewProfile(ui) : ui;
}
private UserInfo createRestrictedUser(boolean isProfile) {
final UserInfo ui = createNewUser();
doReturn(UNAVAILABLE_USER).when(mActivityManagerInternal).getCurrentUserId();
doReturn(false).when(ui).isPrimary();
doReturn(false).when(mGracePeriodObserver).isWithinGracePeriodForUser(ui.id);
return isProfile ? createNewProfile(ui) : ui;
}
private UserInfo createNewProfile(UserInfo parent) {
final UserInfo ui = createNewUser();
parent.profileGroupId = parent.id;
ui.profileGroupId = parent.id;
doReturn(true).when(ui).isProfile();
return ui;
}
private UserInfo createNewUser() {
final UserInfo ui = mock(UserInfo.class);
ui.id = mNextUserId++;
doReturn(ui).when(mUserManagerInternal).getUserInfo(ui.id);
ui.profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
return ui;
}
private static JobStatus createJob(UserInfo userInfo) {
JobStatus jobStatus = JobStatus.createFromJobInfo(
new JobInfo.Builder(1, new ComponentName("foo", "bar")).build(),
userInfo.id * UserHandle.PER_USER_RANGE,
null, userInfo.id, "JobConcurrencyManagerTest");
return jobStatus;
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.job;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import android.app.ActivityManagerInternal;
import android.content.Context;
import android.os.RemoteException;
import android.os.SystemClock;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
import com.android.server.job.JobConcurrencyManager.GracePeriodObserver;
import com.android.server.pm.UserManagerInternal;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Clock;
import java.time.Duration;
import java.time.ZoneOffset;
@RunWith(AndroidJUnit4.class)
@SmallTest
public final class GracePeriodObserverTest {
private GracePeriodObserver mGracePeriodObserver;
private UserManagerInternal mUserManagerInternal;
private static final int FIRST_USER = 0;
@BeforeClass
public static void setUpOnce() {
UserManagerInternal userManagerInternal = mock(UserManagerInternal.class);
LocalServices.addService(UserManagerInternal.class, userManagerInternal);
ActivityManagerInternal activityManagerInternal = mock(ActivityManagerInternal.class);
LocalServices.addService(ActivityManagerInternal.class, activityManagerInternal);
}
@AfterClass
public static void tearDownOnce() {
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
}
@Before
public void setUp() {
final Context context = ApplicationProvider.getApplicationContext();
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
doReturn(FIRST_USER)
.when(LocalServices.getService(ActivityManagerInternal.class)).getCurrentUserId();
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
doReturn(true).when(mUserManagerInternal).exists(FIRST_USER);
mGracePeriodObserver = new GracePeriodObserver(context);
}
@Test
public void testGracePeriod() throws RemoteException {
final int oldUser = FIRST_USER;
final int newUser = 10;
doReturn(true).when(mUserManagerInternal).exists(newUser);
mGracePeriodObserver.onUserSwitchComplete(newUser);
assertTrue(mGracePeriodObserver.isWithinGracePeriodForUser(oldUser));
JobSchedulerService.sElapsedRealtimeClock =
Clock.offset(JobSchedulerService.sElapsedRealtimeClock,
Duration.ofMillis(mGracePeriodObserver.mGracePeriod));
assertFalse(mGracePeriodObserver.isWithinGracePeriodForUser(oldUser));
}
@Test
public void testCleanUp() throws RemoteException {
final int removedUser = FIRST_USER;
final int newUser = 10;
mGracePeriodObserver.onUserSwitchComplete(newUser);
final int sizeBefore = mGracePeriodObserver.mGracePeriodExpiration.size();
doReturn(false).when(mUserManagerInternal).exists(removedUser);
mGracePeriodObserver.onUserRemoved(removedUser);
assertEquals(sizeBefore - 1, mGracePeriodObserver.mGracePeriodExpiration.size());
}
}

View File

@@ -17,6 +17,7 @@
package com.android.server.job; package com.android.server.job;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
@@ -63,17 +64,27 @@ public class WorkCountTrackerTest {
public final SparseIntArray running = new SparseIntArray(); public final SparseIntArray running = new SparseIntArray();
public final SparseIntArray pending = new SparseIntArray(); public final SparseIntArray pending = new SparseIntArray();
public void maybeEnqueueJobs(double startRatio, double fgJobRatio) { public void maybeEnqueueJobs(double startRatio, double fgJobRatio, double fgUserJobRatio) {
// fgUserJobRatio should always be at least fgJobRatio, otherwise no WORK_TYPE_BG will
// be enqueued.
while (mRandom.nextDouble() < startRatio) { while (mRandom.nextDouble() < startRatio) {
if (mRandom.nextDouble() < fgJobRatio) { final double random = mRandom.nextDouble();
if (random < fgJobRatio) {
pending.put(WORK_TYPE_TOP, pending.get(WORK_TYPE_TOP) + 1); pending.put(WORK_TYPE_TOP, pending.get(WORK_TYPE_TOP) + 1);
} else { } else if (random < fgUserJobRatio) {
pending.put(WORK_TYPE_BG, pending.get(WORK_TYPE_BG) + 1); pending.put(WORK_TYPE_BG, pending.get(WORK_TYPE_BG) + 1);
} else {
pending.put(WORK_TYPE_BGUSER, pending.get(WORK_TYPE_BGUSER) + 1);
} }
} }
} }
public void maybeFinishJobs(double stopRatio) { public void maybeFinishJobs(double stopRatio) {
for (int i = running.get(WORK_TYPE_BGUSER); i > 0; i--) {
if (mRandom.nextDouble() < stopRatio) {
running.put(WORK_TYPE_BGUSER, running.get(WORK_TYPE_BGUSER) - 1);
}
}
for (int i = running.get(WORK_TYPE_BG); i > 0; i--) { for (int i = running.get(WORK_TYPE_BG); i > 0; i--) {
if (mRandom.nextDouble() < stopRatio) { if (mRandom.nextDouble() < stopRatio) {
running.put(WORK_TYPE_BG, running.get(WORK_TYPE_BG) - 1); running.put(WORK_TYPE_BG, running.get(WORK_TYPE_BG) - 1);
@@ -120,8 +131,11 @@ public class WorkCountTrackerTest {
while ((jobs.pending.get(WORK_TYPE_TOP) > 0 while ((jobs.pending.get(WORK_TYPE_TOP) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE) && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE)
|| (jobs.pending.get(WORK_TYPE_BG) > 0 || (jobs.pending.get(WORK_TYPE_BG) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)) { && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)
|| (jobs.pending.get(WORK_TYPE_BGUSER) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_BGUSER) != WORK_TYPE_NONE)) {
final boolean isStartingFg = mRandom.nextBoolean(); final boolean isStartingFg = mRandom.nextBoolean();
final boolean isStartingFgUser = mRandom.nextBoolean();
if (isStartingFg) { if (isStartingFg) {
if (jobs.pending.get(WORK_TYPE_TOP) > 0 if (jobs.pending.get(WORK_TYPE_TOP) > 0
@@ -131,7 +145,7 @@ public class WorkCountTrackerTest {
mWorkCountTracker.stageJob(WORK_TYPE_TOP); mWorkCountTracker.stageJob(WORK_TYPE_TOP);
mWorkCountTracker.onJobStarted(WORK_TYPE_TOP); mWorkCountTracker.onJobStarted(WORK_TYPE_TOP);
} }
} else { } else if (isStartingFgUser) {
if (jobs.pending.get(WORK_TYPE_BG) > 0 if (jobs.pending.get(WORK_TYPE_BG) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE) { && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE) {
jobs.pending.put(WORK_TYPE_BG, jobs.pending.get(WORK_TYPE_BG) - 1); jobs.pending.put(WORK_TYPE_BG, jobs.pending.get(WORK_TYPE_BG) - 1);
@@ -139,6 +153,14 @@ public class WorkCountTrackerTest {
mWorkCountTracker.stageJob(WORK_TYPE_BG); mWorkCountTracker.stageJob(WORK_TYPE_BG);
mWorkCountTracker.onJobStarted(WORK_TYPE_BG); mWorkCountTracker.onJobStarted(WORK_TYPE_BG);
} }
} else {
if (jobs.pending.get(WORK_TYPE_BGUSER) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_BGUSER) != WORK_TYPE_NONE) {
jobs.pending.put(WORK_TYPE_BGUSER, jobs.pending.get(WORK_TYPE_BGUSER) - 1);
jobs.running.put(WORK_TYPE_BGUSER, jobs.running.get(WORK_TYPE_BGUSER) + 1);
mWorkCountTracker.stageJob(WORK_TYPE_BGUSER);
mWorkCountTracker.onJobStarted(WORK_TYPE_BGUSER);
}
} }
} }
} }
@@ -149,10 +171,10 @@ public class WorkCountTrackerTest {
private void checkRandom(Jobs jobs, int numTests, int totalMax, private void checkRandom(Jobs jobs, int numTests, int totalMax,
@NonNull List<Pair<Integer, Integer>> minLimits, @NonNull List<Pair<Integer, Integer>> minLimits,
@NonNull List<Pair<Integer, Integer>> maxLimits, @NonNull List<Pair<Integer, Integer>> maxLimits,
double startRatio, double fgJobRatio, double stopRatio) { double startRatio, double fgJobRatio, double fgUserJobRatio, double stopRatio) {
for (int i = 0; i < numTests; i++) { for (int i = 0; i < numTests; i++) {
jobs.maybeFinishJobs(stopRatio); jobs.maybeFinishJobs(stopRatio);
jobs.maybeEnqueueJobs(startRatio, fgJobRatio); jobs.maybeEnqueueJobs(startRatio, fgJobRatio, fgUserJobRatio);
recount(jobs, totalMax, minLimits, maxLimits); recount(jobs, totalMax, minLimits, maxLimits);
startPendingJobs(jobs); startPendingJobs(jobs);
@@ -182,14 +204,21 @@ public class WorkCountTrackerTest {
final int numTests = 5000; final int numTests = 5000;
final int totalMax = 6; final int totalMax = 6;
final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4)); final List<Pair<Integer, Integer>> maxLimits =
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double stopRatio = 0.1; final double stopRatio = 0.1;
// WorkType probabilities:
// WORK_TYPE_TOP -- 50%
// WORK_TYPE_BG -- 50%
// WORK_TYPE_BGUSER -- 0%
final double fgJobRatio = 0.5; final double fgJobRatio = 0.5;
final double fgUserJobRatio = 1;
final double startRatio = 0.1; final double startRatio = 0.1;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
startRatio, fgJobRatio, stopRatio); startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
} }
@Test @Test
@@ -198,14 +227,20 @@ public class WorkCountTrackerTest {
final int numTests = 5000; final int numTests = 5000;
final int totalMax = 2; final int totalMax = 2;
final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final List<Pair<Integer, Integer>> maxLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final List<Pair<Integer, Integer>> minLimits = List.of(); final List<Pair<Integer, Integer>> minLimits = List.of();
final double stopRatio = 0.5; final double stopRatio = 0.5;
// WorkType probabilities:
// WORK_TYPE_TOP -- 50%
// WORK_TYPE_BG -- 50%
// WORK_TYPE_BGUSER -- 0%
final double fgJobRatio = 0.5; final double fgJobRatio = 0.5;
final double fgUserJobRatio = 1;
final double startRatio = 0.5; final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
startRatio, fgJobRatio, stopRatio); startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
} }
@Test @Test
@@ -214,14 +249,20 @@ public class WorkCountTrackerTest {
final int numTests = 5000; final int numTests = 5000;
final int totalMax = 2; final int totalMax = 2;
final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final List<Pair<Integer, Integer>> maxLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.5; final double stopRatio = 0.5;
final double fgJobRatio = 0.5; // WorkType probabilities:
// WORK_TYPE_TOP -- 33%
// WORK_TYPE_BG -- 33%
// WORK_TYPE_BGUSER -- 33%
final double fgJobRatio = 1 / 3.0;
final double fgUserJobRatio = 2 / 3.0;
final double startRatio = 0.5; final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
startRatio, fgJobRatio, stopRatio); startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
} }
@Test @Test
@@ -230,14 +271,20 @@ public class WorkCountTrackerTest {
final int numTests = 5000; final int numTests = 5000;
final int totalMax = 10; final int totalMax = 10;
final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final List<Pair<Integer, Integer>> maxLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final List<Pair<Integer, Integer>> minLimits = List.of(); final List<Pair<Integer, Integer>> minLimits = List.of();
final double stopRatio = 0.5; final double stopRatio = 0.5;
final double fgJobRatio = 0.5; // WorkType probabilities:
// WORK_TYPE_TOP -- 33%
// WORK_TYPE_BG -- 33%
// WORK_TYPE_BGUSER -- 33%
final double fgJobRatio = 1 / 3.0;
final double fgUserJobRatio = 2 / 3.0;
final double startRatio = 0.5; final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
startRatio, fgJobRatio, stopRatio); startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
} }
@Test @Test
@@ -246,14 +293,20 @@ public class WorkCountTrackerTest {
final int numTests = 5000; final int numTests = 5000;
final int totalMax = 6; final int totalMax = 6;
final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4)); final List<Pair<Integer, Integer>> maxLimits =
List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.5; final double stopRatio = 0.5;
// WorkType probabilities:
// WORK_TYPE_TOP -- 10%
// WORK_TYPE_BG -- 80%
// WORK_TYPE_BGUSER -- 10%
final double fgJobRatio = 0.1; final double fgJobRatio = 0.1;
final double fgUserJobRatio = 0.9;
final double startRatio = 0.5; final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
startRatio, fgJobRatio, stopRatio); startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
} }
@Test @Test
@@ -262,14 +315,20 @@ public class WorkCountTrackerTest {
final int numTests = 5000; final int numTests = 5000;
final int totalMax = 6; final int totalMax = 6;
final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4)); final List<Pair<Integer, Integer>> maxLimits =
List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.5; final double stopRatio = 0.5;
// WorkType probabilities:
// WORK_TYPE_TOP -- 90%
// WORK_TYPE_BG -- 10%
// WORK_TYPE_BGUSER -- 0%
final double fgJobRatio = 0.9; final double fgJobRatio = 0.9;
final double fgUserJobRatio = 1;
final double startRatio = 0.5; final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
startRatio, fgJobRatio, stopRatio); startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
} }
@Test @Test
@@ -278,14 +337,20 @@ public class WorkCountTrackerTest {
final int numTests = 5000; final int numTests = 5000;
final int totalMax = 6; final int totalMax = 6;
final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4)); final List<Pair<Integer, Integer>> maxLimits =
List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.4; final double stopRatio = 0.4;
// WorkType probabilities:
// WORK_TYPE_TOP -- 10%
// WORK_TYPE_BG -- 10%
// WORK_TYPE_BGUSER -- 80%
final double fgJobRatio = 0.1; final double fgJobRatio = 0.1;
final double fgUserJobRatio = 0.2;
final double startRatio = 0.5; final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
startRatio, fgJobRatio, stopRatio); startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
} }
@Test @Test
@@ -294,14 +359,90 @@ public class WorkCountTrackerTest {
final int numTests = 5000; final int numTests = 5000;
final int totalMax = 6; final int totalMax = 6;
final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4)); final List<Pair<Integer, Integer>> maxLimits =
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2)); List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double stopRatio = 0.4; final double stopRatio = 0.4;
// WorkType probabilities:
// WORK_TYPE_TOP -- 90%
// WORK_TYPE_BG -- 5%
// WORK_TYPE_BGUSER -- 5%
final double fgJobRatio = 0.9; final double fgJobRatio = 0.9;
final double fgUserJobRatio = 0.95;
final double startRatio = 0.5; final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
startRatio, fgJobRatio, stopRatio); startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
}
@Test
public void testRandom9() {
final Jobs jobs = new Jobs();
final int numTests = 5000;
final int totalMax = 6;
final List<Pair<Integer, Integer>> maxLimits =
List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double stopRatio = 0.5;
// WorkType probabilities:
// WORK_TYPE_TOP -- 0%
// WORK_TYPE_BG -- 50%
// WORK_TYPE_BGUSER -- 50%
final double fgJobRatio = 0;
final double fgUserJobRatio = 0.5;
final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
}
@Test
public void testRandom10() {
final Jobs jobs = new Jobs();
final int numTests = 5000;
final int totalMax = 6;
final List<Pair<Integer, Integer>> maxLimits =
List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double stopRatio = 0.5;
// WorkType probabilities:
// WORK_TYPE_TOP -- 0%
// WORK_TYPE_BG -- 10%
// WORK_TYPE_BGUSER -- 90%
final double fgJobRatio = 0;
final double fgUserJobRatio = 0.1;
final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
}
@Test
public void testRandom11() {
final Jobs jobs = new Jobs();
final int numTests = 5000;
final int totalMax = 6;
final List<Pair<Integer, Integer>> maxLimits =
List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double stopRatio = 0.5;
// WorkType probabilities:
// WORK_TYPE_TOP -- 0%
// WORK_TYPE_BG -- 90%
// WORK_TYPE_BGUSER -- 10%
final double fgJobRatio = 0;
final double fgUserJobRatio = 0.9;
final double startRatio = 0.5;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
startRatio, fgJobRatio, fgUserJobRatio, stopRatio);
} }
/** Used by the following tests */ /** Used by the following tests */
@@ -337,7 +478,7 @@ public class WorkCountTrackerTest {
public void testBasic() { public void testBasic() {
checkSimple(6, checkSimple(6,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)), /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* max */ List.of(Pair.create(WORK_TYPE_BG, 4)), /* max */ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)),
/* run */ List.of(), /* run */ List.of(),
/* pen */ List.of(Pair.create(WORK_TYPE_TOP, 1)), /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 1)),
/* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 1)), /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 1)),
@@ -345,7 +486,7 @@ public class WorkCountTrackerTest {
checkSimple(6, checkSimple(6,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)), /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* max */ List.of(Pair.create(WORK_TYPE_BG, 4)), /* max */ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)),
/* run */ List.of(), /* run */ List.of(),
/* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10)), /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10)),
/* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6)), /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
@@ -354,7 +495,7 @@ public class WorkCountTrackerTest {
// When there are BG jobs pending, 2 (min-BG) jobs should run. // When there are BG jobs pending, 2 (min-BG) jobs should run.
checkSimple(6, checkSimple(6,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)), /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* max */ List.of(Pair.create(WORK_TYPE_BG, 4)), /* max */ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)),
/* run */ List.of(), /* run */ List.of(),
/* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 1)), /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 1)),
/* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 5), Pair.create(WORK_TYPE_BG, 1)), /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 5), Pair.create(WORK_TYPE_BG, 1)),
@@ -383,6 +524,16 @@ public class WorkCountTrackerTest {
/* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)), /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)),
/* resPen */ List.of(Pair.create(WORK_TYPE_BG, 47))); /* resPen */ List.of(Pair.create(WORK_TYPE_BG, 47)));
checkSimple(8,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* max */ List.of(Pair.create(WORK_TYPE_BG, 6)),
/* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 4)),
/* pen */ List.of(Pair.create(WORK_TYPE_TOP, 49), Pair.create(WORK_TYPE_BG, 49)),
/* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 4)),
/* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 47), Pair.create(WORK_TYPE_BG, 49))
);
checkSimple(8, checkSimple(8,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)), /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 6)), /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 6)),
@@ -407,6 +558,14 @@ public class WorkCountTrackerTest {
/* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)), /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)),
/* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 8), Pair.create(WORK_TYPE_BG, 49))); /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 8), Pair.create(WORK_TYPE_BG, 49)));
checkSimple(8,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 1)),
/* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
/* run */ List.of(Pair.create(WORK_TYPE_BG, 6)),
/* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
/* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)),
/* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 8), Pair.create(WORK_TYPE_BG, 49)));
checkSimple(6, checkSimple(6,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)), /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* max */ List.of(Pair.create(WORK_TYPE_BG, 4)), /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
@@ -425,6 +584,38 @@ public class WorkCountTrackerTest {
/* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)), /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
/* resRun */ List.of(Pair.create(WORK_TYPE_BG, 6)), /* resRun */ List.of(Pair.create(WORK_TYPE_BG, 6)),
/* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3))); /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)));
checkSimple(6,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* max */ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)),
/* run */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10),
Pair.create(WORK_TYPE_BG, 3),
Pair.create(WORK_TYPE_BGUSER, 3)),
/* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
/* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 6),
Pair.create(WORK_TYPE_BG, 3),
Pair.create(WORK_TYPE_BGUSER, 3)));
checkSimple(6,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* max */ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 3)),
/* run */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* pen */ List.of(Pair.create(WORK_TYPE_BG, 3), Pair.create(WORK_TYPE_BGUSER, 3)),
/* resRun */ List.of(
Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2)),
/* resPen */ List.of(
Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1)));
checkSimple(6,
/* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* max */ List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)),
/* run */ List.of(Pair.create(WORK_TYPE_BG, 2)),
/* pen */ List.of(Pair.create(WORK_TYPE_BG, 3), Pair.create(WORK_TYPE_BGUSER, 3)),
/* resRun */ List.of(
Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)),
/* resPen */ List.of(
Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 2)));
} }
/** Tests that the counter updates properly when jobs are stopped. */ /** Tests that the counter updates properly when jobs are stopped. */

View File

@@ -16,8 +16,10 @@
package com.android.server.job; package com.android.server.job;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import android.annotation.NonNull; import android.annotation.NonNull;
@@ -31,7 +33,6 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.server.job.JobConcurrencyManager.WorkTypeConfig; import com.android.server.job.JobConcurrencyManager.WorkTypeConfig;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -44,8 +45,10 @@ public class WorkTypeConfigTest {
private static final String KEY_MAX_TOTAL = "concurrency_max_total_test"; 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_TOP = "concurrency_max_top_test";
private static final String KEY_MAX_BG = "concurrency_max_bg_test"; private static final String KEY_MAX_BG = "concurrency_max_bg_test";
private static final String KEY_MAX_BGUSER = "concurrency_max_bguser_test";
private static final String KEY_MIN_TOP = "concurrency_min_top_test"; private static final String KEY_MIN_TOP = "concurrency_min_top_test";
private static final String KEY_MIN_BG = "concurrency_min_bg_test"; private static final String KEY_MIN_BG = "concurrency_min_bg_test";
private static final String KEY_MIN_BGUSER = "concurrency_min_bguser_test";
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
@@ -57,17 +60,21 @@ public class WorkTypeConfigTest {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, "", 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_TOP, "", false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, "", false); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, "", false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BGUSER, "", false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, "", false); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, "", false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, "", false); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, "", false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BGUSER, "", false);
} }
private void check(@Nullable DeviceConfig.Properties config, private void check(@Nullable DeviceConfig.Properties config,
int defaultTotal, int defaultTotal,
@Nullable Pair<Integer, Integer> defaultTopLimits, @Nullable Pair<Integer, Integer> defaultTopLimits,
@Nullable Pair<Integer, Integer> defaultBgLimits, @Nullable Pair<Integer, Integer> defaultBgLimits,
@Nullable Pair<Integer, Integer> defaultBgUserLimits,
boolean expectedValid, int expectedTotal, boolean expectedValid, int expectedTotal,
@NonNull Pair<Integer, Integer> expectedTopLimits, @NonNull Pair<Integer, Integer> expectedTopLimits,
@NonNull Pair<Integer, Integer> expectedBgLimits) throws Exception { @NonNull Pair<Integer, Integer> expectedBgLimits,
@NonNull Pair<Integer, Integer> expectedBgUserLimits) throws Exception {
resetConfig(); resetConfig();
if (config != null) { if (config != null) {
DeviceConfig.setProperties(config); DeviceConfig.setProperties(config);
@@ -92,6 +99,14 @@ public class WorkTypeConfigTest {
defaultMax.add(Pair.create(WORK_TYPE_BG, val)); defaultMax.add(Pair.create(WORK_TYPE_BG, val));
} }
} }
if (defaultBgUserLimits != null) {
if ((val = defaultBgUserLimits.first) != null) {
defaultMin.add(Pair.create(WORK_TYPE_BGUSER, val));
}
if ((val = defaultBgUserLimits.second) != null) {
defaultMax.add(Pair.create(WORK_TYPE_BGUSER, val));
}
}
final WorkTypeConfig counts; final WorkTypeConfig counts;
try { try {
@@ -112,40 +127,45 @@ public class WorkTypeConfigTest {
counts.update(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER)); counts.update(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER));
Assert.assertEquals(expectedTotal, counts.getMaxTotal()); assertEquals(expectedTotal, counts.getMaxTotal());
Assert.assertEquals((int) expectedTopLimits.first, counts.getMinReserved(WORK_TYPE_TOP)); assertEquals((int) expectedTopLimits.first, counts.getMinReserved(WORK_TYPE_TOP));
Assert.assertEquals((int) expectedTopLimits.second, counts.getMax(WORK_TYPE_TOP)); assertEquals((int) expectedTopLimits.second, counts.getMax(WORK_TYPE_TOP));
Assert.assertEquals((int) expectedBgLimits.first, counts.getMinReserved(WORK_TYPE_BG)); assertEquals((int) expectedBgLimits.first, counts.getMinReserved(WORK_TYPE_BG));
Assert.assertEquals((int) expectedBgLimits.second, counts.getMax(WORK_TYPE_BG)); assertEquals((int) expectedBgLimits.second, counts.getMax(WORK_TYPE_BG));
assertEquals((int) expectedBgUserLimits.first, counts.getMinReserved(WORK_TYPE_BGUSER));
assertEquals((int) expectedBgUserLimits.second, counts.getMax(WORK_TYPE_BGUSER));
} }
@Test @Test
public void test() throws Exception { public void test() throws Exception {
// Tests with various combinations. // Tests with various combinations.
check(null, /*default*/ 5, Pair.create(4, null), Pair.create(0, 1), check(null, /*default*/ 5, Pair.create(4, null), Pair.create(0, 1), Pair.create(0, 1),
/*expected*/ true, 5, Pair.create(4, 5), Pair.create(0, 1)); /*expected*/ true, 5, Pair.create(4, 5), Pair.create(0, 1), Pair.create(0, 1));
check(null, /*default*/ 5, Pair.create(5, null), Pair.create(0, 0), check(null, /*default*/ 5, Pair.create(5, null), Pair.create(0, 0), Pair.create(0, 0),
/*expected*/ true, 5, Pair.create(5, 5), Pair.create(0, 1)); /*expected*/ true, 5, Pair.create(5, 5), Pair.create(0, 1), Pair.create(0, 1));
check(null, /*default*/ 0, Pair.create(5, null), Pair.create(0, 0), check(null, /*default*/ 0, Pair.create(5, null), Pair.create(0, 0), Pair.create(0, 0),
/*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1)); /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1), Pair.create(0, 1));
check(null, /*default*/ -1, null, Pair.create(-1, -1), check(null, /*default*/ -1, null, Pair.create(-1, -1), Pair.create(-1, -1),
/*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1)); /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1), Pair.create(0, 1));
check(null, /*default*/ 5, null, Pair.create(5, 5), check(null, /*default*/ 5, null, Pair.create(5, 5), Pair.create(0, 5),
/*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5)); /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5), Pair.create(0, 5));
check(null, /*default*/ 6, Pair.create(1, null), Pair.create(6, 5), check(null, /*default*/ 6, Pair.create(1, null), Pair.create(6, 5), Pair.create(2, 1),
/*expected*/ false, 6, Pair.create(1, 6), Pair.create(5, 5)); /*expected*/ false, 6, Pair.create(1, 6), Pair.create(5, 5), Pair.create(0, 1));
check(null, /*default*/ 4, null, Pair.create(6, 5), check(null, /*default*/ 4, null, Pair.create(6, 5), Pair.create(6, 5),
/*expected*/ false, 4, Pair.create(1, 4), Pair.create(3, 4)); /*expected*/ false, 4, Pair.create(1, 4), Pair.create(3, 4), Pair.create(0, 4));
check(null, /*default*/ 5, Pair.create(4, null), Pair.create(1, 1), check(null, /*default*/ 5, Pair.create(4, null), Pair.create(1, 1), Pair.create(0, 5),
/*expected*/ true, 5, Pair.create(4, 5), Pair.create(1, 1)); /*expected*/ true, 5, Pair.create(4, 5), Pair.create(1, 1), Pair.create(0, 5));
check(null, /*default*/ 15, null, Pair.create(15, 15), check(null, /*default*/ 5, Pair.create(4, null), Pair.create(0, 1), Pair.create(1, 5),
/*expected*/ true, 15, Pair.create(1, 15), Pair.create(14, 15)); /*expected*/ true, 5, Pair.create(4, 5), Pair.create(0, 1), Pair.create(1, 5));
check(null, /*default*/ 16, null, Pair.create(16, 16), check(null, /*default*/ 15, null, Pair.create(15, 15), Pair.create(0, 15),
/*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16)); /*expected*/ true, 15, Pair.create(1, 15), Pair.create(14, 15), Pair.create(0, 15));
check(null, /*default*/ 20, null, Pair.create(20, 20), check(null, /*default*/ 16, null, Pair.create(16, 16), Pair.create(0, 16),
/*expected*/ false, 16, Pair.create(1, 16), Pair.create(15, 16)); /*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16), Pair.create(0, 16));
check(null, /*default*/ 20, null, Pair.create(16, 16), check(null, /*default*/ 20, null, Pair.create(20, 20), Pair.create(10, 20),
/*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16)); /*expected*/ false, 16,
Pair.create(1, 16), Pair.create(15, 16), Pair.create(0, 16));
check(null, /*default*/ 20, null, Pair.create(16, 16), Pair.create(0, 16),
/*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16), Pair.create(0, 16));
// Test for overriding with a setting string. // Test for overriding with a setting string.
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
@@ -153,26 +173,26 @@ public class WorkTypeConfigTest {
.setInt(KEY_MAX_BG, 4) .setInt(KEY_MAX_BG, 4)
.setInt(KEY_MIN_BG, 3) .setInt(KEY_MIN_BG, 3)
.build(), .build(),
/*default*/ 9, null, Pair.create(9, 9), /*default*/ 9, null, Pair.create(9, 9), Pair.create(0, 2),
/*expected*/ true, 5, Pair.create(1, 5), Pair.create(3, 4)); /*expected*/ true, 5, Pair.create(1, 5), Pair.create(3, 4), Pair.create(0, 2));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MAX_TOTAL, 5).build(), .setInt(KEY_MAX_TOTAL, 5).build(),
/*default*/ 9, null, Pair.create(9, 9), /*default*/ 9, null, Pair.create(9, 9), Pair.create(0, 0),
/*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5)); /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5), Pair.create(0, 1));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MAX_BG, 4).build(), .setInt(KEY_MAX_BG, 4).build(),
/*default*/ 9, null, Pair.create(9, 9), /*default*/ 9, null, Pair.create(9, 9), Pair.create(0, 9),
/*expected*/ true, 9, Pair.create(1, 9), Pair.create(4, 4)); /*expected*/ true, 9, Pair.create(1, 9), Pair.create(4, 4), Pair.create(0, 9));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MIN_BG, 3).build(), .setInt(KEY_MIN_BG, 3).build(),
/*default*/ 9, null, Pair.create(9, 9), /*default*/ 9, null, Pair.create(9, 9), Pair.create(0, 6),
/*expected*/ true, 9, Pair.create(1, 9), Pair.create(3, 9)); /*expected*/ true, 9, Pair.create(1, 9), Pair.create(3, 9), Pair.create(0, 6));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER) check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MAX_TOTAL, 20) .setInt(KEY_MAX_TOTAL, 20)
.setInt(KEY_MAX_BG, 20) .setInt(KEY_MAX_BG, 20)
.setInt(KEY_MIN_BG, 8) .setInt(KEY_MIN_BG, 8)
.build(), .build(),
/*default*/ 9, null, Pair.create(9, 9), /*default*/ 9, null, Pair.create(9, 9), Pair.create(0, 8),
/*expected*/ true, 16, Pair.create(1, 16), Pair.create(8, 16)); /*expected*/ true, 16, Pair.create(1, 16), Pair.create(8, 16), Pair.create(0, 8));
} }
} }