Merge "Track last job execution in heartbeat time, not strictly real time" into pi-dev

This commit is contained in:
TreeHugger Robot
2018-03-10 00:22:57 +00:00
committed by Android (Google) Code Review
3 changed files with 82 additions and 11 deletions

View File

@@ -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.
*/

View File

@@ -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.

View File

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