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

Commit b68852f8 authored by Kweku Adams's avatar Kweku Adams
Browse files

Avoid recalculating the same information.

We don't modify the running jobs list while determining new assignments,
so recalculating the top-EJ count is wasteful.

Bug: 141645789
Test: atest FrameworksMockingServicesTests:JobConcurrencyManagerTest
Change-Id: I8a8486692d11b2237e12264e99ec04e7322db1af
parent 90e81227
Loading
Loading
Loading
Loading
+37 −23
Original line number Diff line number Diff line
@@ -327,6 +327,7 @@ class JobConcurrencyManager {
    private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>();
    private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>();
    private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>();
    private final AssignmentInfo mRecycledAssignmentInfo = new AssignmentInfo();

    private final Pools.Pool<ContextAssignment> mContextAssignmentPool =
            new Pools.SimplePool<>(MAX_RETAINED_OBJECTS);
@@ -414,7 +415,7 @@ class JobConcurrencyManager {
    @VisibleForTesting
    JobConcurrencyManager(JobSchedulerService service, Injector injector) {
        mService = service;
        mLock = mService.mLock;
        mLock = mService.getLock();
        mContext = service.getTestableContext();
        mInjector = injector;

@@ -693,8 +694,9 @@ class JobConcurrencyManager {
            return;
        }

        final long minPreferredUidOnlyWaitingTimeMs = prepareForAssignmentDeterminationLocked(
                mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
        prepareForAssignmentDeterminationLocked(
                mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
                mRecycledAssignmentInfo);

        if (DEBUG) {
            Slog.d(TAG, printAssignments("running jobs initial",
@@ -703,7 +705,7 @@ class JobConcurrencyManager {

        determineAssignmentsLocked(
                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
                minPreferredUidOnlyWaitingTimeMs);
                mRecycledAssignmentInfo);

        if (DEBUG) {
            Slog.d(TAG, printAssignments("running jobs final",
@@ -715,17 +717,18 @@ class JobConcurrencyManager {
        carryOutAssignmentChangesLocked(mRecycledChanged);

        cleanUpAfterAssignmentChangesLocked(
                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
                mRecycledAssignmentInfo);

        noteConcurrency();
    }

    /** @return the minimum remaining execution time for preferred UID only JobServiceContexts. */
    @VisibleForTesting
    @GuardedBy("mLock")
    long prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
    void prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
            final List<ContextAssignment> preferredUidOnly,
            final List<ContextAssignment> stoppable) {
            final List<ContextAssignment> stoppable,
            final AssignmentInfo info) {
        final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
        final List<JobServiceContext> activeServices = mActiveServices;

@@ -755,6 +758,9 @@ class JobConcurrencyManager {
            if (js != null) {
                mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
                assignment.workType = jsc.getRunningJobWorkType();
                if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
                    info.numRunningTopEj++;
                }
            }

            assignment.preferredUid = jsc.getPreferredUid();
@@ -789,9 +795,10 @@ class JobConcurrencyManager {
        }

        mWorkCountTracker.onCountDone();
        // Return 0 if there were no preferred UID only contexts to indicate no waiting time due
        // Set 0 if there were no preferred UID only contexts to indicate no waiting time due
        // to such jobs.
        return minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE
        info.minPreferredUidOnlyWaitingTimeMs =
                minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE
                        ? 0 : minPreferredUidOnlyWaitingTimeMs;
    }

@@ -801,7 +808,7 @@ class JobConcurrencyManager {
            final ArraySet<ContextAssignment> idle,
            final List<ContextAssignment> preferredUidOnly,
            final List<ContextAssignment> stoppable,
            long minPreferredUidOnlyWaitingTimeMs) {
            @NonNull AssignmentInfo info) {
        final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
        final List<JobServiceContext> activeServices = mActiveServices;
        pendingJobQueue.resetIterator();
@@ -832,7 +839,7 @@ class JobConcurrencyManager {
            // pending jobs that could be designated as waiting too long, and those other jobs
            // would only have to wait for the new slots to become available.
            final long minWaitingTimeMs =
                    Math.min(minPreferredUidOnlyWaitingTimeMs, minChangedWaitingTimeMs);
                    Math.min(info.minPreferredUidOnlyWaitingTimeMs, minChangedWaitingTimeMs);

            // Find an available slot for nextPending. The context should be one of the following:
            // 1. Unused
@@ -861,13 +868,6 @@ class JobConcurrencyManager {
                }
            }
            if (selectedContext == null && stoppable.size() > 0) {
                int topEjCount = 0;
                for (int r = mRunningJobs.size() - 1; r >= 0; --r) {
                    JobStatus js = mRunningJobs.valueAt(r);
                    if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
                        topEjCount++;
                    }
                }
                for (int s = stoppable.size() - 1; s >= 0; --s) {
                    final ContextAssignment assignment = stoppable.get(s);
                    final JobStatus runningJob = assignment.context.getRunningJobLocked();
@@ -888,7 +888,8 @@ class JobConcurrencyManager {
                        final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
                        canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2
                                || currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
                                || topEjCount > .5 * mWorkTypeConfig.getMaxTotal(); // Case 4
                                // Case 4
                                || info.numRunningTopEj > .5 * mWorkTypeConfig.getMaxTotal();
                    }
                    if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5
                        if (nextPending.shouldTreatAsExpeditedJob()) {
@@ -955,7 +956,7 @@ class JobConcurrencyManager {
                if (selectedContext != null) {
                    selectedContext.newJob = nextPending;
                    preferredUidOnly.remove(selectedContext);
                    minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
                    info.minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
                }
            }
            // Make sure to run EJs for the TOP app immediately.
@@ -1072,7 +1073,8 @@ class JobConcurrencyManager {
    private void cleanUpAfterAssignmentChangesLocked(final ArraySet<ContextAssignment> changed,
            final ArraySet<ContextAssignment> idle,
            final List<ContextAssignment> preferredUidOnly,
            final List<ContextAssignment> stoppable) {
            final List<ContextAssignment> stoppable,
            final AssignmentInfo assignmentInfo) {
        for (int s = stoppable.size() - 1; s >= 0; --s) {
            final ContextAssignment assignment = stoppable.get(s);
            assignment.clear();
@@ -1093,6 +1095,7 @@ class JobConcurrencyManager {
        idle.clear();
        stoppable.clear();
        preferredUidOnly.clear();
        assignmentInfo.clear();
        mWorkCountTracker.resetStagingCount();
        mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
    }
@@ -2540,6 +2543,17 @@ class JobConcurrencyManager {
        }
    }

    @VisibleForTesting
    static final class AssignmentInfo {
        public long minPreferredUidOnlyWaitingTimeMs;
        public int numRunningTopEj;

        void clear() {
            minPreferredUidOnlyWaitingTimeMs = 0;
            numRunningTopEj = 0;
        }
    }

    // TESTING HELPERS

    @VisibleForTesting
+89 −24
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.job;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -34,16 +35,20 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -51,6 +56,8 @@ import android.content.pm.IPackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArrayMap;
@@ -149,12 +156,14 @@ public final class JobConcurrencyManagerTest {
                R.bool.config_jobSchedulerRestrictBackgroundUser);
        when(mContext.getResources()).thenReturn(mResources);
        doReturn(mContext).when(jobSchedulerService).getTestableContext();
        doReturn(jobSchedulerService).when(jobSchedulerService).getLock();
        mConfigBuilder = new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
        doAnswer((Answer<DeviceConfig.Properties>) invocationOnMock -> mConfigBuilder.build())
                .when(() -> DeviceConfig.getProperties(eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER)));
        mPendingJobQueue = new PendingJobQueue();
        doReturn(mPendingJobQueue).when(jobSchedulerService).getPendingJobQueue();
        doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
        doReturn(mock(PowerManager.class)).when(mContext).getSystemService(PowerManager.class);
        mInjector = new InjectorForTest();
        doAnswer((Answer<Long>) invocationOnMock -> {
            Object[] args = invocationOnMock.getArguments();
@@ -171,6 +180,16 @@ public final class JobConcurrencyManagerTest {
        createCurrentUser(true);
        mNextUserId = 10;
        mJobConcurrencyManager.mGracePeriodObserver = mGracePeriodObserver;

        IActivityManager activityManager = ActivityManager.getService();
        spyOn(activityManager);
        try {
            doNothing().when(activityManager).registerUserSwitchObserver(any(), anyString());
        } catch (RemoteException e) {
            fail("registerUserSwitchObserver threw exception: " + e.getMessage());
        }

        mJobConcurrencyManager.onSystemReady();
    }

    @After
@@ -188,13 +207,16 @@ public final class JobConcurrencyManagerTest {
        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
        final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
                new JobConcurrencyManager.AssignmentInfo();
        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
                idle, preferredUidOnly, stoppable, assignmentInfo);

        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
        assertEquals(0, preferredUidOnly.size());
        assertEquals(0, stoppable.size());
        assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
        assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
        assertEquals(0, assignmentInfo.numRunningTopEj);
    }

    @Test
@@ -207,13 +229,16 @@ public final class JobConcurrencyManagerTest {
        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
        final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
                new JobConcurrencyManager.AssignmentInfo();
        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
                idle, preferredUidOnly, stoppable, assignmentInfo);

        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
        assertEquals(0, preferredUidOnly.size());
        assertEquals(0, stoppable.size());
        assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
        assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
        assertEquals(0, assignmentInfo.numRunningTopEj);
    }

    @Test
@@ -230,13 +255,45 @@ public final class JobConcurrencyManagerTest {
        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
        final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
                new JobConcurrencyManager.AssignmentInfo();
        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
                idle, preferredUidOnly, stoppable, assignmentInfo);

        assertEquals(0, idle.size());
        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
        assertEquals(0, stoppable.size());
        assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
        assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
        assertEquals(0, assignmentInfo.numRunningTopEj);
    }

    @Test
    public void testPrepareForAssignmentDetermination_onlyRunningTopEjs() {
        for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
            JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
            job.startedAsExpeditedJob = true;
            job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
            mJobConcurrencyManager.addRunningJobForTesting(job);
        }

        for (int i = 0; i < mInjector.contexts.size(); ++i) {
            doReturn(i % 2 == 0).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
        }

        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
                new JobConcurrencyManager.AssignmentInfo();
        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
                idle, preferredUidOnly, stoppable, assignmentInfo);

        assertEquals(0, idle.size());
        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, preferredUidOnly.size());
        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, stoppable.size());
        assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
                assignmentInfo.numRunningTopEj);
    }

    @Test
@@ -257,11 +314,13 @@ public final class JobConcurrencyManagerTest {
        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
        mJobConcurrencyManager
                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
                new JobConcurrencyManager.AssignmentInfo();
        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
                idle, preferredUidOnly, stoppable, assignmentInfo);
        mJobConcurrencyManager
                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
                        Long.MAX_VALUE);
                        assignmentInfo);

        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, changed.size());
        for (int i = changed.size() - 1; i >= 0; --i) {
@@ -301,15 +360,17 @@ public final class JobConcurrencyManagerTest {
        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
                new JobConcurrencyManager.AssignmentInfo();

        long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
        assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
                idle, preferredUidOnly, stoppable, assignmentInfo);
        assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());

        mJobConcurrencyManager
                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
                        minPreferredUidOnlyWaitingTimeMs);
                        assignmentInfo);

        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
        assertEquals(0, changed.size());
@@ -350,15 +411,17 @@ public final class JobConcurrencyManagerTest {
        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
                new JobConcurrencyManager.AssignmentInfo();

        long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
        assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
                idle, preferredUidOnly, stoppable, assignmentInfo);
        assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());

        mJobConcurrencyManager
                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
                        minPreferredUidOnlyWaitingTimeMs);
                        assignmentInfo);

        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
        for (int i = changed.size() - 1; i >= 0; --i) {
@@ -404,15 +467,17 @@ public final class JobConcurrencyManagerTest {
        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
                new JobConcurrencyManager.AssignmentInfo();

        long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
        assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
                idle, preferredUidOnly, stoppable, assignmentInfo);
        assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());

        mJobConcurrencyManager
                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
                        minPreferredUidOnlyWaitingTimeMs);
                        assignmentInfo);

        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
        // Depending on iteration order, we may create 1 or 2 contexts.