
src.com.android.server.job.JobConcurrencyManager Maven / Gradle / Ivy
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.job;
import android.app.ActivityManager;
import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.PowerManager;
import android.os.RemoteException;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.StatLogger;
import com.android.server.job.JobSchedulerService.Constants;
import com.android.server.job.JobSchedulerService.MaxJobCountsPerMemoryTrimLevel;
import com.android.server.job.controllers.JobStatus;
import com.android.server.job.controllers.StateController;
import java.util.Iterator;
import java.util.List;
/**
* This class decides, given the various configuration and the system status, how many more jobs
* can start.
*/
class JobConcurrencyManager {
private static final String TAG = JobSchedulerService.TAG;
private static final boolean DEBUG = JobSchedulerService.DEBUG;
private final Object mLock;
private final JobSchedulerService mService;
private final JobSchedulerService.Constants mConstants;
private final Context mContext;
private final Handler mHandler;
private PowerManager mPowerManager;
private boolean mCurrentInteractiveState;
private boolean mEffectiveInteractiveState;
private long mLastScreenOnRealtime;
private long mLastScreenOffRealtime;
private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
/**
* This array essentially stores the state of mActiveServices array.
* The ith index stores the job present on the ith JobServiceContext.
* We manipulate this array until we arrive at what jobs should be running on
* what JobServiceContext.
*/
JobStatus[] mRecycledAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
boolean[] mRecycledSlotChanged = new boolean[MAX_JOB_CONTEXTS_COUNT];
int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
/** Max job counts according to the current system state. */
private JobSchedulerService.MaxJobCounts mMaxJobCounts;
private final JobCountTracker mJobCountTracker = new JobCountTracker();
/** Current memory trim level. */
private int mLastMemoryTrimLevel;
/** Used to throttle heavy API calls. */
private long mNextSystemStateRefreshTime;
private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000;
private final StatLogger mStatLogger = new StatLogger(new String[]{
"assignJobsToContexts",
"refreshSystemState",
});
interface Stats {
int ASSIGN_JOBS_TO_CONTEXTS = 0;
int REFRESH_SYSTEM_STATE = 1;
int COUNT = REFRESH_SYSTEM_STATE + 1;
}
JobConcurrencyManager(JobSchedulerService service) {
mService = service;
mLock = mService.mLock;
mConstants = service.mConstants;
mContext = service.getContext();
mHandler = BackgroundThread.getHandler();
}
public void onSystemReady() {
mPowerManager = mContext.getSystemService(PowerManager.class);
final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mReceiver, filter);
onInteractiveStateChanged(mPowerManager.isInteractive());
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case Intent.ACTION_SCREEN_ON:
onInteractiveStateChanged(true);
break;
case Intent.ACTION_SCREEN_OFF:
onInteractiveStateChanged(false);
break;
}
}
};
/**
* Called when the screen turns on / off.
*/
private void onInteractiveStateChanged(boolean interactive) {
synchronized (mLock) {
if (mCurrentInteractiveState == interactive) {
return;
}
mCurrentInteractiveState = interactive;
if (DEBUG) {
Slog.d(TAG, "Interactive: " + interactive);
}
final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis();
if (interactive) {
mLastScreenOnRealtime = nowRealtime;
mEffectiveInteractiveState = true;
mHandler.removeCallbacks(mRampUpForScreenOff);
} else {
mLastScreenOffRealtime = nowRealtime;
// Set mEffectiveInteractiveState to false after the delay, when we may increase
// the concurrency.
// We don't need a wakeup alarm here. When there's a pending job, there should
// also be jobs running too, meaning the device should be awake.
// Note: we can't directly do postDelayed(this::rampUpForScreenOn), because
// we need the exact same instance for removeCallbacks().
mHandler.postDelayed(mRampUpForScreenOff,
mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.getValue());
}
}
}
private final Runnable mRampUpForScreenOff = this::rampUpForScreenOff;
/**
* Called in {@link Constants#SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS} after
* the screen turns off, in order to increase concurrency.
*/
private void rampUpForScreenOff() {
synchronized (mLock) {
// Make sure the screen has really been off for the configured duration.
// (There could be a race.)
if (!mEffectiveInteractiveState) {
return;
}
if (mLastScreenOnRealtime > mLastScreenOffRealtime) {
return;
}
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
if ((mLastScreenOffRealtime
+ mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.getValue())
> now) {
return;
}
mEffectiveInteractiveState = false;
if (DEBUG) {
Slog.d(TAG, "Ramping up concurrency");
}
mService.maybeRunPendingJobsLocked();
}
}
private boolean isFgJob(JobStatus job) {
return job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP;
}
@GuardedBy("mLock")
private void refreshSystemStateLocked() {
final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
// Only refresh the information every so often.
if (nowUptime < mNextSystemStateRefreshTime) {
return;
}
final long start = mStatLogger.getTime();
mNextSystemStateRefreshTime = nowUptime + SYSTEM_STATE_REFRESH_MIN_INTERVAL;
mLastMemoryTrimLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
try {
mLastMemoryTrimLevel = ActivityManager.getService().getMemoryTrimLevel();
} catch (RemoteException e) {
}
mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start);
}
@GuardedBy("mLock")
private void updateMaxCountsLocked() {
refreshSystemStateLocked();
final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState
? mConstants.MAX_JOB_COUNTS_SCREEN_ON
: mConstants.MAX_JOB_COUNTS_SCREEN_OFF;
switch (mLastMemoryTrimLevel) {
case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
mMaxJobCounts = jobCounts.moderate;
break;
case ProcessStats.ADJ_MEM_FACTOR_LOW:
mMaxJobCounts = jobCounts.low;
break;
case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
mMaxJobCounts = jobCounts.critical;
break;
default:
mMaxJobCounts = jobCounts.normal;
break;
}
}
/**
* Takes jobs from pending queue and runs them on available contexts.
* If no contexts are available, preempts lower priority jobs to
* run higher priority ones.
* Lock on mJobs before calling this function.
*/
@GuardedBy("mLock")
void assignJobsToContextsLocked() {
final long start = mStatLogger.getTime();
assignJobsToContextsInternalLocked();
mStatLogger.logDurationStat(Stats.ASSIGN_JOBS_TO_CONTEXTS, start);
}
@GuardedBy("mLock")
private void assignJobsToContextsInternalLocked() {
if (DEBUG) {
Slog.d(TAG, printPendingQueueLocked());
}
final JobPackageTracker tracker = mService.mJobPackageTracker;
final List pendingJobs = mService.mPendingJobs;
final List activeServices = mService.mActiveServices;
final List controllers = mService.mControllers;
updateMaxCountsLocked();
// To avoid GC churn, we recycle the arrays.
JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap;
boolean[] slotChanged = mRecycledSlotChanged;
int[] preferredUidForContext = mRecycledPreferredUidForContext;
// Initialize the work variables and also count running jobs.
mJobCountTracker.reset(
mMaxJobCounts.getMaxTotal(),
mMaxJobCounts.getMaxBg(),
mMaxJobCounts.getMinBg());
for (int i=0; i= nextPending.lastEvaluatedPriority) {
continue;
}
// TODO lastEvaluatedPriority should be evaluateJobPriorityLocked. (double check it)
if (minPriorityForPreemption > nextPending.lastEvaluatedPriority) {
minPriorityForPreemption = nextPending.lastEvaluatedPriority;
selectedContextId = j;
// In this case, we're just going to preempt a low priority job, we're not
// actually starting a job, so don't set startingJob.
}
}
if (selectedContextId != -1) {
contextIdToJobMap[selectedContextId] = nextPending;
slotChanged[selectedContextId] = true;
}
if (startingJob) {
// Increase the counters when we're going to start a job.
mJobCountTracker.onStartingNewJob(isPendingFg);
}
}
if (DEBUG) {
Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
}
mJobCountTracker.logStatus();
tracker.noteConcurrency(mJobCountTracker.getTotalRunningJobCountToNote(),
mJobCountTracker.getFgRunningJobCountToNote());
for (int i=0; i it = mService.mPendingJobs.iterator();
while (it.hasNext()) {
JobStatus js = it.next();
s.append("(")
.append(js.getJob().getId())
.append(", ")
.append(js.getUid())
.append(") ");
}
return s.toString();
}
private static String printContextIdToJobMap(JobStatus[] map, String initial) {
StringBuilder s = new StringBuilder(initial + ": ");
for (int i=0; i
© 2015 - 2025 Weber Informatics LLC | Privacy Policy