Merge "Track last job execution in heartbeat time, not strictly real time" into pi-dev
am: 79dd8b9c22
Change-Id: I763368f1a8585843cf7543c465911370bcd6dc7d
This commit is contained in:
@@ -47,6 +47,11 @@ public interface JobSchedulerInternal {
|
|||||||
*/
|
*/
|
||||||
public long baseHeartbeatForApp(String packageName, @UserIdInt int userId, int appBucket);
|
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.
|
* Returns a list of pending jobs scheduled by the system service.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -238,6 +239,27 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
long mHeartbeat = 0;
|
long mHeartbeat = 0;
|
||||||
long mLastHeartbeatTime = sElapsedRealtimeClock.millis();
|
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*";
|
static final String HEARTBEAT_TAG = "*job.heartbeat*";
|
||||||
final HeartbeatAlarmListener mHeartbeatAlarm = new HeartbeatAlarmListener();
|
final HeartbeatAlarmListener mHeartbeatAlarm = new HeartbeatAlarmListener();
|
||||||
|
|
||||||
@@ -532,11 +554,11 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
DEFAULT_MIN_EXP_BACKOFF_TIME);
|
DEFAULT_MIN_EXP_BACKOFF_TIME);
|
||||||
STANDBY_HEARTBEAT_TIME = mParser.getDurationMillis(KEY_STANDBY_HEARTBEAT_TIME,
|
STANDBY_HEARTBEAT_TIME = mParser.getDurationMillis(KEY_STANDBY_HEARTBEAT_TIME,
|
||||||
DEFAULT_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);
|
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);
|
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);
|
DEFAULT_STANDBY_RARE_BEATS);
|
||||||
CONN_CONGESTION_DELAY_FRAC = mParser.getFloat(KEY_CONN_CONGESTION_DELAY_FRAC,
|
CONN_CONGESTION_DELAY_FRAC = mParser.getFloat(KEY_CONN_CONGESTION_DELAY_FRAC,
|
||||||
DEFAULT_CONN_CONGESTION_DELAY_FRAC);
|
DEFAULT_CONN_CONGESTION_DELAY_FRAC);
|
||||||
@@ -1420,15 +1442,40 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
periodicToReschedule.getLastFailedRunTime());
|
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) {
|
long heartbeatWhenJobsLastRun(String packageName, final @UserIdInt int userId) {
|
||||||
final long heartbeat;
|
// The furthest back in pre-boot time that we need to bother with
|
||||||
final long timeSinceLastJob = mUsageStats.getTimeSinceLastJobRun(packageName, userId);
|
long heartbeat = -mConstants.STANDBY_BEATS[RARE_INDEX];
|
||||||
|
boolean cacheHit = false;
|
||||||
synchronized (mLock) {
|
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) {
|
if (DEBUG_STANDBY) {
|
||||||
Slog.v(TAG, "Last job heartbeat " + heartbeat + " for " + packageName + "/" + userId
|
Slog.v(TAG, "Last job heartbeat " + heartbeat + " for "
|
||||||
+ " delta=" + timeSinceLastJob);
|
+ packageName + "/" + userId);
|
||||||
}
|
}
|
||||||
return heartbeat;
|
return heartbeat;
|
||||||
}
|
}
|
||||||
@@ -1437,12 +1484,21 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
return heartbeatWhenJobsLastRun(job.getSourcePackageName(), job.getSourceUserId());
|
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.
|
// JobCompletedListener implementations.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A job just finished executing. We fetch the
|
* A job just finished executing. We fetch the
|
||||||
* {@link com.android.server.job.controllers.JobStatus} from the store and depending on
|
* {@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 jobStatus Completed job.
|
||||||
* @param needsReschedule Whether the implementing class should reschedule this 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;
|
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
|
* Returns a list of all pending jobs. A running job is not considered pending. Periodic
|
||||||
* jobs are always considered pending.
|
* jobs are always considered pending.
|
||||||
|
|||||||
@@ -268,10 +268,14 @@ public final class JobServiceContext implements ServiceConnection {
|
|||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
// Whatever.
|
// Whatever.
|
||||||
}
|
}
|
||||||
|
final String jobPackage = job.getSourcePackageName();
|
||||||
|
final int jobUserId = job.getSourceUserId();
|
||||||
UsageStatsManagerInternal usageStats =
|
UsageStatsManagerInternal usageStats =
|
||||||
LocalServices.getService(UsageStatsManagerInternal.class);
|
LocalServices.getService(UsageStatsManagerInternal.class);
|
||||||
usageStats.setLastJobRunTime(job.getSourcePackageName(), job.getSourceUserId(),
|
usageStats.setLastJobRunTime(jobPackage, jobUserId, mExecutionStartTimeElapsed);
|
||||||
mExecutionStartTimeElapsed);
|
JobSchedulerInternal jobScheduler =
|
||||||
|
LocalServices.getService(JobSchedulerInternal.class);
|
||||||
|
jobScheduler.noteJobStart(jobPackage, jobUserId);
|
||||||
mAvailable = false;
|
mAvailable = false;
|
||||||
mStoppedReason = null;
|
mStoppedReason = null;
|
||||||
mStoppedTime = 0;
|
mStoppedTime = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user