| /* |
| * 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<JobStatus> pendingJobs = mService.mPendingJobs; |
| final List<JobServiceContext> activeServices = mService.mActiveServices; |
| final List<StateController> 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<MAX_JOB_CONTEXTS_COUNT; i++) { |
| final JobServiceContext js = mService.mActiveServices.get(i); |
| final JobStatus status = js.getRunningJobLocked(); |
| |
| if ((contextIdToJobMap[i] = status) != null) { |
| mJobCountTracker.incrementRunningJobCount(isFgJob(status)); |
| } |
| |
| slotChanged[i] = false; |
| preferredUidForContext[i] = js.getPreferredUid(); |
| } |
| if (DEBUG) { |
| Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial")); |
| } |
| |
| // Next, update the job priorities, and also count the pending FG / BG jobs. |
| for (int i = 0; i < pendingJobs.size(); i++) { |
| final JobStatus pending = pendingJobs.get(i); |
| |
| // If job is already running, go to next job. |
| int jobRunningContext = findJobContextIdFromMap(pending, contextIdToJobMap); |
| if (jobRunningContext != -1) { |
| continue; |
| } |
| |
| final int priority = mService.evaluateJobPriorityLocked(pending); |
| pending.lastEvaluatedPriority = priority; |
| |
| mJobCountTracker.incrementPendingJobCount(isFgJob(pending)); |
| } |
| |
| mJobCountTracker.onCountDone(); |
| |
| for (int i = 0; i < pendingJobs.size(); i++) { |
| final JobStatus nextPending = pendingJobs.get(i); |
| |
| // Unfortunately we need to repeat this relatively expensive check. |
| int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap); |
| if (jobRunningContext != -1) { |
| continue; |
| } |
| |
| final boolean isPendingFg = isFgJob(nextPending); |
| |
| // Find an available slot for nextPending. The context should be available OR |
| // it should have lowest priority among all running jobs |
| // (sharing the same Uid as nextPending) |
| int minPriorityForPreemption = Integer.MAX_VALUE; |
| int selectedContextId = -1; |
| boolean startingJob = false; |
| for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) { |
| JobStatus job = contextIdToJobMap[j]; |
| int preferredUid = preferredUidForContext[j]; |
| if (job == null) { |
| final boolean preferredUidOkay = (preferredUid == nextPending.getUid()) |
| || (preferredUid == JobServiceContext.NO_PREFERRED_UID); |
| |
| if (preferredUidOkay && mJobCountTracker.canJobStart(isPendingFg)) { |
| // This slot is free, and we haven't yet hit the limit on |
| // concurrent jobs... we can just throw the job in to here. |
| selectedContextId = j; |
| startingJob = true; |
| break; |
| } |
| // No job on this context, but nextPending can't run here because |
| // the context has a preferred Uid or we have reached the limit on |
| // concurrent jobs. |
| continue; |
| } |
| if (job.getUid() != nextPending.getUid()) { |
| continue; |
| } |
| |
| final int jobPriority = mService.evaluateJobPriorityLocked(job); |
| if (jobPriority >= 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<MAX_JOB_CONTEXTS_COUNT; i++) { |
| boolean preservePreferredUid = false; |
| if (slotChanged[i]) { |
| JobStatus js = activeServices.get(i).getRunningJobLocked(); |
| if (js != null) { |
| if (DEBUG) { |
| Slog.d(TAG, "preempting job: " |
| + activeServices.get(i).getRunningJobLocked()); |
| } |
| // preferredUid will be set to uid of currently running job. |
| activeServices.get(i).preemptExecutingJobLocked(); |
| preservePreferredUid = true; |
| } else { |
| final JobStatus pendingJob = contextIdToJobMap[i]; |
| if (DEBUG) { |
| Slog.d(TAG, "About to run job on context " |
| + i + ", job: " + pendingJob); |
| } |
| for (int ic=0; ic<controllers.size(); ic++) { |
| controllers.get(ic).prepareForExecutionLocked(pendingJob); |
| } |
| if (!activeServices.get(i).executeRunnableJob(pendingJob)) { |
| Slog.d(TAG, "Error executing " + pendingJob); |
| } |
| if (pendingJobs.remove(pendingJob)) { |
| tracker.noteNonpending(pendingJob); |
| } |
| } |
| } |
| if (!preservePreferredUid) { |
| activeServices.get(i).clearPreferredUid(); |
| } |
| } |
| } |
| |
| private static int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) { |
| for (int i=0; i<map.length; i++) { |
| if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| @GuardedBy("mLock") |
| private String printPendingQueueLocked() { |
| StringBuilder s = new StringBuilder("Pending queue: "); |
| Iterator<JobStatus> 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<map.length; i++) { |
| s.append("(") |
| .append(map[i] == null? -1: map[i].getJobId()) |
| .append(map[i] == null? -1: map[i].getUid()) |
| .append(")" ); |
| } |
| return s.toString(); |
| } |
| |
| |
| public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) { |
| pw.println("Concurrency:"); |
| |
| pw.increaseIndent(); |
| try { |
| pw.print("Screen state: current "); |
| pw.print(mCurrentInteractiveState ? "ON" : "OFF"); |
| pw.print(" effective "); |
| pw.print(mEffectiveInteractiveState ? "ON" : "OFF"); |
| pw.println(); |
| |
| pw.print("Last screen ON : "); |
| TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOnRealtime, now); |
| pw.println(); |
| |
| pw.print("Last screen OFF: "); |
| TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOffRealtime, now); |
| pw.println(); |
| |
| pw.println(); |
| |
| pw.println("Current max jobs:"); |
| pw.println(" "); |
| pw.println(mJobCountTracker); |
| |
| pw.println(); |
| |
| pw.print("mLastMemoryTrimLevel: "); |
| pw.print(mLastMemoryTrimLevel); |
| pw.println(); |
| |
| mStatLogger.dump(pw); |
| } finally { |
| pw.decreaseIndent(); |
| } |
| } |
| |
| public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) { |
| final long token = proto.start(tag); |
| |
| proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE, |
| mCurrentInteractiveState); |
| proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE, |
| mEffectiveInteractiveState); |
| |
| proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS, |
| nowRealtime - mLastScreenOnRealtime); |
| proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS, |
| nowRealtime - mLastScreenOffRealtime); |
| |
| mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER); |
| |
| proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, |
| mLastMemoryTrimLevel); |
| |
| proto.end(token); |
| } |
| |
| /** |
| * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running / |
| * pending, how many more job can start. |
| * |
| * Extracted for testing and logging. |
| */ |
| @VisibleForTesting |
| static class JobCountTracker { |
| private int mConfigNumMaxTotalJobs; |
| private int mConfigNumMaxBgJobs; |
| private int mConfigNumMinBgJobs; |
| |
| private int mNumRunningFgJobs; |
| private int mNumRunningBgJobs; |
| |
| private int mNumPendingFgJobs; |
| private int mNumPendingBgJobs; |
| |
| private int mNumStartingFgJobs; |
| private int mNumStartingBgJobs; |
| |
| private int mNumReservedForBg; |
| private int mNumActualMaxFgJobs; |
| private int mNumActualMaxBgJobs; |
| |
| void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) { |
| mConfigNumMaxTotalJobs = numTotalMaxJobs; |
| mConfigNumMaxBgJobs = numMaxBgJobs; |
| mConfigNumMinBgJobs = numMinBgJobs; |
| |
| mNumRunningFgJobs = 0; |
| mNumRunningBgJobs = 0; |
| |
| mNumPendingFgJobs = 0; |
| mNumPendingBgJobs = 0; |
| |
| mNumStartingFgJobs = 0; |
| mNumStartingBgJobs = 0; |
| |
| mNumReservedForBg = 0; |
| mNumActualMaxFgJobs = 0; |
| mNumActualMaxBgJobs = 0; |
| } |
| |
| void incrementRunningJobCount(boolean isFg) { |
| if (isFg) { |
| mNumRunningFgJobs++; |
| } else { |
| mNumRunningBgJobs++; |
| } |
| } |
| |
| void incrementPendingJobCount(boolean isFg) { |
| if (isFg) { |
| mNumPendingFgJobs++; |
| } else { |
| mNumPendingBgJobs++; |
| } |
| } |
| |
| void onStartingNewJob(boolean isFg) { |
| if (isFg) { |
| mNumStartingFgJobs++; |
| } else { |
| mNumStartingBgJobs++; |
| } |
| } |
| |
| void onCountDone() { |
| // Note some variables are used only here but are made class members in order to have |
| // them on logcat / dumpsys. |
| |
| // How many slots should we allocate to BG jobs at least? |
| // That's basically "getMinBg()", but if there are less jobs, decrease it. |
| // (e.g. even if min-bg is 2, if there's only 1 running+pending job, this has to be 1.) |
| final int reservedForBg = Math.min( |
| mConfigNumMinBgJobs, |
| mNumRunningBgJobs + mNumPendingBgJobs); |
| |
| // However, if there are FG jobs already running, we have to adjust it. |
| mNumReservedForBg = Math.min(reservedForBg, |
| mConfigNumMaxTotalJobs - mNumRunningFgJobs); |
| |
| // Max FG is [total - [number needed for BG jobs]] |
| // [number needed for BG jobs] is the bigger one of [running BG] or [reserved BG] |
| final int maxFg = |
| mConfigNumMaxTotalJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg); |
| |
| // The above maxFg is the theoretical max. If there are less FG jobs, the actual |
| // max FG will be lower accordingly. |
| mNumActualMaxFgJobs = Math.min( |
| maxFg, |
| mNumRunningFgJobs + mNumPendingFgJobs); |
| |
| // Max BG is [total - actual max FG], but cap at [config max BG]. |
| final int maxBg = Math.min( |
| mConfigNumMaxBgJobs, |
| mConfigNumMaxTotalJobs - mNumActualMaxFgJobs); |
| |
| // If there are less BG jobs than maxBg, then reduce the actual max BG accordingly. |
| // This isn't needed for the logic to work, but this will give consistent output |
| // on logcat and dumpsys. |
| mNumActualMaxBgJobs = Math.min( |
| maxBg, |
| mNumRunningBgJobs + mNumPendingBgJobs); |
| } |
| |
| boolean canJobStart(boolean isFg) { |
| if (isFg) { |
| return mNumRunningFgJobs + mNumStartingFgJobs < mNumActualMaxFgJobs; |
| } else { |
| return mNumRunningBgJobs + mNumStartingBgJobs < mNumActualMaxBgJobs; |
| } |
| } |
| |
| public int getNumStartingFgJobs() { |
| return mNumStartingFgJobs; |
| } |
| |
| public int getNumStartingBgJobs() { |
| return mNumStartingBgJobs; |
| } |
| |
| int getTotalRunningJobCountToNote() { |
| return mNumRunningFgJobs + mNumRunningBgJobs |
| + mNumStartingFgJobs + mNumStartingBgJobs; |
| } |
| |
| int getFgRunningJobCountToNote() { |
| return mNumRunningFgJobs + mNumStartingFgJobs; |
| } |
| |
| void logStatus() { |
| if (DEBUG) { |
| Slog.d(TAG, "assignJobsToContexts: " + this); |
| } |
| } |
| |
| public String toString() { |
| final int totalFg = mNumRunningFgJobs + mNumStartingFgJobs; |
| final int totalBg = mNumRunningBgJobs + mNumStartingBgJobs; |
| return String.format( |
| "Config={tot=%d bg min/max=%d/%d}" |
| + " Running[FG/BG (total)]: %d / %d (%d)" |
| + " Pending: %d / %d (%d)" |
| + " Actual max: %d%s / %d%s (%d%s)" |
| + " Res BG: %d" |
| + " Starting: %d / %d (%d)" |
| + " Total: %d%s / %d%s (%d%s)", |
| mConfigNumMaxTotalJobs, |
| mConfigNumMinBgJobs, |
| mConfigNumMaxBgJobs, |
| |
| mNumRunningFgJobs, mNumRunningBgJobs, |
| mNumRunningFgJobs + mNumRunningBgJobs, |
| |
| mNumPendingFgJobs, mNumPendingBgJobs, |
| mNumPendingFgJobs + mNumPendingBgJobs, |
| |
| mNumActualMaxFgJobs, (totalFg <= mConfigNumMaxTotalJobs) ? "" : "*", |
| mNumActualMaxBgJobs, (totalBg <= mConfigNumMaxBgJobs) ? "" : "*", |
| |
| mNumActualMaxFgJobs + mNumActualMaxBgJobs, |
| (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumMaxTotalJobs) |
| ? "" : "*", |
| |
| mNumReservedForBg, |
| |
| mNumStartingFgJobs, mNumStartingBgJobs, mNumStartingFgJobs + mNumStartingBgJobs, |
| |
| totalFg, (totalFg <= mNumActualMaxFgJobs) ? "" : "*", |
| totalBg, (totalBg <= mNumActualMaxBgJobs) ? "" : "*", |
| totalFg + totalBg, (totalFg + totalBg <= mConfigNumMaxTotalJobs) ? "" : "*" |
| ); |
| } |
| |
| public void dumpProto(ProtoOutputStream proto, long fieldId) { |
| final long token = proto.start(fieldId); |
| |
| proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_TOTAL_JOBS, mConfigNumMaxTotalJobs); |
| proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_BG_JOBS, mConfigNumMaxBgJobs); |
| proto.write(JobCountTrackerProto.CONFIG_NUM_MIN_BG_JOBS, mConfigNumMinBgJobs); |
| |
| proto.write(JobCountTrackerProto.NUM_RUNNING_FG_JOBS, mNumRunningFgJobs); |
| proto.write(JobCountTrackerProto.NUM_RUNNING_BG_JOBS, mNumRunningBgJobs); |
| |
| proto.write(JobCountTrackerProto.NUM_PENDING_FG_JOBS, mNumPendingFgJobs); |
| proto.write(JobCountTrackerProto.NUM_PENDING_BG_JOBS, mNumPendingBgJobs); |
| |
| proto.end(token); |
| } |
| } |
| } |