Loading core/proto/android/server/jobscheduler.proto +16 −1 Original line number Diff line number Diff line Loading @@ -175,6 +175,11 @@ message ConstantsProto { // 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. optional int32 min_ready_jobs_count = 7; // Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early. optional int32 min_ready_non_active_jobs_count = 29; // Don't batch a non-ACTIVE job if it's been delayed due to force batching attempts for // at least this amount of time. optional int64 max_non_active_job_batch_delay_ms = 30; // This is the job execution factor that is considered to be heavy use of // the system. optional double heavy_use_factor = 8; Loading Loading @@ -307,6 +312,8 @@ message ConstantsProto { // In this time after screen turns on, we increase job concurrency. optional int32 screen_off_job_concurrency_increase_delay_ms = 28; // Next tag: 31 } // Next tag: 4 Loading Loading @@ -938,7 +945,15 @@ message JobStatusDumpProto { optional int64 internal_flags = 24; // Next tag: 28 // Amount of time since this job was first deferred due to standby bucketing policy. Will be // 0 if this job was never deferred. optional int64 time_since_first_deferral_ms = 28; // Amount of time since JobScheduler first tried to force batch this job. Will be 0 if there // was no attempt. optional int64 time_since_first_force_batch_attempt_ms = 29; // Next tag: 30 } // Dump from com.android.server.job.JobConcurrencyManager. Loading services/core/java/com/android/server/job/JobSchedulerService.java +87 −41 Original line number Diff line number Diff line Loading @@ -446,6 +446,10 @@ public class JobSchedulerService extends com.android.server.SystemService 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_MIN_READY_NON_ACTIVE_JOBS_COUNT = "min_ready_non_active_jobs_count"; private static final String KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = "max_non_active_job_batch_delay_ms"; private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor"; private static final String KEY_MODERATE_USE_FACTOR = "moderate_use_factor"; Loading Loading @@ -476,6 +480,8 @@ public class JobSchedulerService extends com.android.server.SystemService 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 int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5; private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS; private static final float DEFAULT_HEAVY_USE_FACTOR = .9f; private static final float DEFAULT_MODERATE_USE_FACTOR = .5f; private static final int DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT = Integer.MAX_VALUE; Loading Loading @@ -527,6 +533,18 @@ public class JobSchedulerService extends com.android.server.SystemService * a much better mechanism. */ int MIN_READY_JOBS_COUNT = DEFAULT_MIN_READY_JOBS_COUNT; /** * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early. */ int MIN_READY_NON_ACTIVE_JOBS_COUNT = DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT; /** * Don't batch a non-ACTIVE job if it's been delayed due to force batching attempts for * at least this amount of time. */ long MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS; /** * This is the job execution factor that is considered to be heavy use of the system. */ Loading Loading @@ -660,6 +678,12 @@ public class JobSchedulerService extends com.android.server.SystemService DEFAULT_MIN_CONTENT_COUNT); MIN_READY_JOBS_COUNT = mParser.getInt(KEY_MIN_READY_JOBS_COUNT, DEFAULT_MIN_READY_JOBS_COUNT); MIN_READY_NON_ACTIVE_JOBS_COUNT = mParser.getInt( KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT, DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT); MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = mParser.getLong( KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS, DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS); HEAVY_USE_FACTOR = mParser.getFloat(KEY_HEAVY_USE_FACTOR, DEFAULT_HEAVY_USE_FACTOR); MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR, Loading Loading @@ -710,6 +734,10 @@ public class JobSchedulerService extends com.android.server.SystemService pw.printPair(KEY_MIN_CONNECTIVITY_COUNT, MIN_CONNECTIVITY_COUNT).println(); pw.printPair(KEY_MIN_CONTENT_COUNT, MIN_CONTENT_COUNT).println(); pw.printPair(KEY_MIN_READY_JOBS_COUNT, MIN_READY_JOBS_COUNT).println(); pw.printPair(KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT, MIN_READY_NON_ACTIVE_JOBS_COUNT).println(); pw.printPair(KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS, MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS).println(); pw.printPair(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println(); pw.printPair(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println(); Loading Loading @@ -752,6 +780,10 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(ConstantsProto.MIN_CONNECTIVITY_COUNT, MIN_CONNECTIVITY_COUNT); proto.write(ConstantsProto.MIN_CONTENT_COUNT, MIN_CONTENT_COUNT); proto.write(ConstantsProto.MIN_READY_JOBS_COUNT, MIN_READY_JOBS_COUNT); proto.write(ConstantsProto.MIN_READY_NON_ACTIVE_JOBS_COUNT, MIN_READY_NON_ACTIVE_JOBS_COUNT); proto.write(ConstantsProto.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS, MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS); proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR); proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR); Loading Loading @@ -1989,7 +2021,7 @@ public class JobSchedulerService extends com.android.server.SystemService } final class ReadyJobQueueFunctor implements Consumer<JobStatus> { ArrayList<JobStatus> newReadyJobs; final ArrayList<JobStatus> newReadyJobs = new ArrayList<>(); @Override public void accept(JobStatus job) { Loading @@ -1997,9 +2029,6 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, " queued " + job.toShortString()); } if (newReadyJobs == null) { newReadyJobs = new ArrayList<JobStatus>(); } newReadyJobs.add(job); } else { evaluateControllerStatesLocked(job); Loading @@ -2007,14 +2036,13 @@ public class JobSchedulerService extends com.android.server.SystemService } public void postProcess() { if (newReadyJobs != null) { noteJobsPending(newReadyJobs); mPendingJobs.addAll(newReadyJobs); if (mPendingJobs.size() > 1) { mPendingJobs.sort(mEnqueueTimeComparator); } } newReadyJobs = null; newReadyJobs.clear(); } } private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor(); Loading @@ -2031,7 +2059,9 @@ public class JobSchedulerService extends com.android.server.SystemService int backoffCount; int connectivityCount; int contentCount; List<JobStatus> runnableJobs; int forceBatchedCount; int unbatchedCount; final List<JobStatus> runnableJobs = new ArrayList<>(); public MaybeReadyJobQueueFunctor() { reset(); Loading @@ -2051,6 +2081,18 @@ public class JobSchedulerService extends com.android.server.SystemService } } catch (RemoteException e) { } if (mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1 && job.getStandbyBucket() != ACTIVE_INDEX && (job.getFirstForceBatchedTimeElapsed() == 0 || sElapsedRealtimeClock.millis() - job.getFirstForceBatchedTimeElapsed() < mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS)) { // Force batching non-ACTIVE jobs. Don't include them in the other counts. forceBatchedCount++; if (job.getFirstForceBatchedTimeElapsed() == 0) { job.setFirstForceBatchedTimeElapsed(sElapsedRealtimeClock.millis()); } } else { unbatchedCount++; if (job.getNumFailures() > 0) { backoffCount++; } Loading @@ -2072,8 +2114,6 @@ public class JobSchedulerService extends com.android.server.SystemService if (job.hasContentTriggerConstraint()) { contentCount++; } if (runnableJobs == null) { runnableJobs = new ArrayList<>(); } runnableJobs.add(job); } else { Loading @@ -2089,8 +2129,9 @@ public class JobSchedulerService extends com.android.server.SystemService batteryNotLowCount >= mConstants.MIN_BATTERY_NOT_LOW_COUNT || storageNotLowCount >= mConstants.MIN_STORAGE_NOT_LOW_COUNT || contentCount >= mConstants.MIN_CONTENT_COUNT || (runnableJobs != null && runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) { forceBatchedCount >= mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT || (unbatchedCount > 0 && (unbatchedCount + forceBatchedCount) >= mConstants.MIN_READY_JOBS_COUNT)) { if (DEBUG) { Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running jobs."); } Loading @@ -2109,7 +2150,8 @@ public class JobSchedulerService extends com.android.server.SystemService reset(); } private void reset() { @VisibleForTesting void reset() { chargingCount = 0; idleCount = 0; backoffCount = 0; Loading @@ -2117,7 +2159,9 @@ public class JobSchedulerService extends com.android.server.SystemService batteryNotLowCount = 0; storageNotLowCount = 0; contentCount = 0; runnableJobs = null; forceBatchedCount = 0; unbatchedCount = 0; runnableJobs.clear(); } } private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor(); Loading Loading @@ -2226,7 +2270,8 @@ public class JobSchedulerService extends com.android.server.SystemService * - The job's standby bucket has come due to be runnable. * - The component is enabled and runnable. */ private boolean isReadyToBeExecutedLocked(JobStatus job) { @VisibleForTesting boolean isReadyToBeExecutedLocked(JobStatus job) { final boolean jobReady = job.isReady(); if (DEBUG) { Loading Loading @@ -2358,7 +2403,8 @@ public class JobSchedulerService extends com.android.server.SystemService return !appIsBad; } private void evaluateControllerStatesLocked(final JobStatus job) { @VisibleForTesting void evaluateControllerStatesLocked(final JobStatus job) { for (int c = mControllers.size() - 1; c >= 0; --c) { final StateController sc = mControllers.get(c); sc.evaluateStateLocked(job); Loading services/core/java/com/android/server/job/controllers/JobStatus.java +26 −0 Original line number Diff line number Diff line Loading @@ -186,6 +186,9 @@ public final class JobStatus { */ private long whenStandbyDeferred; /** The first time this job was force batched. */ private long mFirstForceBatchedTimeElapsed; // Constraints. final int requiredConstraints; private final int mRequiredConstraintsOfInterest; Loading Loading @@ -729,6 +732,18 @@ public final class JobStatus { whenStandbyDeferred = now; } /** * Returns the first time this job was force batched, in the elapsed realtime timebase. Will be * 0 if this job was never force batched. */ public long getFirstForceBatchedTimeElapsed() { return mFirstForceBatchedTimeElapsed; } public void setFirstForceBatchedTimeElapsed(long now) { mFirstForceBatchedTimeElapsed = now; } public String getSourceTag() { return sourceTag; } Loading Loading @@ -1625,6 +1640,12 @@ public final class JobStatus { TimeUtils.formatDuration(whenStandbyDeferred, elapsedRealtimeMillis, pw); pw.println(); } if (mFirstForceBatchedTimeElapsed != 0) { pw.print(prefix); pw.print(" Time since first force batch attempt: "); TimeUtils.formatDuration(mFirstForceBatchedTimeElapsed, elapsedRealtimeMillis, pw); pw.println(); } pw.print(prefix); pw.print("Enqueue time: "); TimeUtils.formatDuration(enqueueTime, elapsedRealtimeMillis, pw); pw.println(); Loading Loading @@ -1817,6 +1838,11 @@ public final class JobStatus { proto.write(JobStatusDumpProto.STANDBY_BUCKET, standbyBucket); proto.write(JobStatusDumpProto.ENQUEUE_DURATION_MS, elapsedRealtimeMillis - enqueueTime); proto.write(JobStatusDumpProto.TIME_SINCE_FIRST_DEFERRAL_MS, whenStandbyDeferred == 0 ? 0 : elapsedRealtimeMillis - whenStandbyDeferred); proto.write(JobStatusDumpProto.TIME_SINCE_FIRST_FORCE_BATCH_ATTEMPT_MS, mFirstForceBatchedTimeElapsed == 0 ? 0 : elapsedRealtimeMillis - mFirstForceBatchedTimeElapsed); if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME) { proto.write(JobStatusDumpProto.TIME_UNTIL_EARLIEST_RUNTIME_MS, 0); } else { Loading services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +84 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; import static com.android.server.job.JobSchedulerService.RARE_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import static org.junit.Assert.assertEquals; Loading Loading @@ -662,4 +664,86 @@ public class JobSchedulerServiceTest { assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime()); assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed()); } /** Tests that rare job batching works as expected. */ @Test public void testRareJobBatching() { spyOn(mService); doNothing().when(mService).evaluateControllerStatesLocked(any()); doNothing().when(mService).noteJobsPending(any()); doReturn(true).when(mService).isReadyToBeExecutedLocked(any()); advanceElapsedClock(24 * HOUR_IN_MILLIS); JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor = mService.new MaybeReadyJobQueueFunctor(); mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT = 5; mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS; mService.mConstants.MIN_CONNECTIVITY_COUNT = 2; mService.mConstants.MIN_READY_JOBS_COUNT = 1; JobStatus job = createJobStatus( "testRareJobBatching", createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); job.setStandbyBucket(RARE_INDEX); // Not enough RARE jobs to run. mService.mPendingJobs.clear(); maybeQueueFunctor.reset(); for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) { maybeQueueFunctor.accept(job); assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount); assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size()); assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed()); } maybeQueueFunctor.postProcess(); assertEquals(0, mService.mPendingJobs.size()); // Enough RARE jobs to run. mService.mPendingJobs.clear(); maybeQueueFunctor.reset(); for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT; ++i) { maybeQueueFunctor.accept(job); assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount); assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size()); assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed()); } maybeQueueFunctor.postProcess(); assertEquals(5, mService.mPendingJobs.size()); // Not enough RARE jobs to run, but a non-batched job saves the day. mService.mPendingJobs.clear(); maybeQueueFunctor.reset(); JobStatus activeJob = createJobStatus( "testRareJobBatching", createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); activeJob.setStandbyBucket(ACTIVE_INDEX); for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) { maybeQueueFunctor.accept(job); assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount); assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size()); assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed()); } maybeQueueFunctor.accept(activeJob); maybeQueueFunctor.postProcess(); assertEquals(3, mService.mPendingJobs.size()); // Not enough RARE jobs to run, but an old RARE job saves the day. mService.mPendingJobs.clear(); maybeQueueFunctor.reset(); JobStatus oldRareJob = createJobStatus("testRareJobBatching", createJobInfo()); oldRareJob.setStandbyBucket(RARE_INDEX); final long oldBatchTime = sElapsedRealtimeClock.millis() - 2 * mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS; oldRareJob.setFirstForceBatchedTimeElapsed(oldBatchTime); for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) { maybeQueueFunctor.accept(job); assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount); assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size()); assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed()); } maybeQueueFunctor.accept(oldRareJob); assertEquals(oldBatchTime, oldRareJob.getFirstForceBatchedTimeElapsed()); maybeQueueFunctor.postProcess(); assertEquals(3, mService.mPendingJobs.size()); } } Loading
core/proto/android/server/jobscheduler.proto +16 −1 Original line number Diff line number Diff line Loading @@ -175,6 +175,11 @@ message ConstantsProto { // 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. optional int32 min_ready_jobs_count = 7; // Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early. optional int32 min_ready_non_active_jobs_count = 29; // Don't batch a non-ACTIVE job if it's been delayed due to force batching attempts for // at least this amount of time. optional int64 max_non_active_job_batch_delay_ms = 30; // This is the job execution factor that is considered to be heavy use of // the system. optional double heavy_use_factor = 8; Loading Loading @@ -307,6 +312,8 @@ message ConstantsProto { // In this time after screen turns on, we increase job concurrency. optional int32 screen_off_job_concurrency_increase_delay_ms = 28; // Next tag: 31 } // Next tag: 4 Loading Loading @@ -938,7 +945,15 @@ message JobStatusDumpProto { optional int64 internal_flags = 24; // Next tag: 28 // Amount of time since this job was first deferred due to standby bucketing policy. Will be // 0 if this job was never deferred. optional int64 time_since_first_deferral_ms = 28; // Amount of time since JobScheduler first tried to force batch this job. Will be 0 if there // was no attempt. optional int64 time_since_first_force_batch_attempt_ms = 29; // Next tag: 30 } // Dump from com.android.server.job.JobConcurrencyManager. Loading
services/core/java/com/android/server/job/JobSchedulerService.java +87 −41 Original line number Diff line number Diff line Loading @@ -446,6 +446,10 @@ public class JobSchedulerService extends com.android.server.SystemService 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_MIN_READY_NON_ACTIVE_JOBS_COUNT = "min_ready_non_active_jobs_count"; private static final String KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = "max_non_active_job_batch_delay_ms"; private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor"; private static final String KEY_MODERATE_USE_FACTOR = "moderate_use_factor"; Loading Loading @@ -476,6 +480,8 @@ public class JobSchedulerService extends com.android.server.SystemService 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 int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5; private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS; private static final float DEFAULT_HEAVY_USE_FACTOR = .9f; private static final float DEFAULT_MODERATE_USE_FACTOR = .5f; private static final int DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT = Integer.MAX_VALUE; Loading Loading @@ -527,6 +533,18 @@ public class JobSchedulerService extends com.android.server.SystemService * a much better mechanism. */ int MIN_READY_JOBS_COUNT = DEFAULT_MIN_READY_JOBS_COUNT; /** * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early. */ int MIN_READY_NON_ACTIVE_JOBS_COUNT = DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT; /** * Don't batch a non-ACTIVE job if it's been delayed due to force batching attempts for * at least this amount of time. */ long MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS; /** * This is the job execution factor that is considered to be heavy use of the system. */ Loading Loading @@ -660,6 +678,12 @@ public class JobSchedulerService extends com.android.server.SystemService DEFAULT_MIN_CONTENT_COUNT); MIN_READY_JOBS_COUNT = mParser.getInt(KEY_MIN_READY_JOBS_COUNT, DEFAULT_MIN_READY_JOBS_COUNT); MIN_READY_NON_ACTIVE_JOBS_COUNT = mParser.getInt( KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT, DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT); MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = mParser.getLong( KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS, DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS); HEAVY_USE_FACTOR = mParser.getFloat(KEY_HEAVY_USE_FACTOR, DEFAULT_HEAVY_USE_FACTOR); MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR, Loading Loading @@ -710,6 +734,10 @@ public class JobSchedulerService extends com.android.server.SystemService pw.printPair(KEY_MIN_CONNECTIVITY_COUNT, MIN_CONNECTIVITY_COUNT).println(); pw.printPair(KEY_MIN_CONTENT_COUNT, MIN_CONTENT_COUNT).println(); pw.printPair(KEY_MIN_READY_JOBS_COUNT, MIN_READY_JOBS_COUNT).println(); pw.printPair(KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT, MIN_READY_NON_ACTIVE_JOBS_COUNT).println(); pw.printPair(KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS, MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS).println(); pw.printPair(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println(); pw.printPair(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println(); Loading Loading @@ -752,6 +780,10 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(ConstantsProto.MIN_CONNECTIVITY_COUNT, MIN_CONNECTIVITY_COUNT); proto.write(ConstantsProto.MIN_CONTENT_COUNT, MIN_CONTENT_COUNT); proto.write(ConstantsProto.MIN_READY_JOBS_COUNT, MIN_READY_JOBS_COUNT); proto.write(ConstantsProto.MIN_READY_NON_ACTIVE_JOBS_COUNT, MIN_READY_NON_ACTIVE_JOBS_COUNT); proto.write(ConstantsProto.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS, MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS); proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR); proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR); Loading Loading @@ -1989,7 +2021,7 @@ public class JobSchedulerService extends com.android.server.SystemService } final class ReadyJobQueueFunctor implements Consumer<JobStatus> { ArrayList<JobStatus> newReadyJobs; final ArrayList<JobStatus> newReadyJobs = new ArrayList<>(); @Override public void accept(JobStatus job) { Loading @@ -1997,9 +2029,6 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, " queued " + job.toShortString()); } if (newReadyJobs == null) { newReadyJobs = new ArrayList<JobStatus>(); } newReadyJobs.add(job); } else { evaluateControllerStatesLocked(job); Loading @@ -2007,14 +2036,13 @@ public class JobSchedulerService extends com.android.server.SystemService } public void postProcess() { if (newReadyJobs != null) { noteJobsPending(newReadyJobs); mPendingJobs.addAll(newReadyJobs); if (mPendingJobs.size() > 1) { mPendingJobs.sort(mEnqueueTimeComparator); } } newReadyJobs = null; newReadyJobs.clear(); } } private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor(); Loading @@ -2031,7 +2059,9 @@ public class JobSchedulerService extends com.android.server.SystemService int backoffCount; int connectivityCount; int contentCount; List<JobStatus> runnableJobs; int forceBatchedCount; int unbatchedCount; final List<JobStatus> runnableJobs = new ArrayList<>(); public MaybeReadyJobQueueFunctor() { reset(); Loading @@ -2051,6 +2081,18 @@ public class JobSchedulerService extends com.android.server.SystemService } } catch (RemoteException e) { } if (mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1 && job.getStandbyBucket() != ACTIVE_INDEX && (job.getFirstForceBatchedTimeElapsed() == 0 || sElapsedRealtimeClock.millis() - job.getFirstForceBatchedTimeElapsed() < mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS)) { // Force batching non-ACTIVE jobs. Don't include them in the other counts. forceBatchedCount++; if (job.getFirstForceBatchedTimeElapsed() == 0) { job.setFirstForceBatchedTimeElapsed(sElapsedRealtimeClock.millis()); } } else { unbatchedCount++; if (job.getNumFailures() > 0) { backoffCount++; } Loading @@ -2072,8 +2114,6 @@ public class JobSchedulerService extends com.android.server.SystemService if (job.hasContentTriggerConstraint()) { contentCount++; } if (runnableJobs == null) { runnableJobs = new ArrayList<>(); } runnableJobs.add(job); } else { Loading @@ -2089,8 +2129,9 @@ public class JobSchedulerService extends com.android.server.SystemService batteryNotLowCount >= mConstants.MIN_BATTERY_NOT_LOW_COUNT || storageNotLowCount >= mConstants.MIN_STORAGE_NOT_LOW_COUNT || contentCount >= mConstants.MIN_CONTENT_COUNT || (runnableJobs != null && runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) { forceBatchedCount >= mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT || (unbatchedCount > 0 && (unbatchedCount + forceBatchedCount) >= mConstants.MIN_READY_JOBS_COUNT)) { if (DEBUG) { Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running jobs."); } Loading @@ -2109,7 +2150,8 @@ public class JobSchedulerService extends com.android.server.SystemService reset(); } private void reset() { @VisibleForTesting void reset() { chargingCount = 0; idleCount = 0; backoffCount = 0; Loading @@ -2117,7 +2159,9 @@ public class JobSchedulerService extends com.android.server.SystemService batteryNotLowCount = 0; storageNotLowCount = 0; contentCount = 0; runnableJobs = null; forceBatchedCount = 0; unbatchedCount = 0; runnableJobs.clear(); } } private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor(); Loading Loading @@ -2226,7 +2270,8 @@ public class JobSchedulerService extends com.android.server.SystemService * - The job's standby bucket has come due to be runnable. * - The component is enabled and runnable. */ private boolean isReadyToBeExecutedLocked(JobStatus job) { @VisibleForTesting boolean isReadyToBeExecutedLocked(JobStatus job) { final boolean jobReady = job.isReady(); if (DEBUG) { Loading Loading @@ -2358,7 +2403,8 @@ public class JobSchedulerService extends com.android.server.SystemService return !appIsBad; } private void evaluateControllerStatesLocked(final JobStatus job) { @VisibleForTesting void evaluateControllerStatesLocked(final JobStatus job) { for (int c = mControllers.size() - 1; c >= 0; --c) { final StateController sc = mControllers.get(c); sc.evaluateStateLocked(job); Loading
services/core/java/com/android/server/job/controllers/JobStatus.java +26 −0 Original line number Diff line number Diff line Loading @@ -186,6 +186,9 @@ public final class JobStatus { */ private long whenStandbyDeferred; /** The first time this job was force batched. */ private long mFirstForceBatchedTimeElapsed; // Constraints. final int requiredConstraints; private final int mRequiredConstraintsOfInterest; Loading Loading @@ -729,6 +732,18 @@ public final class JobStatus { whenStandbyDeferred = now; } /** * Returns the first time this job was force batched, in the elapsed realtime timebase. Will be * 0 if this job was never force batched. */ public long getFirstForceBatchedTimeElapsed() { return mFirstForceBatchedTimeElapsed; } public void setFirstForceBatchedTimeElapsed(long now) { mFirstForceBatchedTimeElapsed = now; } public String getSourceTag() { return sourceTag; } Loading Loading @@ -1625,6 +1640,12 @@ public final class JobStatus { TimeUtils.formatDuration(whenStandbyDeferred, elapsedRealtimeMillis, pw); pw.println(); } if (mFirstForceBatchedTimeElapsed != 0) { pw.print(prefix); pw.print(" Time since first force batch attempt: "); TimeUtils.formatDuration(mFirstForceBatchedTimeElapsed, elapsedRealtimeMillis, pw); pw.println(); } pw.print(prefix); pw.print("Enqueue time: "); TimeUtils.formatDuration(enqueueTime, elapsedRealtimeMillis, pw); pw.println(); Loading Loading @@ -1817,6 +1838,11 @@ public final class JobStatus { proto.write(JobStatusDumpProto.STANDBY_BUCKET, standbyBucket); proto.write(JobStatusDumpProto.ENQUEUE_DURATION_MS, elapsedRealtimeMillis - enqueueTime); proto.write(JobStatusDumpProto.TIME_SINCE_FIRST_DEFERRAL_MS, whenStandbyDeferred == 0 ? 0 : elapsedRealtimeMillis - whenStandbyDeferred); proto.write(JobStatusDumpProto.TIME_SINCE_FIRST_FORCE_BATCH_ATTEMPT_MS, mFirstForceBatchedTimeElapsed == 0 ? 0 : elapsedRealtimeMillis - mFirstForceBatchedTimeElapsed); if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME) { proto.write(JobStatusDumpProto.TIME_UNTIL_EARLIEST_RUNTIME_MS, 0); } else { Loading
services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +84 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; import static com.android.server.job.JobSchedulerService.RARE_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import static org.junit.Assert.assertEquals; Loading Loading @@ -662,4 +664,86 @@ public class JobSchedulerServiceTest { assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime()); assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed()); } /** Tests that rare job batching works as expected. */ @Test public void testRareJobBatching() { spyOn(mService); doNothing().when(mService).evaluateControllerStatesLocked(any()); doNothing().when(mService).noteJobsPending(any()); doReturn(true).when(mService).isReadyToBeExecutedLocked(any()); advanceElapsedClock(24 * HOUR_IN_MILLIS); JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor = mService.new MaybeReadyJobQueueFunctor(); mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT = 5; mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS; mService.mConstants.MIN_CONNECTIVITY_COUNT = 2; mService.mConstants.MIN_READY_JOBS_COUNT = 1; JobStatus job = createJobStatus( "testRareJobBatching", createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); job.setStandbyBucket(RARE_INDEX); // Not enough RARE jobs to run. mService.mPendingJobs.clear(); maybeQueueFunctor.reset(); for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) { maybeQueueFunctor.accept(job); assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount); assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size()); assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed()); } maybeQueueFunctor.postProcess(); assertEquals(0, mService.mPendingJobs.size()); // Enough RARE jobs to run. mService.mPendingJobs.clear(); maybeQueueFunctor.reset(); for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT; ++i) { maybeQueueFunctor.accept(job); assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount); assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size()); assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed()); } maybeQueueFunctor.postProcess(); assertEquals(5, mService.mPendingJobs.size()); // Not enough RARE jobs to run, but a non-batched job saves the day. mService.mPendingJobs.clear(); maybeQueueFunctor.reset(); JobStatus activeJob = createJobStatus( "testRareJobBatching", createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); activeJob.setStandbyBucket(ACTIVE_INDEX); for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) { maybeQueueFunctor.accept(job); assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount); assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size()); assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed()); } maybeQueueFunctor.accept(activeJob); maybeQueueFunctor.postProcess(); assertEquals(3, mService.mPendingJobs.size()); // Not enough RARE jobs to run, but an old RARE job saves the day. mService.mPendingJobs.clear(); maybeQueueFunctor.reset(); JobStatus oldRareJob = createJobStatus("testRareJobBatching", createJobInfo()); oldRareJob.setStandbyBucket(RARE_INDEX); final long oldBatchTime = sElapsedRealtimeClock.millis() - 2 * mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS; oldRareJob.setFirstForceBatchedTimeElapsed(oldBatchTime); for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) { maybeQueueFunctor.accept(job); assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount); assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size()); assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed()); } maybeQueueFunctor.accept(oldRareJob); assertEquals(oldBatchTime, oldRareJob.getFirstForceBatchedTimeElapsed()); maybeQueueFunctor.postProcess(); assertEquals(3, mService.mPendingJobs.size()); } }