From e9a988caca733d2f292991a52a0047685a69812f Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Fri, 27 May 2016 17:59:40 -0700 Subject: [PATCH] Work on issue #28942589: Tune job scheduler We now have a new settings key that provides all of the existing tuning parameters, plus some newly redone ones for dealing with different memory levels. Changed the minimum batching for overall jobs from 2 to 1, so we will never get in the way of immediately scheduling jobs when the developer asks for this. We should now be able to rely on the doze modes to do better batching of jobs for us when it is really important. Also work on issue #28981330: Excessive JobScheduler wakeup alarms. Use a work source with scheduled alarms to blame them on the app whose job they are being scheduled for, and add a check for whether a job's timing constraint has been satisfied before considering it a possible candidate for the next alarm. (If it is satisified, the time is in the past, so we should not schedule an alarm for it.) Finally clean up a bunch of the dumpsys output to make it easier to understand. Change-Id: I06cf2c1310448f47cf386f393e9b267335fabaeb --- core/java/android/app/AlarmManager.java | 16 + core/java/android/app/job/JobInfo.java | 17 +- core/java/android/provider/Settings.java | 30 ++ .../java/android/util/KeyValueListParser.java | 18 + .../android/server/job/JobPackageTracker.java | 20 + .../server/job/JobSchedulerService.java | 353 ++++++++++++++---- .../java/com/android/server/job/JobStore.java | 12 +- .../job/controllers/AppIdleController.java | 21 +- .../job/controllers/BatteryController.java | 29 +- .../controllers/ConnectivityController.java | 16 +- .../ContentObserverController.java | 23 +- .../controllers/DeviceIdleJobsController.java | 9 +- .../job/controllers/IdleController.java | 13 +- .../server/job/controllers/JobStatus.java | 26 +- .../job/controllers/TimeController.java | 80 ++-- 15 files changed, 529 insertions(+), 154 deletions(-) diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index 02dcc5c7e3bd2..f9df4f4cfcf6c 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -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. + *

+ * 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. diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java index 61790ea92ed9d..63c1dbb99d824 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -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; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index f867fb0246867..e408cca4e330d 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -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: + * + *

+         * 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)
+         * 
+ * + *

+ * 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: diff --git a/core/java/android/util/KeyValueListParser.java b/core/java/android/util/KeyValueListParser.java index 4abdde00e6c69..e4c025dece49c 100644 --- a/core/java/android/util/KeyValueListParser.java +++ b/core/java/android/util/KeyValueListParser.java @@ -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. diff --git a/services/core/java/com/android/server/job/JobPackageTracker.java b/services/core/java/com/android/server/job/JobPackageTracker.java index eb5edd38d8a91..ba96b7494bba5 100644 --- a/services/core/java/com/android/server/job/JobPackageTracker.java +++ b/services/core/java/com/android/server/job/JobPackageTracker.java @@ -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(); diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 1b8eccb431f54..5e212b0ef6631 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -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= 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 0) { - mJobs.forEachJob(new JobStatusFunctor() { - private int index = 0; - + final List jobs = mJobs.mJobSet.getAllJobs(); + Collections.sort(jobs, new Comparator() { @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."); } diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java index 1f7d3128d6556..602b9c755e884 100644 --- a/services/core/java/com/android/server/job/JobStore.java +++ b/services/core/java/com/android/server/job/JobStore.java @@ -844,8 +844,16 @@ public class JobStore { // Inefficient; use only for testing public List getAllJobs() { ArrayList allJobs = new ArrayList(size()); - for (int i = mJobs.size(); i >= 0; i--) { - allJobs.addAll(mJobs.valueAt(i)); + for (int i = mJobs.size() - 1; i >= 0; i--) { + ArraySet 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; } diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java index 759303542aecb..a23af3541e196 100644 --- a/services/core/java/com/android/server/job/controllers/AppIdleController.java +++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java @@ -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) { diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java index a0cb25fb68700..f6b8ef4387b88 100644 --- a/services/core/java/com/android/server/job/controllers/BatteryController.java +++ b/services/core/java/com/android/server/job/controllers/BatteryController.java @@ -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 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(); } } diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java index 7d28633104cdc..2ff880c5f19ca 100644 --- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java @@ -187,14 +187,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()); } } } diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java index c1d7c5826a4fe..26660e82e8410 100644 --- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java +++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java @@ -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 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 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 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 will 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 will 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(); } } } \ No newline at end of file