Merge "Work on issue #28942589: Tune job scheduler" into nyc-dev

am: 438693dd6c

* commit '438693dd6cc4e3fe2ffb336d551c0d69e9546102':
  Work on issue #28942589: Tune job scheduler

Change-Id: I9e4ddcc64d84a8d943128d8bd8ebf158743c5549
This commit is contained in:
Dianne Hackborn
2016-06-01 16:38:18 +00:00
committed by android-build-merger
15 changed files with 529 additions and 154 deletions

View File

@@ -596,6 +596,22 @@ public class AlarmManager {
null, workSource, null);
}
/**
* Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
* Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.
* <p>
* The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
* invoked via the specified target Handler, or on the application's main looper
* if {@code null} is passed as the {@code targetHandler} parameter.
*
* @hide
*/
public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
String tag, OnAlarmListener listener, Handler targetHandler, WorkSource workSource) {
setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, tag,
targetHandler, workSource, null);
}
/**
* Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
* Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.

View File

@@ -192,7 +192,8 @@ public class JobInfo implements Parcelable {
private final int flags;
/**
* Unique job id associated with this class. This is assigned to your job by the scheduler.
* Unique job id associated with this application (uid). This is the same job ID
* you supplied in the {@link Builder} constructor.
*/
public int getId() {
return jobId;
@@ -524,9 +525,9 @@ public class JobInfo implements Parcelable {
/** Builder class for constructing {@link JobInfo} objects. */
public static final class Builder {
private int mJobId;
private final int mJobId;
private final ComponentName mJobService;
private PersistableBundle mExtras = PersistableBundle.EMPTY;
private ComponentName mJobService;
private int mPriority = PRIORITY_DEFAULT;
private int mFlags;
// Requirements.
@@ -553,11 +554,15 @@ public class JobInfo implements Parcelable {
private boolean mBackoffPolicySet = false;
/**
* Initialize a new Builder to construct a {@link JobInfo}.
*
* @param jobId Application-provided id for this job. Subsequent calls to cancel, or
* jobs created with the same jobId, will update the pre-existing job with
* the same id.
* jobs created with the same jobId, will update the pre-existing job with
* the same id. This ID must be unique across all clients of the same uid
* (not just the same package). You will want to make sure this is a stable
* id across app updates, so probably not based on a resource ID.
* @param jobService The endpoint that you implement that will receive the callback from the
* JobScheduler.
* JobScheduler.
*/
public Builder(int jobId, ComponentName jobService) {
mJobService = jobService;

View File

@@ -8017,6 +8017,36 @@ public final class Settings {
*/
public static final String ALARM_MANAGER_CONSTANTS = "alarm_manager_constants";
/**
* Job scheduler specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
*
* "min_ready_jobs_count=2,moderate_use_factor=.5"
*
* The following keys are supported:
*
* <pre>
* min_idle_count (int)
* min_charging_count (int)
* min_connectivity_count (int)
* min_content_count (int)
* min_ready_jobs_count (int)
* heavy_use_factor (float)
* moderate_use_factor (float)
* fg_job_count (int)
* bg_normal_job_count (int)
* bg_moderate_job_count (int)
* bg_low_job_count (int)
* bg_critical_job_count (int)
* </pre>
*
* <p>
* Type: string
* @hide
* @see com.android.server.job.JobSchedulerService.Constants
*/
public static final String JOB_SCHEDULER_CONSTANTS = "job_scheduler_constants";
/**
* ShortcutManager specific settings.
* This is encoded as a key=value list, separated by commas. Ex:

View File

@@ -62,6 +62,24 @@ public class KeyValueListParser {
}
}
/**
* Get the value for key as an int.
* @param key The key to lookup.
* @param def The value to return if the key was not found, or the value was not a long.
* @return the int value associated with the key.
*/
public int getInt(String key, int def) {
String value = mValues.get(key);
if (value != null) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
// fallthrough
}
}
return def;
}
/**
* Get the value for key as a long.
* @param key The key to lookup.

View File

@@ -105,6 +105,8 @@ public final class JobPackageTracker {
final long mStartElapsedTime;
final long mStartClockTime;
long mSummedTime;
int mMaxTotalActive;
int mMaxFgActive;
public DataSet(DataSet otherTimes) {
mStartUptimeTime = otherTimes.mStartUptimeTime;
@@ -257,6 +259,12 @@ public final class JobPackageTracker {
}
}
}
if (mMaxTotalActive > out.mMaxTotalActive) {
out.mMaxTotalActive = mMaxTotalActive;
}
if (mMaxFgActive > out.mMaxFgActive) {
out.mMaxFgActive = mMaxFgActive;
}
}
void printDuration(PrintWriter pw, long period, long duration, int count, String suffix) {
@@ -317,6 +325,9 @@ public final class JobPackageTracker {
pw.println();
}
}
pw.print(prefix); pw.print(" Max concurrency: ");
pw.print(mMaxTotalActive); pw.print(" total, ");
pw.print(mMaxFgActive); pw.println(" foreground");
}
}
@@ -366,6 +377,15 @@ public final class JobPackageTracker {
addEvent(EVENT_STOP_JOB, job.getSourceUid(), job.getBatteryName());
}
public void noteConcurrency(int totalActive, int fgActive) {
if (totalActive > mCurDataSet.mMaxTotalActive) {
mCurDataSet.mMaxTotalActive = totalActive;
}
if (fgActive > mCurDataSet.mMaxFgActive) {
mCurDataSet.mMaxFgActive = fgActive;
}
}
public float getLoadFactor(JobStatus job) {
final int uid = job.getSourceUid();
final String pkg = job.getSourcePackageName();

View File

@@ -23,6 +23,8 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
@@ -37,6 +39,7 @@ import android.app.job.JobService;
import android.app.job.IJobScheduler;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -44,6 +47,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryStats;
import android.os.Binder;
@@ -57,6 +61,8 @@ import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.KeyValueListParser;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -98,17 +104,12 @@ public final class JobSchedulerService extends com.android.server.SystemService
public static final boolean DEBUG = false;
/** The maximum number of concurrent jobs we run at one time. */
private static final int MAX_JOB_CONTEXTS_COUNT = 12;
/** The number of MAX_JOB_CONTEXTS_COUNT we reserve for the foreground app. */
private static final int FG_JOB_CONTEXTS_COUNT = 4;
private static final int MAX_JOB_CONTEXTS_COUNT = 16;
/** Enforce a per-app limit on scheduled jobs? */
private static final boolean ENFORCE_MAX_JOBS = true;
/** The maximum number of jobs that we allow an unprivileged app to schedule */
private static final int MAX_JOBS_PER_APP = 100;
/** This is the job execution factor that is considered to be heavy use of the system. */
private static final float HEAVY_USE_FACTOR = .9f;
/** This is the job execution factor that is considered to be moderate use of the system. */
private static final float MODERATE_USE_FACTOR = .5f;
/** Global local for all job scheduler state. */
final Object mLock = new Object();
@@ -122,34 +123,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
static final int MSG_STOP_JOB = 2;
static final int MSG_CHECK_JOB_GREEDY = 3;
// Policy constants
/**
* Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
* early.
*/
static final int MIN_IDLE_COUNT = 1;
/**
* Minimum # of charging jobs that must be ready in order to force the JMS to schedule things
* early.
*/
static final int MIN_CHARGING_COUNT = 1;
/**
* Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
* things early.
*/
static final int MIN_CONNECTIVITY_COUNT = 1; // Run connectivity jobs as soon as ready.
/**
* Minimum # of content trigger jobs that must be ready in order to force the JMS to schedule
* things early.
*/
static final int MIN_CONTENT_COUNT = 1;
/**
* Minimum # of jobs (with no particular constraints) for which the JMS will be happy running
* some work early.
* This is correlated with the amount of batching we'll be able to do.
*/
static final int MIN_READY_JOBS_COUNT = 2;
/**
* Track Services that have currently active or pending jobs. The index is provided by
* {@link JobStatus#getServiceToken()}
@@ -186,7 +159,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
* Current limit on the number of concurrent JobServiceContext entries we want to
* keep actively running a job.
*/
int mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT;
int mMaxActiveJobs = 1;
/**
* Which uids are currently in the foreground.
@@ -211,6 +184,211 @@ public final class JobSchedulerService extends com.android.server.SystemService
*/
int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
/**
* All times are in milliseconds. These constants are kept synchronized with the system
* global Settings. Any access to this class or its fields should be done while
* holding the JobSchedulerService.mLock lock.
*/
private final class Constants extends ContentObserver {
// Key names stored in the settings value.
private static final String KEY_MIN_IDLE_COUNT = "min_idle_count";
private static final String KEY_MIN_CHARGING_COUNT = "min_charging_count";
private static final String KEY_MIN_CONNECTIVITY_COUNT = "min_connectivity_count";
private static final String KEY_MIN_CONTENT_COUNT = "min_content_count";
private static final String KEY_MIN_READY_JOBS_COUNT = "min_ready_jobs_count";
private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor";
private static final String KEY_MODERATE_USE_FACTOR = "moderate_use_factor";
private static final String KEY_FG_JOB_COUNT = "fg_job_count";
private static final String KEY_BG_NORMAL_JOB_COUNT = "bg_normal_job_count";
private static final String KEY_BG_MODERATE_JOB_COUNT = "bg_moderate_job_count";
private static final String KEY_BG_LOW_JOB_COUNT = "bg_low_job_count";
private static final String KEY_BG_CRITICAL_JOB_COUNT = "bg_critical_job_count";
private static final int DEFAULT_MIN_IDLE_COUNT = 1;
private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
private static final int DEFAULT_MIN_CONNECTIVITY_COUNT = 1;
private static final int DEFAULT_MIN_CONTENT_COUNT = 1;
private static final int DEFAULT_MIN_READY_JOBS_COUNT = 1;
private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
private static final int DEFAULT_FG_JOB_COUNT = 4;
private static final int DEFAULT_BG_NORMAL_JOB_COUNT = 6;
private static final int DEFAULT_BG_MODERATE_JOB_COUNT = 4;
private static final int DEFAULT_BG_LOW_JOB_COUNT = 2;
private static final int DEFAULT_BG_CRITICAL_JOB_COUNT = 1;
/**
* Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
* early.
*/
int MIN_IDLE_COUNT = DEFAULT_MIN_IDLE_COUNT;
/**
* Minimum # of charging jobs that must be ready in order to force the JMS to schedule
* things early.
*/
int MIN_CHARGING_COUNT = DEFAULT_MIN_CHARGING_COUNT;
/**
* Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
* things early. 1 == Run connectivity jobs as soon as ready.
*/
int MIN_CONNECTIVITY_COUNT = DEFAULT_MIN_CONNECTIVITY_COUNT;
/**
* Minimum # of content trigger jobs that must be ready in order to force the JMS to
* schedule things early.
*/
int MIN_CONTENT_COUNT = DEFAULT_MIN_CONTENT_COUNT;
/**
* Minimum # of jobs (with no particular constraints) for which the JMS will be happy
* running some work early. This (and thus the other min counts) is now set to 1, to
* prevent any batching at this level. Since we now do batching through doze, that is
* a much better mechanism.
*/
int MIN_READY_JOBS_COUNT = DEFAULT_MIN_READY_JOBS_COUNT;
/**
* This is the job execution factor that is considered to be heavy use of the system.
*/
float HEAVY_USE_FACTOR = DEFAULT_HEAVY_USE_FACTOR;
/**
* This is the job execution factor that is considered to be moderate use of the system.
*/
float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
/**
* The number of MAX_JOB_CONTEXTS_COUNT we reserve for the foreground app.
*/
int FG_JOB_COUNT = DEFAULT_FG_JOB_COUNT;
/**
* The maximum number of background jobs we allow when the system is in a normal
* memory state.
*/
int BG_NORMAL_JOB_COUNT = DEFAULT_BG_NORMAL_JOB_COUNT;
/**
* The maximum number of background jobs we allow when the system is in a moderate
* memory state.
*/
int BG_MODERATE_JOB_COUNT = DEFAULT_BG_MODERATE_JOB_COUNT;
/**
* The maximum number of background jobs we allow when the system is in a low
* memory state.
*/
int BG_LOW_JOB_COUNT = DEFAULT_BG_LOW_JOB_COUNT;
/**
* The maximum number of background jobs we allow when the system is in a critical
* memory state.
*/
int BG_CRITICAL_JOB_COUNT = DEFAULT_BG_CRITICAL_JOB_COUNT;
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
public Constants(Handler handler) {
super(handler);
}
public void start(ContentResolver resolver) {
mResolver = resolver;
mResolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.JOB_SCHEDULER_CONSTANTS), false, this);
updateConstants();
}
@Override
public void onChange(boolean selfChange, Uri uri) {
updateConstants();
}
private void updateConstants() {
synchronized (mLock) {
try {
mParser.setString(Settings.Global.getString(mResolver,
Settings.Global.ALARM_MANAGER_CONSTANTS));
} catch (IllegalArgumentException e) {
// Failed to parse the settings string, log this and move on
// with defaults.
Slog.e(TAG, "Bad device idle settings", e);
}
MIN_IDLE_COUNT = mParser.getInt(KEY_MIN_IDLE_COUNT,
DEFAULT_MIN_IDLE_COUNT);
MIN_CHARGING_COUNT = mParser.getInt(KEY_MIN_CHARGING_COUNT,
DEFAULT_MIN_CHARGING_COUNT);
MIN_CONNECTIVITY_COUNT = mParser.getInt(KEY_MIN_CONNECTIVITY_COUNT,
DEFAULT_MIN_CONNECTIVITY_COUNT);
MIN_CONTENT_COUNT = mParser.getInt(KEY_MIN_CONTENT_COUNT,
DEFAULT_MIN_CONTENT_COUNT);
MIN_READY_JOBS_COUNT = mParser.getInt(KEY_MIN_READY_JOBS_COUNT,
DEFAULT_MIN_READY_JOBS_COUNT);
HEAVY_USE_FACTOR = mParser.getFloat(KEY_HEAVY_USE_FACTOR,
DEFAULT_HEAVY_USE_FACTOR);
MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR,
DEFAULT_MODERATE_USE_FACTOR);
FG_JOB_COUNT = mParser.getInt(KEY_FG_JOB_COUNT,
DEFAULT_FG_JOB_COUNT);
BG_NORMAL_JOB_COUNT = mParser.getInt(KEY_BG_NORMAL_JOB_COUNT,
DEFAULT_BG_NORMAL_JOB_COUNT);
if ((FG_JOB_COUNT+BG_NORMAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
BG_NORMAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
}
BG_MODERATE_JOB_COUNT = mParser.getInt(KEY_BG_MODERATE_JOB_COUNT,
DEFAULT_BG_MODERATE_JOB_COUNT);
if ((FG_JOB_COUNT+BG_MODERATE_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
BG_MODERATE_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
}
BG_LOW_JOB_COUNT = mParser.getInt(KEY_BG_LOW_JOB_COUNT,
DEFAULT_BG_LOW_JOB_COUNT);
if ((FG_JOB_COUNT+BG_LOW_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
BG_LOW_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
}
BG_CRITICAL_JOB_COUNT = mParser.getInt(KEY_BG_CRITICAL_JOB_COUNT,
DEFAULT_BG_CRITICAL_JOB_COUNT);
if ((FG_JOB_COUNT+BG_CRITICAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
BG_CRITICAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
}
}
}
void dump(PrintWriter pw) {
pw.println(" Settings:");
pw.print(" "); pw.print(KEY_MIN_IDLE_COUNT); pw.print("=");
pw.print(MIN_IDLE_COUNT); pw.println();
pw.print(" "); pw.print(KEY_MIN_CHARGING_COUNT); pw.print("=");
pw.print(MIN_CHARGING_COUNT); pw.println();
pw.print(" "); pw.print(KEY_MIN_CONNECTIVITY_COUNT); pw.print("=");
pw.print(MIN_CONNECTIVITY_COUNT); pw.println();
pw.print(" "); pw.print(KEY_MIN_CONTENT_COUNT); pw.print("=");
pw.print(MIN_CONTENT_COUNT); pw.println();
pw.print(" "); pw.print(KEY_MIN_READY_JOBS_COUNT); pw.print("=");
pw.print(MIN_READY_JOBS_COUNT); pw.println();
pw.print(" "); pw.print(KEY_HEAVY_USE_FACTOR); pw.print("=");
pw.print(HEAVY_USE_FACTOR); pw.println();
pw.print(" "); pw.print(KEY_MODERATE_USE_FACTOR); pw.print("=");
pw.print(MODERATE_USE_FACTOR); pw.println();
pw.print(" "); pw.print(KEY_FG_JOB_COUNT); pw.print("=");
pw.print(FG_JOB_COUNT); pw.println();
pw.print(" "); pw.print(KEY_BG_NORMAL_JOB_COUNT); pw.print("=");
pw.print(BG_NORMAL_JOB_COUNT); pw.println();
pw.print(" "); pw.print(KEY_BG_MODERATE_JOB_COUNT); pw.print("=");
pw.print(BG_MODERATE_JOB_COUNT); pw.println();
pw.print(" "); pw.print(KEY_BG_LOW_JOB_COUNT); pw.print("=");
pw.print(BG_LOW_JOB_COUNT); pw.println();
pw.print(" "); pw.print(KEY_BG_CRITICAL_JOB_COUNT); pw.print("=");
pw.print(BG_CRITICAL_JOB_COUNT); pw.println();
}
}
final Constants mConstants;
/**
* Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
* still clean up. On reinstall the package will have a new uid.
@@ -550,6 +728,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
mControllers.add(DeviceIdleJobsController.get(this));
mHandler = new JobHandler(context.getMainLooper());
mConstants = new Constants(mHandler);
mJobSchedulerStub = new JobSchedulerStub();
mJobs = JobStore.initAndGet(this);
}
@@ -563,6 +742,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
@Override
public void onBootPhase(int phase) {
if (PHASE_SYSTEM_SERVICES_READY == phase) {
mConstants.start(getContext().getContentResolver());
// Register br for package removals and user removals.
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -994,11 +1174,12 @@ public final class JobSchedulerService extends com.android.server.SystemService
public void postProcess() {
if (backoffCount > 0 ||
idleCount >= MIN_IDLE_COUNT ||
connectivityCount >= MIN_CONNECTIVITY_COUNT ||
chargingCount >= MIN_CHARGING_COUNT ||
contentCount >= MIN_CONTENT_COUNT ||
(runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
idleCount >= mConstants.MIN_IDLE_COUNT ||
connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT ||
chargingCount >= mConstants.MIN_CHARGING_COUNT ||
contentCount >= mConstants.MIN_CONTENT_COUNT ||
(runnableJobs != null
&& runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) {
if (DEBUG) {
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
}
@@ -1095,9 +1276,9 @@ public final class JobSchedulerService extends com.android.server.SystemService
private int adjustJobPriority(int curPriority, JobStatus job) {
if (curPriority < JobInfo.PRIORITY_TOP_APP) {
float factor = mJobPackageTracker.getLoadFactor(job);
if (factor >= HEAVY_USE_FACTOR) {
if (factor >= mConstants.HEAVY_USE_FACTOR) {
curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING;
} else if (factor >= MODERATE_USE_FACTOR) {
} else if (factor >= mConstants.MODERATE_USE_FACTOR) {
curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING;
}
}
@@ -1135,16 +1316,16 @@ public final class JobSchedulerService extends com.android.server.SystemService
}
switch (memLevel) {
case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) * 2) / 3;
mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT;
break;
case ProcessStats.ADJ_MEM_FACTOR_LOW:
mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) / 3;
mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT;
break;
case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
mMaxActiveJobs = 1;
mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT;
break;
default:
mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT;
mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT;
break;
}
@@ -1152,10 +1333,15 @@ public final class JobSchedulerService extends com.android.server.SystemService
boolean[] act = mTmpAssignAct;
int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
int numActive = 0;
int numForeground = 0;
for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
final JobServiceContext js = mActiveServices.get(i);
if ((contextIdToJobMap[i] = js.getRunningJob()) != null) {
final JobStatus status = js.getRunningJob();
if ((contextIdToJobMap[i] = status) != null) {
numActive++;
if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
numForeground++;
}
}
act[i] = false;
preferredUidForContext[i] = js.getPreferredUid();
@@ -1184,13 +1370,14 @@ public final class JobSchedulerService extends com.android.server.SystemService
JobStatus job = contextIdToJobMap[j];
int preferredUid = preferredUidForContext[j];
if (job == null) {
if ((numActive < mMaxActiveJobs || priority >= JobInfo.PRIORITY_TOP_APP) &&
if ((numActive < mMaxActiveJobs ||
(priority >= JobInfo.PRIORITY_TOP_APP &&
numForeground < mConstants.FG_JOB_COUNT)) &&
(preferredUid == nextPending.getUid() ||
preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
// This slot is free, and we haven't yet hit the limit on
// concurrent jobs... we can just throw the job in to here.
minPriorityContextId = j;
numActive++;
break;
}
// No job on this context, but nextPending can't run here because
@@ -1212,11 +1399,16 @@ public final class JobSchedulerService extends com.android.server.SystemService
if (minPriorityContextId != -1) {
contextIdToJobMap[minPriorityContextId] = nextPending;
act[minPriorityContextId] = true;
numActive++;
if (priority >= JobInfo.PRIORITY_TOP_APP) {
numForeground++;
}
}
}
if (DEBUG) {
Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
}
mJobPackageTracker.noteConcurrency(numActive, numForeground);
for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
boolean preservePreferredUid = false;
if (act[i]) {
@@ -1569,36 +1761,49 @@ public final class JobSchedulerService extends com.android.server.SystemService
final int filterUidFinal = UserHandle.getAppId(filterUid);
final long now = SystemClock.elapsedRealtime();
synchronized (mLock) {
mConstants.dump(pw);
pw.println();
pw.println("Started users: " + Arrays.toString(mStartedUsers));
pw.println("Registered jobs:");
pw.print("Registered ");
pw.print(mJobs.size());
pw.println(" jobs:");
if (mJobs.size() > 0) {
mJobs.forEachJob(new JobStatusFunctor() {
private int index = 0;
final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
Collections.sort(jobs, new Comparator<JobStatus>() {
@Override
public void process(JobStatus job) {
pw.print(" Job #"); pw.print(index++); pw.print(": ");
pw.println(job.toShortString());
// Skip printing details if the caller requested a filter
if (!job.shouldDump(filterUidFinal)) {
return;
public int compare(JobStatus o1, JobStatus o2) {
int uid1 = o1.getUid();
int uid2 = o2.getUid();
int id1 = o1.getJobId();
int id2 = o2.getJobId();
if (uid1 != uid2) {
return uid1 < uid2 ? -1 : 1;
}
job.dump(pw, " ", true);
pw.print(" Ready: ");
pw.print(mHandler.isReadyToBeExecutedLocked(job));
pw.print(" (job=");
pw.print(job.isReady());
pw.print(" pending=");
pw.print(mPendingJobs.contains(job));
pw.print(" active=");
pw.print(isCurrentlyActiveLocked(job));
pw.print(" user=");
pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
pw.println(")");
return id1 < id2 ? -1 : (id1 > id2 ? 1 : 0);
}
});
for (JobStatus job : jobs) {
pw.print(" JOB #"); job.printUniqueId(pw); pw.print(": ");
pw.println(job.toShortStringExceptUniqueId());
// Skip printing details if the caller requested a filter
if (!job.shouldDump(filterUidFinal)) {
continue;
}
job.dump(pw, " ", true);
pw.print(" Ready: ");
pw.print(mHandler.isReadyToBeExecutedLocked(job));
pw.print(" (job=");
pw.print(job.isReady());
pw.print(" pending=");
pw.print(mPendingJobs.contains(job));
pw.print(" active=");
pw.print(isCurrentlyActiveLocked(job));
pw.print(" user=");
pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
pw.println(")");
}
} else {
pw.println(" None.");
}

View File

@@ -844,8 +844,16 @@ public class JobStore {
// Inefficient; use only for testing
public List<JobStatus> getAllJobs() {
ArrayList<JobStatus> allJobs = new ArrayList<JobStatus>(size());
for (int i = mJobs.size(); i >= 0; i--) {
allJobs.addAll(mJobs.valueAt(i));
for (int i = mJobs.size() - 1; i >= 0; i--) {
ArraySet<JobStatus> jobs = mJobs.valueAt(i);
if (jobs != null) {
// Use a for loop over the ArraySet, so we don't need to make its
// optional collection class iterator implementation or have to go
// through a temporary array from toArray().
for (int j = jobs.size() - 1; j >= 0; j--) {
allJobs.add(jobs.valueAt(j));
}
}
}
return allJobs;
}

View File

@@ -18,6 +18,7 @@ package com.android.server.job.controllers;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.os.UserHandle;
import android.util.Slog;
import com.android.server.LocalServices;
@@ -42,6 +43,7 @@ public class AppIdleController extends StateController {
private static volatile AppIdleController sController;
private final JobSchedulerService mJobSchedulerService;
private final UsageStatsManagerInternal mUsageStatsInternal;
private boolean mInitializedParoleOn;
boolean mAppIdleParoleOn;
final class GlobalUpdateFunc implements JobStore.JobStatusFunctor {
@@ -100,12 +102,16 @@ public class AppIdleController extends StateController {
super(service, context, lock);
mJobSchedulerService = service;
mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class);
mAppIdleParoleOn = mUsageStatsInternal.isAppIdleParoleOn();
mAppIdleParoleOn = true;
mUsageStatsInternal.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
}
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
if (!mInitializedParoleOn) {
mInitializedParoleOn = true;
mAppIdleParoleOn = mUsageStatsInternal.isAppIdleParoleOn();
}
String packageName = jobStatus.getSourcePackageName();
final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
jobStatus.getSourceUid(), jobStatus.getSourceUserId());
@@ -122,21 +128,24 @@ public class AppIdleController extends StateController {
@Override
public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
pw.println("AppIdle");
pw.println("Parole On: " + mAppIdleParoleOn);
pw.print("AppIdle: parole on = ");
pw.println(mAppIdleParoleOn);
mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
@Override public void process(JobStatus jobStatus) {
// Skip printing details if the caller requested a filter
if (!jobStatus.shouldDump(filterUid)) {
return;
}
pw.print(" ");
pw.print(" #");
jobStatus.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, jobStatus.getSourceUid());
pw.print(": ");
pw.print(jobStatus.getSourcePackageName());
pw.print(": runnable=");
pw.print(", runnable=");
pw.println((jobStatus.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0);
}
});
pw.println();
}
void setAppIdleParoleOn(boolean isAppIdleParoleOn) {

View File

@@ -23,6 +23,7 @@ import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -195,21 +196,21 @@ public class BatteryController extends StateController {
@Override
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
pw.println("Batt.");
pw.println("Stable power: " + mChargeTracker.isOnStablePower());
Iterator<JobStatus> it = mTrackedTasks.iterator();
if (it.hasNext()) {
JobStatus jobStatus = it.next();
if (jobStatus.shouldDump(filterUid)) {
pw.print(String.valueOf(jobStatus.hashCode()));
pw.print("Battery: stable power = ");
pw.println(mChargeTracker.isOnStablePower());
pw.print("Tracking ");
pw.print(mTrackedTasks.size());
pw.println(":");
for (int i = 0; i < mTrackedTasks.size(); i++) {
final JobStatus js = mTrackedTasks.get(i);
if (!js.shouldDump(filterUid)) {
continue;
}
pw.print(" #");
js.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, js.getSourceUid());
pw.println();
}
while (it.hasNext()) {
JobStatus jobStatus = it.next();
if (jobStatus.shouldDump(filterUid)) {
pw.print("," + String.valueOf(jobStatus.hashCode()));
}
}
pw.println();
}
}

View File

@@ -188,14 +188,20 @@ public class ConnectivityController extends StateController implements
@Override
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
pw.println("Conn.");
pw.println("Connectivity.");
pw.print("Tracking ");
pw.print(mTrackedJobs.size());
pw.println(":");
for (int i = 0; i < mTrackedJobs.size(); i++) {
final JobStatus js = mTrackedJobs.get(i);
if (js.shouldDump(filterUid)) {
pw.println(String.valueOf(js.getJobId() + "," + js.getUid())
+ ": C=" + js.hasConnectivityConstraint()
+ ", UM=" + js.hasUnmeteredConstraint()
+ ", NR=" + js.hasNotRoamingConstraint());
pw.print(" #");
js.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, js.getSourceUid());
pw.print(": C="); pw.print(js.hasConnectivityConstraint());
pw.print(": UM="); pw.print(js.hasUnmeteredConstraint());
pw.print(": NR="); pw.println(js.hasNotRoamingConstraint());
}
}
}

View File

@@ -21,6 +21,7 @@ import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import android.util.TimeUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -323,23 +324,17 @@ public class ContentObserverController extends StateController {
@Override
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
pw.println("Content.");
boolean printed = false;
pw.println("Content:");
Iterator<JobStatus> it = mTrackedTasks.iterator();
while (it.hasNext()) {
JobStatus js = it.next();
if (!js.shouldDump(filterUid)) {
continue;
}
if (!printed) {
pw.print(" ");
printed = true;
} else {
pw.print(",");
}
pw.print(System.identityHashCode(js));
}
if (printed) {
pw.print(" #");
js.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, js.getSourceUid());
pw.println();
}
int N = mObservers.size();
@@ -367,8 +362,10 @@ public class ContentObserverController extends StateController {
pw.println(" Jobs:");
for (int j=0; j<M; j++) {
JobInstance inst = obs.mJobs.valueAt(j);
pw.print(" ");
pw.print(System.identityHashCode(inst.mJobStatus));
pw.print(" #");
inst.mJobStatus.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, inst.mJobStatus.getSourceUid());
if (inst.mChangedAuthorities != null) {
pw.println(":");
if (inst.mTriggerPending) {

View File

@@ -180,13 +180,16 @@ public class DeviceIdleJobsController extends StateController {
if (!jobStatus.shouldDump(filterUid)) {
return;
}
pw.print(" ");
pw.print(" #");
jobStatus.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, jobStatus.getSourceUid());
pw.print(": ");
pw.print(jobStatus.getSourcePackageName());
pw.print(": runnable=");
pw.print(", runnable=");
pw.println((jobStatus.satisfiedConstraints
& JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0);
}
});
pw.println();
}
}

View File

@@ -26,6 +26,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
import com.android.server.am.ActivityManagerService;
@@ -191,15 +192,19 @@ public class IdleController extends StateController {
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
pw.print("Idle: ");
pw.println(mIdleTracker.isIdle() ? "true" : "false");
pw.println(mTrackedTasks.size());
pw.print("Tracking ");
pw.print(mTrackedTasks.size());
pw.println(":");
for (int i = 0; i < mTrackedTasks.size(); i++) {
final JobStatus js = mTrackedTasks.get(i);
if (!js.shouldDump(filterUid)) {
continue;
}
pw.print(" ");
pw.print(String.valueOf(js.getJobId() + "," + js.getUid()));
pw.println("..");
pw.print(" #");
js.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, js.getSourceUid());
pw.println();
}
}
}

View File

@@ -249,6 +249,12 @@ public final class JobStatus {
return job.getId();
}
public void printUniqueId(PrintWriter pw) {
UserHandle.formatUid(pw, callingUid);
pw.print("/");
pw.print(job.getId());
}
public int getNumFailures() {
return numFailures;
}
@@ -410,6 +416,10 @@ public final class JobStatus {
return true;
}
boolean isConstraintSatisfied(int constraint) {
return (satisfiedConstraints&constraint) != 0;
}
public boolean shouldDump(int filterUid) {
return filterUid == -1 || UserHandle.getAppId(getUid()) == filterUid
|| UserHandle.getAppId(getSourceUid()) == filterUid;
@@ -505,10 +515,22 @@ public final class JobStatus {
public String toShortString() {
StringBuilder sb = new StringBuilder();
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" jId=");
sb.append(" #");
UserHandle.formatUid(sb, callingUid);
sb.append("/");
sb.append(job.getId());
sb.append(' ');
UserHandle.formatUid(sb, callingUid);
sb.append(batteryName);
return sb.toString();
}
/**
* Convenience function to identify a job uniquely without pulling all the data that
* {@link #toString()} returns.
*/
public String toShortStringExceptUniqueId() {
StringBuilder sb = new StringBuilder();
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(' ');
sb.append(batteryName);
return sb.toString();

View File

@@ -20,7 +20,10 @@ import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
import android.content.Context;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Slog;
import android.util.TimeUtils;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
@@ -39,9 +42,9 @@ public class TimeController extends StateController {
private static final String TAG = "JobScheduler.Time";
/** Deadline alarm tag for logging purposes */
private final String DEADLINE_TAG = "JobScheduler.deadline";
private final String DEADLINE_TAG = "*job.deadline*";
/** Delay alarm tag for logging purposes */
private final String DELAY_TAG = "JobScheduler.delay";
private final String DELAY_TAG = "*job.delay*";
private long mNextJobExpiredElapsedMillis;
private long mNextDelayExpiredElapsedMillis;
@@ -91,7 +94,8 @@ public class TimeController extends StateController {
it.add(job);
maybeUpdateAlarmsLocked(
job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE);
job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE,
job.getSourceUid());
}
}
@@ -134,6 +138,7 @@ public class TimeController extends StateController {
private void checkExpiredDeadlinesAndResetAlarm() {
synchronized (mLock) {
long nextExpiryTime = Long.MAX_VALUE;
int nextExpiryUid = 0;
final long nowElapsedMillis = SystemClock.elapsedRealtime();
Iterator<JobStatus> it = mTrackedJobs.iterator();
@@ -153,10 +158,11 @@ public class TimeController extends StateController {
it.remove();
} else { // Sorted by expiry time, so take the next one and stop.
nextExpiryTime = jobDeadline;
nextExpiryUid = job.getSourceUid();
break;
}
}
setDeadlineExpiredAlarmLocked(nextExpiryTime);
setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryUid);
}
}
@@ -168,6 +174,7 @@ public class TimeController extends StateController {
synchronized (mLock) {
final long nowElapsedMillis = SystemClock.elapsedRealtime();
long nextDelayTime = Long.MAX_VALUE;
int nextDelayUid = 0;
boolean ready = false;
Iterator<JobStatus> it = mTrackedJobs.iterator();
while (it.hasNext()) {
@@ -184,25 +191,29 @@ public class TimeController extends StateController {
if (job.isReady()) {
ready = true;
}
} else { // Keep going through list to get next delay time.
} else if (!job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)) {
// If this job still doesn't have its delay constraint satisfied,
// then see if it is the next upcoming delay time for the alarm.
if (nextDelayTime > jobDelayTime) {
nextDelayTime = jobDelayTime;
nextDelayUid = job.getSourceUid();
}
}
}
if (ready) {
mStateChangedListener.onControllerStateChanged();
}
setDelayExpiredAlarmLocked(nextDelayTime);
setDelayExpiredAlarmLocked(nextDelayTime, nextDelayUid);
}
}
private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed) {
private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed,
int uid) {
if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
setDelayExpiredAlarmLocked(delayExpiredElapsed);
setDelayExpiredAlarmLocked(delayExpiredElapsed, uid);
}
if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) {
setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed);
setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, uid);
}
}
@@ -211,11 +222,11 @@ public class TimeController extends StateController {
* delay will expire.
* This alarm <b>will</b> wake up the phone.
*/
private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis) {
private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) {
alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener,
mNextDelayExpiredElapsedMillis);
mNextDelayExpiredElapsedMillis, uid);
}
/**
@@ -223,11 +234,11 @@ public class TimeController extends StateController {
* deadline will expire.
* This alarm <b>will</b> wake up the phone.
*/
private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis) {
private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) {
alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis;
updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener,
mNextJobExpiredElapsedMillis);
mNextJobExpiredElapsedMillis, uid);
}
private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
@@ -239,7 +250,7 @@ public class TimeController extends StateController {
}
private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener,
long alarmTimeElapsed) {
long alarmTimeElapsed, int uid) {
ensureAlarmServiceLocked();
if (alarmTimeElapsed == Long.MAX_VALUE) {
mAlarmService.cancel(listener);
@@ -248,7 +259,7 @@ public class TimeController extends StateController {
Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed);
}
mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed,
tag, listener, null);
AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, new WorkSource(uid));
}
}
@@ -277,20 +288,39 @@ public class TimeController extends StateController {
@Override
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
final long nowElapsed = SystemClock.elapsedRealtime();
pw.println("Alarms (" + SystemClock.elapsedRealtime() + ")");
pw.println(
"Next delay alarm in " + (mNextDelayExpiredElapsedMillis - nowElapsed)/1000 + "s");
pw.println("Next deadline alarm in " + (mNextJobExpiredElapsedMillis - nowElapsed)/1000
+ "s");
pw.println("Tracking:");
pw.print("Alarms: now=");
pw.print(SystemClock.elapsedRealtime());
pw.println();
pw.print("Next delay alarm in ");
TimeUtils.formatDuration(mNextDelayExpiredElapsedMillis, nowElapsed, pw);
pw.println();
pw.print("Next deadline alarm in ");
TimeUtils.formatDuration(mNextJobExpiredElapsedMillis, nowElapsed, pw);
pw.println();
pw.print("Tracking ");
pw.print(mTrackedJobs.size());
pw.println(":");
for (JobStatus ts : mTrackedJobs) {
if (!ts.shouldDump(filterUid)) {
continue;
}
pw.println(String.valueOf(ts.getJobId() + "," + ts.getUid())
+ ": (" + (ts.hasTimingDelayConstraint() ? ts.getEarliestRunTime() : "N/A")
+ ", " + (ts.hasDeadlineConstraint() ?ts.getLatestRunTimeElapsed() : "N/A")
+ ")");
pw.print(" #");
ts.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, ts.getSourceUid());
pw.print(": Delay=");
if (ts.hasTimingDelayConstraint()) {
TimeUtils.formatDuration(ts.getEarliestRunTime(), nowElapsed, pw);
} else {
pw.print("N/A");
}
pw.print(", Deadline=");
if (ts.hasDeadlineConstraint()) {
TimeUtils.formatDuration(ts.getLatestRunTimeElapsed(), nowElapsed, pw);
} else {
pw.print("N/A");
}
pw.println();
}
}
}