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

Commit e4cb0714 authored by Christopher Tate's avatar Christopher Tate Committed by android-build-merger
Browse files

Merge "Track last job execution in heartbeat time, not strictly real time"...

Merge "Track last job execution in heartbeat time, not strictly real time" into pi-dev am: 79dd8b9c
am: 43d93100

Change-Id: Ib57515e7c56e5c4d6adf346b710c98144725c066
parents 8c32899e 43d93100
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -47,6 +47,11 @@ public interface JobSchedulerInternal {
     */
    public long baseHeartbeatForApp(String packageName, @UserIdInt int userId, int appBucket);

    /**
     * Tell the scheduler when a JobServiceContext starts running a job in an app
     */
    void noteJobStart(String packageName, int userId);

    /**
     * Returns a list of pending jobs scheduled by the system service.
     */
+71 −9
Original line number Diff line number Diff line
@@ -108,6 +108,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
@@ -238,6 +239,27 @@ public final class JobSchedulerService extends com.android.server.SystemService
    long mHeartbeat = 0;
    long mLastHeartbeatTime = sElapsedRealtimeClock.millis();

    /**
     * Named indices into the STANDBY_BEATS array, for clarity in referring to
     * specific buckets' bookkeeping.
     */
    static final int ACTIVE_INDEX = 0;
    static final int WORKING_INDEX = 1;
    static final int FREQUENT_INDEX = 2;
    static final int RARE_INDEX = 3;

    /**
     * Bookkeeping about when jobs last run.  We keep our own record in heartbeat time,
     * rather than rely on Usage Stats' timestamps, because heartbeat time can be
     * manipulated for testing purposes and we need job runnability to track that rather
     * than real time.
     *
     * Outer SparseArray slices by user handle; inner map of package name to heartbeat
     * is a HashMap<> rather than ArrayMap<> because we expect O(hundreds) of keys
     * and it will be accessed in a known-hot code path.
     */
    final SparseArray<HashMap<String, Long>> mLastJobHeartbeats = new SparseArray<>();

    static final String HEARTBEAT_TAG = "*job.heartbeat*";
    final HeartbeatAlarmListener mHeartbeatAlarm = new HeartbeatAlarmListener();

@@ -532,11 +554,11 @@ public final class JobSchedulerService extends com.android.server.SystemService
                    DEFAULT_MIN_EXP_BACKOFF_TIME);
            STANDBY_HEARTBEAT_TIME = mParser.getDurationMillis(KEY_STANDBY_HEARTBEAT_TIME,
                    DEFAULT_STANDBY_HEARTBEAT_TIME);
            STANDBY_BEATS[1] = mParser.getInt(KEY_STANDBY_WORKING_BEATS,
            STANDBY_BEATS[WORKING_INDEX] = mParser.getInt(KEY_STANDBY_WORKING_BEATS,
                    DEFAULT_STANDBY_WORKING_BEATS);
            STANDBY_BEATS[2] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS,
            STANDBY_BEATS[FREQUENT_INDEX] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS,
                    DEFAULT_STANDBY_FREQUENT_BEATS);
            STANDBY_BEATS[3] = mParser.getInt(KEY_STANDBY_RARE_BEATS,
            STANDBY_BEATS[RARE_INDEX] = mParser.getInt(KEY_STANDBY_RARE_BEATS,
                    DEFAULT_STANDBY_RARE_BEATS);
            CONN_CONGESTION_DELAY_FRAC = mParser.getFloat(KEY_CONN_CONGESTION_DELAY_FRAC,
                    DEFAULT_CONN_CONGESTION_DELAY_FRAC);
@@ -1420,15 +1442,40 @@ public final class JobSchedulerService extends com.android.server.SystemService
                periodicToReschedule.getLastFailedRunTime());
    }

    /*
     * We default to "long enough ago that every bucket's jobs are immediately runnable" to
     * avoid starvation of apps in uncommon-use buckets that might arise from repeated
     * reboot behavior.
     */
    long heartbeatWhenJobsLastRun(String packageName, final @UserIdInt int userId) {
        final long heartbeat;
        final long timeSinceLastJob = mUsageStats.getTimeSinceLastJobRun(packageName, userId);
        // The furthest back in pre-boot time that we need to bother with
        long heartbeat = -mConstants.STANDBY_BEATS[RARE_INDEX];
        boolean cacheHit = false;
        synchronized (mLock) {
            heartbeat = mHeartbeat - (timeSinceLastJob / mConstants.STANDBY_HEARTBEAT_TIME);
            HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId);
            if (jobPackages != null) {
                long cachedValue = jobPackages.getOrDefault(packageName, Long.MAX_VALUE);
                if (cachedValue < Long.MAX_VALUE) {
                    cacheHit = true;
                    heartbeat = cachedValue;
                }
            }
            if (!cacheHit) {
                // We haven't seen it yet; ask usage stats about it
                final long timeSinceJob = mUsageStats.getTimeSinceLastJobRun(packageName, userId);
                if (timeSinceJob < Long.MAX_VALUE) {
                    // Usage stats knows about it from before, so calculate back from that
                    // and go from there.
                    heartbeat = mHeartbeat - (timeSinceJob / mConstants.STANDBY_HEARTBEAT_TIME);
                }
                // If usage stats returned its "not found" MAX_VALUE, we still have the
                // negative default 'heartbeat' value we established above
                setLastJobHeartbeatLocked(packageName, userId, heartbeat);
            }
        }
        if (DEBUG_STANDBY) {
            Slog.v(TAG, "Last job heartbeat " + heartbeat + " for " + packageName + "/" + userId
                    + " delta=" + timeSinceLastJob);
            Slog.v(TAG, "Last job heartbeat " + heartbeat + " for "
                    + packageName + "/" + userId);
        }
        return heartbeat;
    }
@@ -1437,12 +1484,21 @@ public final class JobSchedulerService extends com.android.server.SystemService
        return heartbeatWhenJobsLastRun(job.getSourcePackageName(), job.getSourceUserId());
    }

    void setLastJobHeartbeatLocked(String packageName, int userId, long heartbeat) {
        HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId);
        if (jobPackages == null) {
            jobPackages = new HashMap<>();
            mLastJobHeartbeats.put(userId, jobPackages);
        }
        jobPackages.put(packageName, heartbeat);
    }

    // JobCompletedListener implementations.

    /**
     * A job just finished executing. We fetch the
     * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
     * whether we want to reschedule we readd it to the controllers.
     * whether we want to reschedule we re-add it to the controllers.
     * @param jobStatus Completed job.
     * @param needsReschedule Whether the implementing class should reschedule this job.
     */
@@ -2208,6 +2264,12 @@ public final class JobSchedulerService extends com.android.server.SystemService
            return baseHeartbeat;
        }

        public void noteJobStart(String packageName, int userId) {
            synchronized (mLock) {
                setLastJobHeartbeatLocked(packageName, userId, mHeartbeat);
            }
        }

        /**
         * Returns a list of all pending jobs. A running job is not considered pending. Periodic
         * jobs are always considered pending.
+6 −2
Original line number Diff line number Diff line
@@ -268,10 +268,14 @@ public final class JobServiceContext implements ServiceConnection {
            } catch (RemoteException e) {
                // Whatever.
            }
            final String jobPackage = job.getSourcePackageName();
            final int jobUserId = job.getSourceUserId();
            UsageStatsManagerInternal usageStats =
                    LocalServices.getService(UsageStatsManagerInternal.class);
            usageStats.setLastJobRunTime(job.getSourcePackageName(), job.getSourceUserId(),
                    mExecutionStartTimeElapsed);
            usageStats.setLastJobRunTime(jobPackage, jobUserId, mExecutionStartTimeElapsed);
            JobSchedulerInternal jobScheduler =
                    LocalServices.getService(JobSchedulerInternal.class);
            jobScheduler.noteJobStart(jobPackage, jobUserId);
            mAvailable = false;
            mStoppedReason = null;
            mStoppedTime = 0;