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