Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c6e7f9ae authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Force batching non-ACTIVE jobs."

parents 791331cf 41cb3328
Loading
Loading
Loading
Loading
+16 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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
@@ -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.
+87 −41
Original line number Diff line number Diff line
@@ -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";

@@ -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;
@@ -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.
         */
@@ -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,
@@ -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();

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

@@ -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) {
@@ -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);
@@ -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();
@@ -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();
@@ -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++;
                    }
@@ -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 {
@@ -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.");
                }
@@ -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;
@@ -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();
@@ -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) {
@@ -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);
+26 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
    }
@@ -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();
@@ -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 {
+84 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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());
    }
}