1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.job;
18 
19 import android.app.ActivityManager;
20 import android.app.job.JobInfo;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.os.Handler;
26 import android.os.PowerManager;
27 import android.os.RemoteException;
28 import android.util.Slog;
29 import android.util.TimeUtils;
30 import android.util.proto.ProtoOutputStream;
31 
32 import com.android.internal.annotations.GuardedBy;
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.internal.app.procstats.ProcessStats;
35 import com.android.internal.os.BackgroundThread;
36 import com.android.internal.util.IndentingPrintWriter;
37 import com.android.internal.util.StatLogger;
38 import com.android.server.job.JobSchedulerService.Constants;
39 import com.android.server.job.JobSchedulerService.MaxJobCountsPerMemoryTrimLevel;
40 import com.android.server.job.controllers.JobStatus;
41 import com.android.server.job.controllers.StateController;
42 
43 import java.util.Iterator;
44 import java.util.List;
45 
46 /**
47  * This class decides, given the various configuration and the system status, how many more jobs
48  * can start.
49  */
50 class JobConcurrencyManager {
51     private static final String TAG = JobSchedulerService.TAG;
52     private static final boolean DEBUG = JobSchedulerService.DEBUG;
53 
54     private final Object mLock;
55     private final JobSchedulerService mService;
56     private final JobSchedulerService.Constants mConstants;
57     private final Context mContext;
58     private final Handler mHandler;
59 
60     private PowerManager mPowerManager;
61 
62     private boolean mCurrentInteractiveState;
63     private boolean mEffectiveInteractiveState;
64 
65     private long mLastScreenOnRealtime;
66     private long mLastScreenOffRealtime;
67 
68     private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
69 
70     /**
71      * This array essentially stores the state of mActiveServices array.
72      * The ith index stores the job present on the ith JobServiceContext.
73      * We manipulate this array until we arrive at what jobs should be running on
74      * what JobServiceContext.
75      */
76     JobStatus[] mRecycledAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
77 
78     boolean[] mRecycledSlotChanged = new boolean[MAX_JOB_CONTEXTS_COUNT];
79 
80     int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
81 
82     /** Max job counts according to the current system state. */
83     private JobSchedulerService.MaxJobCounts mMaxJobCounts;
84 
85     private final JobCountTracker mJobCountTracker = new JobCountTracker();
86 
87     /** Current memory trim level. */
88     private int mLastMemoryTrimLevel;
89 
90     /** Used to throttle heavy API calls. */
91     private long mNextSystemStateRefreshTime;
92     private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000;
93 
94     private final StatLogger mStatLogger = new StatLogger(new String[]{
95             "assignJobsToContexts",
96             "refreshSystemState",
97     });
98 
99     interface Stats {
100         int ASSIGN_JOBS_TO_CONTEXTS = 0;
101         int REFRESH_SYSTEM_STATE = 1;
102 
103         int COUNT = REFRESH_SYSTEM_STATE + 1;
104     }
105 
JobConcurrencyManager(JobSchedulerService service)106     JobConcurrencyManager(JobSchedulerService service) {
107         mService = service;
108         mLock = mService.mLock;
109         mConstants = service.mConstants;
110         mContext = service.getContext();
111 
112         mHandler = BackgroundThread.getHandler();
113     }
114 
onSystemReady()115     public void onSystemReady() {
116         mPowerManager = mContext.getSystemService(PowerManager.class);
117 
118         final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
119         filter.addAction(Intent.ACTION_SCREEN_OFF);
120         mContext.registerReceiver(mReceiver, filter);
121 
122         onInteractiveStateChanged(mPowerManager.isInteractive());
123     }
124 
125     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
126         @Override
127         public void onReceive(Context context, Intent intent) {
128             switch (intent.getAction()) {
129                 case Intent.ACTION_SCREEN_ON:
130                     onInteractiveStateChanged(true);
131                     break;
132                 case Intent.ACTION_SCREEN_OFF:
133                     onInteractiveStateChanged(false);
134                     break;
135             }
136         }
137     };
138 
139     /**
140      * Called when the screen turns on / off.
141      */
onInteractiveStateChanged(boolean interactive)142     private void onInteractiveStateChanged(boolean interactive) {
143         synchronized (mLock) {
144             if (mCurrentInteractiveState == interactive) {
145                 return;
146             }
147             mCurrentInteractiveState = interactive;
148             if (DEBUG) {
149                 Slog.d(TAG, "Interactive: " + interactive);
150             }
151 
152             final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis();
153             if (interactive) {
154                 mLastScreenOnRealtime = nowRealtime;
155                 mEffectiveInteractiveState = true;
156 
157                 mHandler.removeCallbacks(mRampUpForScreenOff);
158             } else {
159                 mLastScreenOffRealtime = nowRealtime;
160 
161                 // Set mEffectiveInteractiveState to false after the delay, when we may increase
162                 // the concurrency.
163                 // We don't need a wakeup alarm here. When there's a pending job, there should
164                 // also be jobs running too, meaning the device should be awake.
165 
166                 // Note: we can't directly do postDelayed(this::rampUpForScreenOn), because
167                 // we need the exact same instance for removeCallbacks().
168                 mHandler.postDelayed(mRampUpForScreenOff,
169                         mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.getValue());
170             }
171         }
172     }
173 
174     private final Runnable mRampUpForScreenOff = this::rampUpForScreenOff;
175 
176     /**
177      * Called in {@link Constants#SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS} after
178      * the screen turns off, in order to increase concurrency.
179      */
rampUpForScreenOff()180     private void rampUpForScreenOff() {
181         synchronized (mLock) {
182             // Make sure the screen has really been off for the configured duration.
183             // (There could be a race.)
184             if (!mEffectiveInteractiveState) {
185                 return;
186             }
187             if (mLastScreenOnRealtime > mLastScreenOffRealtime) {
188                 return;
189             }
190             final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
191             if ((mLastScreenOffRealtime
192                     + mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.getValue())
193                     > now) {
194                 return;
195             }
196 
197             mEffectiveInteractiveState = false;
198 
199             if (DEBUG) {
200                 Slog.d(TAG, "Ramping up concurrency");
201             }
202 
203             mService.maybeRunPendingJobsLocked();
204         }
205     }
206 
isFgJob(JobStatus job)207     private boolean isFgJob(JobStatus job) {
208         return job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP;
209     }
210 
211     @GuardedBy("mLock")
refreshSystemStateLocked()212     private void refreshSystemStateLocked() {
213         final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
214 
215         // Only refresh the information every so often.
216         if (nowUptime < mNextSystemStateRefreshTime) {
217             return;
218         }
219 
220         final long start = mStatLogger.getTime();
221         mNextSystemStateRefreshTime = nowUptime + SYSTEM_STATE_REFRESH_MIN_INTERVAL;
222 
223         mLastMemoryTrimLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
224         try {
225             mLastMemoryTrimLevel = ActivityManager.getService().getMemoryTrimLevel();
226         } catch (RemoteException e) {
227         }
228 
229         mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start);
230     }
231 
232     @GuardedBy("mLock")
updateMaxCountsLocked()233     private void updateMaxCountsLocked() {
234         refreshSystemStateLocked();
235 
236         final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState
237                 ? mConstants.MAX_JOB_COUNTS_SCREEN_ON
238                 : mConstants.MAX_JOB_COUNTS_SCREEN_OFF;
239 
240 
241         switch (mLastMemoryTrimLevel) {
242             case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
243                 mMaxJobCounts = jobCounts.moderate;
244                 break;
245             case ProcessStats.ADJ_MEM_FACTOR_LOW:
246                 mMaxJobCounts = jobCounts.low;
247                 break;
248             case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
249                 mMaxJobCounts = jobCounts.critical;
250                 break;
251             default:
252                 mMaxJobCounts = jobCounts.normal;
253                 break;
254         }
255     }
256 
257     /**
258      * Takes jobs from pending queue and runs them on available contexts.
259      * If no contexts are available, preempts lower priority jobs to
260      * run higher priority ones.
261      * Lock on mJobs before calling this function.
262      */
263     @GuardedBy("mLock")
assignJobsToContextsLocked()264     void assignJobsToContextsLocked() {
265         final long start = mStatLogger.getTime();
266 
267         assignJobsToContextsInternalLocked();
268 
269         mStatLogger.logDurationStat(Stats.ASSIGN_JOBS_TO_CONTEXTS, start);
270     }
271 
272     @GuardedBy("mLock")
assignJobsToContextsInternalLocked()273     private void assignJobsToContextsInternalLocked() {
274         if (DEBUG) {
275             Slog.d(TAG, printPendingQueueLocked());
276         }
277 
278         final JobPackageTracker tracker = mService.mJobPackageTracker;
279         final List<JobStatus> pendingJobs = mService.mPendingJobs;
280         final List<JobServiceContext> activeServices = mService.mActiveServices;
281         final List<StateController> controllers = mService.mControllers;
282 
283         updateMaxCountsLocked();
284 
285         // To avoid GC churn, we recycle the arrays.
286         JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap;
287         boolean[] slotChanged = mRecycledSlotChanged;
288         int[] preferredUidForContext = mRecycledPreferredUidForContext;
289 
290 
291         // Initialize the work variables and also count running jobs.
292         mJobCountTracker.reset(
293                 mMaxJobCounts.getMaxTotal(),
294                 mMaxJobCounts.getMaxBg(),
295                 mMaxJobCounts.getMinBg());
296 
297         for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
298             final JobServiceContext js = mService.mActiveServices.get(i);
299             final JobStatus status = js.getRunningJobLocked();
300 
301             if ((contextIdToJobMap[i] = status) != null) {
302                 mJobCountTracker.incrementRunningJobCount(isFgJob(status));
303             }
304 
305             slotChanged[i] = false;
306             preferredUidForContext[i] = js.getPreferredUid();
307         }
308         if (DEBUG) {
309             Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
310         }
311 
312         // Next, update the job priorities, and also count the pending FG / BG jobs.
313         for (int i = 0; i < pendingJobs.size(); i++) {
314             final JobStatus pending = pendingJobs.get(i);
315 
316             // If job is already running, go to next job.
317             int jobRunningContext = findJobContextIdFromMap(pending, contextIdToJobMap);
318             if (jobRunningContext != -1) {
319                 continue;
320             }
321 
322             final int priority = mService.evaluateJobPriorityLocked(pending);
323             pending.lastEvaluatedPriority = priority;
324 
325             mJobCountTracker.incrementPendingJobCount(isFgJob(pending));
326         }
327 
328         mJobCountTracker.onCountDone();
329 
330         for (int i = 0; i < pendingJobs.size(); i++) {
331             final JobStatus nextPending = pendingJobs.get(i);
332 
333             // Unfortunately we need to repeat this relatively expensive check.
334             int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
335             if (jobRunningContext != -1) {
336                 continue;
337             }
338 
339             final boolean isPendingFg = isFgJob(nextPending);
340 
341             // Find an available slot for nextPending. The context should be available OR
342             // it should have lowest priority among all running jobs
343             // (sharing the same Uid as nextPending)
344             int minPriorityForPreemption = Integer.MAX_VALUE;
345             int selectedContextId = -1;
346             boolean startingJob = false;
347             for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
348                 JobStatus job = contextIdToJobMap[j];
349                 int preferredUid = preferredUidForContext[j];
350                 if (job == null) {
351                     final boolean preferredUidOkay = (preferredUid == nextPending.getUid())
352                             || (preferredUid == JobServiceContext.NO_PREFERRED_UID);
353 
354                     if (preferredUidOkay && mJobCountTracker.canJobStart(isPendingFg)) {
355                         // This slot is free, and we haven't yet hit the limit on
356                         // concurrent jobs...  we can just throw the job in to here.
357                         selectedContextId = j;
358                         startingJob = true;
359                         break;
360                     }
361                     // No job on this context, but nextPending can't run here because
362                     // the context has a preferred Uid or we have reached the limit on
363                     // concurrent jobs.
364                     continue;
365                 }
366                 if (job.getUid() != nextPending.getUid()) {
367                     continue;
368                 }
369 
370                 final int jobPriority = mService.evaluateJobPriorityLocked(job);
371                 if (jobPriority >= nextPending.lastEvaluatedPriority) {
372                     continue;
373                 }
374 
375                 // TODO lastEvaluatedPriority should be evaluateJobPriorityLocked. (double check it)
376                 if (minPriorityForPreemption > nextPending.lastEvaluatedPriority) {
377                     minPriorityForPreemption = nextPending.lastEvaluatedPriority;
378                     selectedContextId = j;
379                     // In this case, we're just going to preempt a low priority job, we're not
380                     // actually starting a job, so don't set startingJob.
381                 }
382             }
383             if (selectedContextId != -1) {
384                 contextIdToJobMap[selectedContextId] = nextPending;
385                 slotChanged[selectedContextId] = true;
386             }
387             if (startingJob) {
388                 // Increase the counters when we're going to start a job.
389                 mJobCountTracker.onStartingNewJob(isPendingFg);
390             }
391         }
392         if (DEBUG) {
393             Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
394         }
395 
396         mJobCountTracker.logStatus();
397 
398         tracker.noteConcurrency(mJobCountTracker.getTotalRunningJobCountToNote(),
399                 mJobCountTracker.getFgRunningJobCountToNote());
400 
401         for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
402             boolean preservePreferredUid = false;
403             if (slotChanged[i]) {
404                 JobStatus js = activeServices.get(i).getRunningJobLocked();
405                 if (js != null) {
406                     if (DEBUG) {
407                         Slog.d(TAG, "preempting job: "
408                                 + activeServices.get(i).getRunningJobLocked());
409                     }
410                     // preferredUid will be set to uid of currently running job.
411                     activeServices.get(i).preemptExecutingJobLocked();
412                     preservePreferredUid = true;
413                 } else {
414                     final JobStatus pendingJob = contextIdToJobMap[i];
415                     if (DEBUG) {
416                         Slog.d(TAG, "About to run job on context "
417                                 + i + ", job: " + pendingJob);
418                     }
419                     for (int ic=0; ic<controllers.size(); ic++) {
420                         controllers.get(ic).prepareForExecutionLocked(pendingJob);
421                     }
422                     if (!activeServices.get(i).executeRunnableJob(pendingJob)) {
423                         Slog.d(TAG, "Error executing " + pendingJob);
424                     }
425                     if (pendingJobs.remove(pendingJob)) {
426                         tracker.noteNonpending(pendingJob);
427                     }
428                 }
429             }
430             if (!preservePreferredUid) {
431                 activeServices.get(i).clearPreferredUid();
432             }
433         }
434     }
435 
findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map)436     private static int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
437         for (int i=0; i<map.length; i++) {
438             if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
439                 return i;
440             }
441         }
442         return -1;
443     }
444 
445     @GuardedBy("mLock")
printPendingQueueLocked()446     private String printPendingQueueLocked() {
447         StringBuilder s = new StringBuilder("Pending queue: ");
448         Iterator<JobStatus> it = mService.mPendingJobs.iterator();
449         while (it.hasNext()) {
450             JobStatus js = it.next();
451             s.append("(")
452                     .append(js.getJob().getId())
453                     .append(", ")
454                     .append(js.getUid())
455                     .append(") ");
456         }
457         return s.toString();
458     }
459 
printContextIdToJobMap(JobStatus[] map, String initial)460     private static String printContextIdToJobMap(JobStatus[] map, String initial) {
461         StringBuilder s = new StringBuilder(initial + ": ");
462         for (int i=0; i<map.length; i++) {
463             s.append("(")
464                     .append(map[i] == null? -1: map[i].getJobId())
465                     .append(map[i] == null? -1: map[i].getUid())
466                     .append(")" );
467         }
468         return s.toString();
469     }
470 
471 
dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime)472     public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
473         pw.println("Concurrency:");
474 
475         pw.increaseIndent();
476         try {
477             pw.print("Screen state: current ");
478             pw.print(mCurrentInteractiveState ? "ON" : "OFF");
479             pw.print("  effective ");
480             pw.print(mEffectiveInteractiveState ? "ON" : "OFF");
481             pw.println();
482 
483             pw.print("Last screen ON : ");
484             TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOnRealtime, now);
485             pw.println();
486 
487             pw.print("Last screen OFF: ");
488             TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOffRealtime, now);
489             pw.println();
490 
491             pw.println();
492 
493             pw.println("Current max jobs:");
494             pw.println("  ");
495             pw.println(mJobCountTracker);
496 
497             pw.println();
498 
499             pw.print("mLastMemoryTrimLevel: ");
500             pw.print(mLastMemoryTrimLevel);
501             pw.println();
502 
503             mStatLogger.dump(pw);
504         } finally {
505             pw.decreaseIndent();
506         }
507     }
508 
dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime)509     public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) {
510         final long token = proto.start(tag);
511 
512         proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE,
513                 mCurrentInteractiveState);
514         proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE,
515                 mEffectiveInteractiveState);
516 
517         proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS,
518                 nowRealtime - mLastScreenOnRealtime);
519         proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
520                 nowRealtime - mLastScreenOffRealtime);
521 
522         mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER);
523 
524         proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL,
525                 mLastMemoryTrimLevel);
526 
527         proto.end(token);
528     }
529 
530     /**
531      * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running /
532      * pending, how many more job can start.
533      *
534      * Extracted for testing and logging.
535      */
536     @VisibleForTesting
537     static class JobCountTracker {
538         private int mConfigNumMaxTotalJobs;
539         private int mConfigNumMaxBgJobs;
540         private int mConfigNumMinBgJobs;
541 
542         private int mNumRunningFgJobs;
543         private int mNumRunningBgJobs;
544 
545         private int mNumPendingFgJobs;
546         private int mNumPendingBgJobs;
547 
548         private int mNumStartingFgJobs;
549         private int mNumStartingBgJobs;
550 
551         private int mNumReservedForBg;
552         private int mNumActualMaxFgJobs;
553         private int mNumActualMaxBgJobs;
554 
reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs)555         void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) {
556             mConfigNumMaxTotalJobs = numTotalMaxJobs;
557             mConfigNumMaxBgJobs = numMaxBgJobs;
558             mConfigNumMinBgJobs = numMinBgJobs;
559 
560             mNumRunningFgJobs = 0;
561             mNumRunningBgJobs = 0;
562 
563             mNumPendingFgJobs = 0;
564             mNumPendingBgJobs = 0;
565 
566             mNumStartingFgJobs = 0;
567             mNumStartingBgJobs = 0;
568 
569             mNumReservedForBg = 0;
570             mNumActualMaxFgJobs = 0;
571             mNumActualMaxBgJobs = 0;
572         }
573 
incrementRunningJobCount(boolean isFg)574         void incrementRunningJobCount(boolean isFg) {
575             if (isFg) {
576                 mNumRunningFgJobs++;
577             } else {
578                 mNumRunningBgJobs++;
579             }
580         }
581 
incrementPendingJobCount(boolean isFg)582         void incrementPendingJobCount(boolean isFg) {
583             if (isFg) {
584                 mNumPendingFgJobs++;
585             } else {
586                 mNumPendingBgJobs++;
587             }
588         }
589 
onStartingNewJob(boolean isFg)590         void onStartingNewJob(boolean isFg) {
591             if (isFg) {
592                 mNumStartingFgJobs++;
593             } else {
594                 mNumStartingBgJobs++;
595             }
596         }
597 
onCountDone()598         void onCountDone() {
599             // Note some variables are used only here but are made class members in order to have
600             // them on logcat / dumpsys.
601 
602             // How many slots should we allocate to BG jobs at least?
603             // That's basically "getMinBg()", but if there are less jobs, decrease it.
604             // (e.g. even if min-bg is 2, if there's only 1 running+pending job, this has to be 1.)
605             final int reservedForBg = Math.min(
606                     mConfigNumMinBgJobs,
607                     mNumRunningBgJobs + mNumPendingBgJobs);
608 
609             // However, if there are FG jobs already running, we have to adjust it.
610             mNumReservedForBg = Math.min(reservedForBg,
611                     mConfigNumMaxTotalJobs - mNumRunningFgJobs);
612 
613             // Max FG is [total - [number needed for BG jobs]]
614             // [number needed for BG jobs] is the bigger one of [running BG] or [reserved BG]
615             final int maxFg =
616                     mConfigNumMaxTotalJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg);
617 
618             // The above maxFg is the theoretical max. If there are less FG jobs, the actual
619             // max FG will be lower accordingly.
620             mNumActualMaxFgJobs = Math.min(
621                     maxFg,
622                     mNumRunningFgJobs + mNumPendingFgJobs);
623 
624             // Max BG is [total - actual max FG], but cap at [config max BG].
625             final int maxBg = Math.min(
626                     mConfigNumMaxBgJobs,
627                     mConfigNumMaxTotalJobs - mNumActualMaxFgJobs);
628 
629             // If there are less BG jobs than maxBg, then reduce the actual max BG accordingly.
630             // This isn't needed for the logic to work, but this will give consistent output
631             // on logcat and dumpsys.
632             mNumActualMaxBgJobs = Math.min(
633                     maxBg,
634                     mNumRunningBgJobs + mNumPendingBgJobs);
635         }
636 
canJobStart(boolean isFg)637         boolean canJobStart(boolean isFg) {
638             if (isFg) {
639                 return mNumRunningFgJobs + mNumStartingFgJobs < mNumActualMaxFgJobs;
640             } else {
641                 return mNumRunningBgJobs + mNumStartingBgJobs < mNumActualMaxBgJobs;
642             }
643         }
644 
getNumStartingFgJobs()645         public int getNumStartingFgJobs() {
646             return mNumStartingFgJobs;
647         }
648 
getNumStartingBgJobs()649         public int getNumStartingBgJobs() {
650             return mNumStartingBgJobs;
651         }
652 
getTotalRunningJobCountToNote()653         int getTotalRunningJobCountToNote() {
654             return mNumRunningFgJobs + mNumRunningBgJobs
655                     + mNumStartingFgJobs + mNumStartingBgJobs;
656         }
657 
getFgRunningJobCountToNote()658         int getFgRunningJobCountToNote() {
659             return mNumRunningFgJobs + mNumStartingFgJobs;
660         }
661 
logStatus()662         void logStatus() {
663             if (DEBUG) {
664                 Slog.d(TAG, "assignJobsToContexts: " + this);
665             }
666         }
667 
toString()668         public String toString() {
669             final int totalFg = mNumRunningFgJobs + mNumStartingFgJobs;
670             final int totalBg = mNumRunningBgJobs + mNumStartingBgJobs;
671             return String.format(
672                     "Config={tot=%d bg min/max=%d/%d}"
673                             + " Running[FG/BG (total)]: %d / %d (%d)"
674                             + " Pending: %d / %d (%d)"
675                             + " Actual max: %d%s / %d%s (%d%s)"
676                             + " Res BG: %d"
677                             + " Starting: %d / %d (%d)"
678                             + " Total: %d%s / %d%s (%d%s)",
679                     mConfigNumMaxTotalJobs,
680                     mConfigNumMinBgJobs,
681                     mConfigNumMaxBgJobs,
682 
683                     mNumRunningFgJobs, mNumRunningBgJobs,
684                     mNumRunningFgJobs + mNumRunningBgJobs,
685 
686                     mNumPendingFgJobs, mNumPendingBgJobs,
687                     mNumPendingFgJobs + mNumPendingBgJobs,
688 
689                     mNumActualMaxFgJobs, (totalFg <= mConfigNumMaxTotalJobs) ? "" : "*",
690                     mNumActualMaxBgJobs, (totalBg <= mConfigNumMaxBgJobs) ? "" : "*",
691 
692                     mNumActualMaxFgJobs + mNumActualMaxBgJobs,
693                     (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumMaxTotalJobs)
694                             ? "" : "*",
695 
696                     mNumReservedForBg,
697 
698                     mNumStartingFgJobs, mNumStartingBgJobs, mNumStartingFgJobs + mNumStartingBgJobs,
699 
700                     totalFg, (totalFg <= mNumActualMaxFgJobs) ? "" : "*",
701                     totalBg, (totalBg <= mNumActualMaxBgJobs) ? "" : "*",
702                     totalFg + totalBg, (totalFg + totalBg <= mConfigNumMaxTotalJobs) ? "" : "*"
703             );
704         }
705 
dumpProto(ProtoOutputStream proto, long fieldId)706         public void dumpProto(ProtoOutputStream proto, long fieldId) {
707             final long token = proto.start(fieldId);
708 
709             proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_TOTAL_JOBS, mConfigNumMaxTotalJobs);
710             proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_BG_JOBS, mConfigNumMaxBgJobs);
711             proto.write(JobCountTrackerProto.CONFIG_NUM_MIN_BG_JOBS, mConfigNumMinBgJobs);
712 
713             proto.write(JobCountTrackerProto.NUM_RUNNING_FG_JOBS, mNumRunningFgJobs);
714             proto.write(JobCountTrackerProto.NUM_RUNNING_BG_JOBS, mNumRunningBgJobs);
715 
716             proto.write(JobCountTrackerProto.NUM_PENDING_FG_JOBS, mNumPendingFgJobs);
717             proto.write(JobCountTrackerProto.NUM_PENDING_BG_JOBS, mNumPendingBgJobs);
718 
719             proto.end(token);
720         }
721     }
722 }
723