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                 if (minPriorityForPreemption > jobPriority) {
376                     // Step down the preemption threshold - wind up replacing
377                     // the lowest-priority running job
378                     minPriorityForPreemption = jobPriority;
379                     selectedContextId = j;
380                     // In this case, we're just going to preempt a low priority job, we're not
381                     // actually starting a job, so don't set startingJob.
382                 }
383             }
384             if (selectedContextId != -1) {
385                 contextIdToJobMap[selectedContextId] = nextPending;
386                 slotChanged[selectedContextId] = true;
387             }
388             if (startingJob) {
389                 // Increase the counters when we're going to start a job.
390                 mJobCountTracker.onStartingNewJob(isPendingFg);
391             }
392         }
393         if (DEBUG) {
394             Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
395         }
396 
397         mJobCountTracker.logStatus();
398 
399         tracker.noteConcurrency(mJobCountTracker.getTotalRunningJobCountToNote(),
400                 mJobCountTracker.getFgRunningJobCountToNote());
401 
402         for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
403             boolean preservePreferredUid = false;
404             if (slotChanged[i]) {
405                 JobStatus js = activeServices.get(i).getRunningJobLocked();
406                 if (js != null) {
407                     if (DEBUG) {
408                         Slog.d(TAG, "preempting job: "
409                                 + activeServices.get(i).getRunningJobLocked());
410                     }
411                     // preferredUid will be set to uid of currently running job.
412                     activeServices.get(i).preemptExecutingJobLocked();
413                     preservePreferredUid = true;
414                 } else {
415                     final JobStatus pendingJob = contextIdToJobMap[i];
416                     if (DEBUG) {
417                         Slog.d(TAG, "About to run job on context "
418                                 + i + ", job: " + pendingJob);
419                     }
420                     for (int ic=0; ic<controllers.size(); ic++) {
421                         controllers.get(ic).prepareForExecutionLocked(pendingJob);
422                     }
423                     if (!activeServices.get(i).executeRunnableJob(pendingJob)) {
424                         Slog.d(TAG, "Error executing " + pendingJob);
425                     }
426                     if (pendingJobs.remove(pendingJob)) {
427                         tracker.noteNonpending(pendingJob);
428                     }
429                 }
430             }
431             if (!preservePreferredUid) {
432                 activeServices.get(i).clearPreferredUid();
433             }
434         }
435     }
436 
findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map)437     private static int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
438         for (int i=0; i<map.length; i++) {
439             if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
440                 return i;
441             }
442         }
443         return -1;
444     }
445 
446     @GuardedBy("mLock")
printPendingQueueLocked()447     private String printPendingQueueLocked() {
448         StringBuilder s = new StringBuilder("Pending queue: ");
449         Iterator<JobStatus> it = mService.mPendingJobs.iterator();
450         while (it.hasNext()) {
451             JobStatus js = it.next();
452             s.append("(")
453                     .append(js.getJob().getId())
454                     .append(", ")
455                     .append(js.getUid())
456                     .append(") ");
457         }
458         return s.toString();
459     }
460 
printContextIdToJobMap(JobStatus[] map, String initial)461     private static String printContextIdToJobMap(JobStatus[] map, String initial) {
462         StringBuilder s = new StringBuilder(initial + ": ");
463         for (int i=0; i<map.length; i++) {
464             s.append("(")
465                     .append(map[i] == null? -1: map[i].getJobId())
466                     .append(map[i] == null? -1: map[i].getUid())
467                     .append(")" );
468         }
469         return s.toString();
470     }
471 
472 
dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime)473     public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
474         pw.println("Concurrency:");
475 
476         pw.increaseIndent();
477         try {
478             pw.print("Screen state: current ");
479             pw.print(mCurrentInteractiveState ? "ON" : "OFF");
480             pw.print("  effective ");
481             pw.print(mEffectiveInteractiveState ? "ON" : "OFF");
482             pw.println();
483 
484             pw.print("Last screen ON: ");
485             TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOnRealtime, now);
486             pw.println();
487 
488             pw.print("Last screen OFF: ");
489             TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOffRealtime, now);
490             pw.println();
491 
492             pw.println();
493 
494             pw.println("Current max jobs:");
495             pw.println("  ");
496             pw.println(mJobCountTracker);
497 
498             pw.println();
499 
500             pw.print("mLastMemoryTrimLevel: ");
501             pw.print(mLastMemoryTrimLevel);
502             pw.println();
503 
504             mStatLogger.dump(pw);
505         } finally {
506             pw.decreaseIndent();
507         }
508     }
509 
dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime)510     public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) {
511         final long token = proto.start(tag);
512 
513         proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE_STATE, mCurrentInteractiveState);
514         proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE_STATE,
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, mLastMemoryTrimLevel);
525 
526         mStatLogger.dumpProto(proto, JobConcurrencyManagerProto.STATS);
527 
528         proto.end(token);
529     }
530 
531     /**
532      * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running /
533      * pending, how many more job can start.
534      *
535      * Extracted for testing and logging.
536      */
537     @VisibleForTesting
538     static class JobCountTracker {
539         private int mConfigNumMaxTotalJobs;
540         private int mConfigNumMaxBgJobs;
541         private int mConfigNumMinBgJobs;
542 
543         private int mNumRunningFgJobs;
544         private int mNumRunningBgJobs;
545 
546         private int mNumPendingFgJobs;
547         private int mNumPendingBgJobs;
548 
549         private int mNumStartingFgJobs;
550         private int mNumStartingBgJobs;
551 
552         private int mNumReservedForBg;
553         private int mNumActualMaxFgJobs;
554         private int mNumActualMaxBgJobs;
555 
reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs)556         void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) {
557             mConfigNumMaxTotalJobs = numTotalMaxJobs;
558             mConfigNumMaxBgJobs = numMaxBgJobs;
559             mConfigNumMinBgJobs = numMinBgJobs;
560 
561             mNumRunningFgJobs = 0;
562             mNumRunningBgJobs = 0;
563 
564             mNumPendingFgJobs = 0;
565             mNumPendingBgJobs = 0;
566 
567             mNumStartingFgJobs = 0;
568             mNumStartingBgJobs = 0;
569 
570             mNumReservedForBg = 0;
571             mNumActualMaxFgJobs = 0;
572             mNumActualMaxBgJobs = 0;
573         }
574 
incrementRunningJobCount(boolean isFg)575         void incrementRunningJobCount(boolean isFg) {
576             if (isFg) {
577                 mNumRunningFgJobs++;
578             } else {
579                 mNumRunningBgJobs++;
580             }
581         }
582 
incrementPendingJobCount(boolean isFg)583         void incrementPendingJobCount(boolean isFg) {
584             if (isFg) {
585                 mNumPendingFgJobs++;
586             } else {
587                 mNumPendingBgJobs++;
588             }
589         }
590 
onStartingNewJob(boolean isFg)591         void onStartingNewJob(boolean isFg) {
592             if (isFg) {
593                 mNumStartingFgJobs++;
594             } else {
595                 mNumStartingBgJobs++;
596             }
597         }
598 
onCountDone()599         void onCountDone() {
600             // Note some variables are used only here but are made class members in order to have
601             // them on logcat / dumpsys.
602 
603             // How many slots should we allocate to BG jobs at least?
604             // That's basically "getMinBg()", but if there are less jobs, decrease it.
605             // (e.g. even if min-bg is 2, if there's only 1 running+pending job, this has to be 1.)
606             final int reservedForBg = Math.min(
607                     mConfigNumMinBgJobs,
608                     mNumRunningBgJobs + mNumPendingBgJobs);
609 
610             // However, if there are FG jobs already running, we have to adjust it.
611             mNumReservedForBg = Math.min(reservedForBg,
612                     mConfigNumMaxTotalJobs - mNumRunningFgJobs);
613 
614             // Max FG is [total - [number needed for BG jobs]]
615             // [number needed for BG jobs] is the bigger one of [running BG] or [reserved BG]
616             final int maxFg =
617                     mConfigNumMaxTotalJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg);
618 
619             // The above maxFg is the theoretical max. If there are less FG jobs, the actual
620             // max FG will be lower accordingly.
621             mNumActualMaxFgJobs = Math.min(
622                     maxFg,
623                     mNumRunningFgJobs + mNumPendingFgJobs);
624 
625             // Max BG is [total - actual max FG], but cap at [config max BG].
626             final int maxBg = Math.min(
627                     mConfigNumMaxBgJobs,
628                     mConfigNumMaxTotalJobs - mNumActualMaxFgJobs);
629 
630             // If there are less BG jobs than maxBg, then reduce the actual max BG accordingly.
631             // This isn't needed for the logic to work, but this will give consistent output
632             // on logcat and dumpsys.
633             mNumActualMaxBgJobs = Math.min(
634                     maxBg,
635                     mNumRunningBgJobs + mNumPendingBgJobs);
636         }
637 
canJobStart(boolean isFg)638         boolean canJobStart(boolean isFg) {
639             if (isFg) {
640                 return mNumRunningFgJobs + mNumStartingFgJobs < mNumActualMaxFgJobs;
641             } else {
642                 return mNumRunningBgJobs + mNumStartingBgJobs < mNumActualMaxBgJobs;
643             }
644         }
645 
getNumStartingFgJobs()646         public int getNumStartingFgJobs() {
647             return mNumStartingFgJobs;
648         }
649 
getNumStartingBgJobs()650         public int getNumStartingBgJobs() {
651             return mNumStartingBgJobs;
652         }
653 
getTotalRunningJobCountToNote()654         int getTotalRunningJobCountToNote() {
655             return mNumRunningFgJobs + mNumRunningBgJobs
656                     + mNumStartingFgJobs + mNumStartingBgJobs;
657         }
658 
getFgRunningJobCountToNote()659         int getFgRunningJobCountToNote() {
660             return mNumRunningFgJobs + mNumStartingFgJobs;
661         }
662 
logStatus()663         void logStatus() {
664             if (DEBUG) {
665                 Slog.d(TAG, "assignJobsToContexts: " + this);
666             }
667         }
668 
toString()669         public String toString() {
670             final int totalFg = mNumRunningFgJobs + mNumStartingFgJobs;
671             final int totalBg = mNumRunningBgJobs + mNumStartingBgJobs;
672             return String.format(
673                     "Config={tot=%d bg min/max=%d/%d}"
674                             + " Running[FG/BG (total)]: %d / %d (%d)"
675                             + " Pending: %d / %d (%d)"
676                             + " Actual max: %d%s / %d%s (%d%s)"
677                             + " Res BG: %d"
678                             + " Starting: %d / %d (%d)"
679                             + " Total: %d%s / %d%s (%d%s)",
680                     mConfigNumMaxTotalJobs, mConfigNumMinBgJobs, mConfigNumMaxBgJobs,
681 
682                     mNumRunningFgJobs, mNumRunningBgJobs, mNumRunningFgJobs + mNumRunningBgJobs,
683 
684                     mNumPendingFgJobs, mNumPendingBgJobs, mNumPendingFgJobs + mNumPendingBgJobs,
685 
686                     mNumActualMaxFgJobs, (totalFg <= mConfigNumMaxTotalJobs) ? "" : "*",
687                     mNumActualMaxBgJobs, (totalBg <= mConfigNumMaxBgJobs) ? "" : "*",
688                     mNumActualMaxFgJobs + mNumActualMaxBgJobs,
689                     (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumMaxTotalJobs)
690                             ? "" : "*",
691 
692                     mNumReservedForBg,
693 
694                     mNumStartingFgJobs, mNumStartingBgJobs, mNumStartingFgJobs + mNumStartingBgJobs,
695 
696                     totalFg, (totalFg <= mNumActualMaxFgJobs) ? "" : "*",
697                     totalBg, (totalBg <= mNumActualMaxBgJobs) ? "" : "*",
698                     totalFg + totalBg, (totalFg + totalBg <= mConfigNumMaxTotalJobs) ? "" : "*"
699             );
700         }
701 
dumpProto(ProtoOutputStream proto, long fieldId)702         public void dumpProto(ProtoOutputStream proto, long fieldId) {
703             final long token = proto.start(fieldId);
704 
705             proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_TOTAL_JOBS, mConfigNumMaxTotalJobs);
706             proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_BG_JOBS, mConfigNumMaxBgJobs);
707             proto.write(JobCountTrackerProto.CONFIG_NUM_MIN_BG_JOBS, mConfigNumMinBgJobs);
708 
709             proto.write(JobCountTrackerProto.NUM_RUNNING_FG_JOBS, mNumRunningFgJobs);
710             proto.write(JobCountTrackerProto.NUM_RUNNING_BG_JOBS, mNumRunningBgJobs);
711 
712             proto.write(JobCountTrackerProto.NUM_PENDING_FG_JOBS, mNumPendingFgJobs);
713             proto.write(JobCountTrackerProto.NUM_PENDING_BG_JOBS, mNumPendingBgJobs);
714 
715             proto.write(JobCountTrackerProto.NUM_ACTUAL_MAX_FG_JOBS, mNumActualMaxFgJobs);
716             proto.write(JobCountTrackerProto.NUM_ACTUAL_MAX_BG_JOBS, mNumActualMaxBgJobs);
717 
718             proto.write(JobCountTrackerProto.NUM_RESERVED_FOR_BG, mNumReservedForBg);
719 
720             proto.write(JobCountTrackerProto.NUM_STARTING_FG_JOBS, mNumStartingFgJobs);
721             proto.write(JobCountTrackerProto.NUM_STARTING_BG_JOBS, mNumStartingBgJobs);
722 
723             proto.end(token);
724         }
725     }
726 }
727