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