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 static com.android.server.job.JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; 20 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 21 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.app.ActivityManager; 26 import android.app.ActivityManagerInternal; 27 import android.app.UserSwitchObserver; 28 import android.app.job.JobInfo; 29 import android.app.job.JobParameters; 30 import android.content.BroadcastReceiver; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.pm.UserInfo; 35 import android.os.Handler; 36 import android.os.PowerManager; 37 import android.os.RemoteException; 38 import android.os.UserHandle; 39 import android.provider.DeviceConfig; 40 import android.util.ArraySet; 41 import android.util.IndentingPrintWriter; 42 import android.util.Pair; 43 import android.util.Pools; 44 import android.util.Slog; 45 import android.util.SparseArrayMap; 46 import android.util.SparseIntArray; 47 import android.util.SparseLongArray; 48 import android.util.TimeUtils; 49 import android.util.proto.ProtoOutputStream; 50 51 import com.android.internal.R; 52 import com.android.internal.annotations.GuardedBy; 53 import com.android.internal.annotations.VisibleForTesting; 54 import com.android.internal.app.procstats.ProcessStats; 55 import com.android.internal.util.StatLogger; 56 import com.android.server.JobSchedulerBackgroundThread; 57 import com.android.server.LocalServices; 58 import com.android.server.job.controllers.JobStatus; 59 import com.android.server.job.controllers.StateController; 60 import com.android.server.pm.UserManagerInternal; 61 62 import java.lang.annotation.Retention; 63 import java.lang.annotation.RetentionPolicy; 64 import java.util.Iterator; 65 import java.util.List; 66 import java.util.function.Consumer; 67 68 /** 69 * This class decides, given the various configuration and the system status, which jobs can start 70 * and which {@link JobServiceContext} to run each job on. 71 */ 72 class JobConcurrencyManager { 73 private static final String TAG = JobSchedulerService.TAG + ".Concurrency"; 74 private static final boolean DEBUG = JobSchedulerService.DEBUG; 75 76 static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_"; 77 private static final String KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 78 CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms"; 79 private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000; 80 private static final String KEY_PKG_CONCURRENCY_LIMIT_EJ = 81 CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_ej"; 82 private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_EJ = 3; 83 private static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR = 84 CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular"; 85 private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = MAX_JOB_CONTEXTS_COUNT / 2; 86 87 /** 88 * Set of possible execution types that a job can have. The actual type(s) of a job are based 89 * on the {@link JobStatus#lastEvaluatedPriority}, which is typically evaluated right before 90 * execution (when we're trying to determine which jobs to run next) and won't change after the 91 * job has started executing. 92 * 93 * Try to give higher priority types lower values. 94 * 95 * @see #getJobWorkTypes(JobStatus) 96 */ 97 98 /** Job shouldn't run or qualify as any other work type. */ 99 static final int WORK_TYPE_NONE = 0; 100 /** The job is for an app in the TOP state for a currently active user. */ 101 static final int WORK_TYPE_TOP = 1 << 0; 102 /** 103 * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher 104 * state (excluding {@link ActivityManager#PROCESS_STATE_TOP} for a currently active user. 105 */ 106 static final int WORK_TYPE_FGS = 1 << 1; 107 /** The job is allowed to run as an expedited job for a currently active user. */ 108 static final int WORK_TYPE_EJ = 1 << 2; 109 /** 110 * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP}, 111 * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a currently active user, so 112 * can run as a background job. 113 */ 114 static final int WORK_TYPE_BG = 1 << 3; 115 /** 116 * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher 117 * state, or is allowed to run as an expedited job, but is for a completely background user. 118 */ 119 static final int WORK_TYPE_BGUSER_IMPORTANT = 1 << 4; 120 /** 121 * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP}, 122 * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a completely background user, 123 * so can run as a background user job. 124 */ 125 static final int WORK_TYPE_BGUSER = 1 << 5; 126 @VisibleForTesting 127 static final int NUM_WORK_TYPES = 6; 128 private static final int ALL_WORK_TYPES = (1 << NUM_WORK_TYPES) - 1; 129 130 @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = { 131 WORK_TYPE_NONE, 132 WORK_TYPE_TOP, 133 WORK_TYPE_FGS, 134 WORK_TYPE_EJ, 135 WORK_TYPE_BG, 136 WORK_TYPE_BGUSER_IMPORTANT, 137 WORK_TYPE_BGUSER 138 }) 139 @Retention(RetentionPolicy.SOURCE) 140 public @interface WorkType { 141 } 142 143 @VisibleForTesting workTypeToString(@orkType int workType)144 static String workTypeToString(@WorkType int workType) { 145 switch (workType) { 146 case WORK_TYPE_NONE: 147 return "NONE"; 148 case WORK_TYPE_TOP: 149 return "TOP"; 150 case WORK_TYPE_FGS: 151 return "FGS"; 152 case WORK_TYPE_EJ: 153 return "EJ"; 154 case WORK_TYPE_BG: 155 return "BG"; 156 case WORK_TYPE_BGUSER: 157 return "BGUSER"; 158 case WORK_TYPE_BGUSER_IMPORTANT: 159 return "BGUSER_IMPORTANT"; 160 default: 161 return "WORK(" + workType + ")"; 162 } 163 } 164 165 private final Object mLock; 166 private final JobSchedulerService mService; 167 private final Context mContext; 168 private final Handler mHandler; 169 170 private PowerManager mPowerManager; 171 172 private boolean mCurrentInteractiveState; 173 private boolean mEffectiveInteractiveState; 174 175 private long mLastScreenOnRealtime; 176 private long mLastScreenOffRealtime; 177 178 private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON = 179 new WorkConfigLimitsPerMemoryTrimLevel( 180 new WorkTypeConfig("screen_on_normal", 11, 181 // defaultMin 182 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), 183 Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2), 184 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)), 185 // defaultMax 186 List.of(Pair.create(WORK_TYPE_BG, 6), 187 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 2), 188 Pair.create(WORK_TYPE_BGUSER, 3)) 189 ), 190 new WorkTypeConfig("screen_on_moderate", 9, 191 // defaultMin 192 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), 193 Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1), 194 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)), 195 // defaultMax 196 List.of(Pair.create(WORK_TYPE_BG, 4), 197 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), 198 Pair.create(WORK_TYPE_BGUSER, 1)) 199 ), 200 new WorkTypeConfig("screen_on_low", 6, 201 // defaultMin 202 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), 203 Pair.create(WORK_TYPE_EJ, 1)), 204 // defaultMax 205 List.of(Pair.create(WORK_TYPE_BG, 2), 206 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), 207 Pair.create(WORK_TYPE_BGUSER, 1)) 208 ), 209 new WorkTypeConfig("screen_on_critical", 6, 210 // defaultMin 211 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), 212 Pair.create(WORK_TYPE_EJ, 1)), 213 // defaultMax 214 List.of(Pair.create(WORK_TYPE_BG, 1), 215 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), 216 Pair.create(WORK_TYPE_BGUSER, 1)) 217 ) 218 ); 219 private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF = 220 new WorkConfigLimitsPerMemoryTrimLevel( 221 new WorkTypeConfig("screen_off_normal", 16, 222 // defaultMin 223 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 2), 224 Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2), 225 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)), 226 // defaultMax 227 List.of(Pair.create(WORK_TYPE_BG, 10), 228 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 2), 229 Pair.create(WORK_TYPE_BGUSER, 3)) 230 ), 231 new WorkTypeConfig("screen_off_moderate", 14, 232 // defaultMin 233 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 2), 234 Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2), 235 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)), 236 // defaultMax 237 List.of(Pair.create(WORK_TYPE_BG, 7), 238 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), 239 Pair.create(WORK_TYPE_BGUSER, 1)) 240 ), 241 new WorkTypeConfig("screen_off_low", 9, 242 // defaultMin 243 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), 244 Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)), 245 // defaultMax 246 List.of(Pair.create(WORK_TYPE_BG, 3), 247 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), 248 Pair.create(WORK_TYPE_BGUSER, 1)) 249 ), 250 new WorkTypeConfig("screen_off_critical", 6, 251 // defaultMin 252 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), 253 Pair.create(WORK_TYPE_EJ, 1)), 254 // defaultMax 255 List.of(Pair.create(WORK_TYPE_BG, 1), 256 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), 257 Pair.create(WORK_TYPE_BGUSER, 1)) 258 ) 259 ); 260 261 /** 262 * This array essentially stores the state of mActiveServices array. 263 * The ith index stores the job present on the ith JobServiceContext. 264 * We manipulate this array until we arrive at what jobs should be running on 265 * what JobServiceContext. 266 */ 267 JobStatus[] mRecycledAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT]; 268 269 boolean[] mRecycledSlotChanged = new boolean[MAX_JOB_CONTEXTS_COUNT]; 270 271 int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT]; 272 273 int[] mRecycledWorkTypeForContext = new int[MAX_JOB_CONTEXTS_COUNT]; 274 275 String[] mRecycledPreemptReasonForContext = new String[MAX_JOB_CONTEXTS_COUNT]; 276 277 int[] mRecycledPreemptReasonCodeForContext = new int[MAX_JOB_CONTEXTS_COUNT]; 278 279 String[] mRecycledShouldStopJobReason = new String[MAX_JOB_CONTEXTS_COUNT]; 280 281 private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>(); 282 283 private final WorkCountTracker mWorkCountTracker = new WorkCountTracker(); 284 285 private final Pools.Pool<PackageStats> mPkgStatsPool = 286 new Pools.SimplePool<>(MAX_JOB_CONTEXTS_COUNT); 287 288 private final SparseArrayMap<String, PackageStats> mActivePkgStats = new SparseArrayMap<>(); 289 290 private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal; 291 292 /** Wait for this long after screen off before adjusting the job concurrency. */ 293 private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS; 294 295 /** 296 * The maximum number of expedited jobs a single userId-package can have running simultaneously. 297 * TOP apps are not limited. 298 */ 299 private long mPkgConcurrencyLimitEj = DEFAULT_PKG_CONCURRENCY_LIMIT_EJ; 300 301 /** 302 * The maximum number of regular jobs a single userId-package can have running simultaneously. 303 * TOP apps are not limited. 304 */ 305 private long mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR; 306 307 /** Current memory trim level. */ 308 private int mLastMemoryTrimLevel; 309 310 /** Used to throttle heavy API calls. */ 311 private long mNextSystemStateRefreshTime; 312 private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000; 313 314 private final Consumer<PackageStats> mPackageStatsStagingCountClearer = 315 PackageStats::resetStagedCount; 316 317 private final StatLogger mStatLogger = new StatLogger(new String[]{ 318 "assignJobsToContexts", 319 "refreshSystemState", 320 }); 321 @VisibleForTesting 322 GracePeriodObserver mGracePeriodObserver; 323 @VisibleForTesting 324 boolean mShouldRestrictBgUser; 325 326 interface Stats { 327 int ASSIGN_JOBS_TO_CONTEXTS = 0; 328 int REFRESH_SYSTEM_STATE = 1; 329 330 int COUNT = REFRESH_SYSTEM_STATE + 1; 331 } 332 JobConcurrencyManager(JobSchedulerService service)333 JobConcurrencyManager(JobSchedulerService service) { 334 mService = service; 335 mLock = mService.mLock; 336 mContext = service.getTestableContext(); 337 338 mHandler = JobSchedulerBackgroundThread.getHandler(); 339 340 mGracePeriodObserver = new GracePeriodObserver(mContext); 341 mShouldRestrictBgUser = mContext.getResources().getBoolean( 342 R.bool.config_jobSchedulerRestrictBackgroundUser); 343 } 344 onSystemReady()345 public void onSystemReady() { 346 mPowerManager = mContext.getSystemService(PowerManager.class); 347 348 final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); 349 filter.addAction(Intent.ACTION_SCREEN_OFF); 350 filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); 351 filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); 352 mContext.registerReceiver(mReceiver, filter); 353 try { 354 ActivityManager.getService().registerUserSwitchObserver(mGracePeriodObserver, TAG); 355 } catch (RemoteException e) { 356 } 357 358 onInteractiveStateChanged(mPowerManager.isInteractive()); 359 } 360 361 @GuardedBy("mLock") onAppRemovedLocked(String pkgName, int uid)362 void onAppRemovedLocked(String pkgName, int uid) { 363 final PackageStats packageStats = mActivePkgStats.get(UserHandle.getUserId(uid), pkgName); 364 if (packageStats != null) { 365 if (packageStats.numRunningEj > 0 || packageStats.numRunningRegular > 0) { 366 // Don't delete the object just yet. We'll remove it in onJobCompleted() when the 367 // jobs officially stop running. 368 Slog.w(TAG, 369 pkgName + "(" + uid + ") marked as removed before jobs stopped running"); 370 } else { 371 mActivePkgStats.delete(UserHandle.getUserId(uid), pkgName); 372 } 373 } 374 } 375 onUserRemoved(int userId)376 void onUserRemoved(int userId) { 377 mGracePeriodObserver.onUserRemoved(userId); 378 } 379 380 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 381 @Override 382 public void onReceive(Context context, Intent intent) { 383 switch (intent.getAction()) { 384 case Intent.ACTION_SCREEN_ON: 385 onInteractiveStateChanged(true); 386 break; 387 case Intent.ACTION_SCREEN_OFF: 388 onInteractiveStateChanged(false); 389 break; 390 case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: 391 if (mPowerManager != null && mPowerManager.isDeviceIdleMode()) { 392 synchronized (mLock) { 393 stopLongRunningJobsLocked("deep doze"); 394 } 395 } 396 break; 397 case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED: 398 if (mPowerManager != null && mPowerManager.isPowerSaveMode()) { 399 synchronized (mLock) { 400 stopLongRunningJobsLocked("battery saver"); 401 } 402 } 403 break; 404 } 405 } 406 }; 407 408 /** 409 * Called when the screen turns on / off. 410 */ onInteractiveStateChanged(boolean interactive)411 private void onInteractiveStateChanged(boolean interactive) { 412 synchronized (mLock) { 413 if (mCurrentInteractiveState == interactive) { 414 return; 415 } 416 mCurrentInteractiveState = interactive; 417 if (DEBUG) { 418 Slog.d(TAG, "Interactive: " + interactive); 419 } 420 421 final long nowRealtime = sElapsedRealtimeClock.millis(); 422 if (interactive) { 423 mLastScreenOnRealtime = nowRealtime; 424 mEffectiveInteractiveState = true; 425 426 mHandler.removeCallbacks(mRampUpForScreenOff); 427 } else { 428 mLastScreenOffRealtime = nowRealtime; 429 430 // Set mEffectiveInteractiveState to false after the delay, when we may increase 431 // the concurrency. 432 // We don't need a wakeup alarm here. When there's a pending job, there should 433 // also be jobs running too, meaning the device should be awake. 434 435 // Note: we can't directly do postDelayed(this::rampUpForScreenOn), because 436 // we need the exact same instance for removeCallbacks(). 437 mHandler.postDelayed(mRampUpForScreenOff, mScreenOffAdjustmentDelayMs); 438 } 439 } 440 } 441 442 private final Runnable mRampUpForScreenOff = this::rampUpForScreenOff; 443 444 /** 445 * Called in {@link #mScreenOffAdjustmentDelayMs} after 446 * the screen turns off, in order to increase concurrency. 447 */ rampUpForScreenOff()448 private void rampUpForScreenOff() { 449 synchronized (mLock) { 450 // Make sure the screen has really been off for the configured duration. 451 // (There could be a race.) 452 if (!mEffectiveInteractiveState) { 453 return; 454 } 455 if (mLastScreenOnRealtime > mLastScreenOffRealtime) { 456 return; 457 } 458 final long now = sElapsedRealtimeClock.millis(); 459 if ((mLastScreenOffRealtime + mScreenOffAdjustmentDelayMs) > now) { 460 return; 461 } 462 463 mEffectiveInteractiveState = false; 464 465 if (DEBUG) { 466 Slog.d(TAG, "Ramping up concurrency"); 467 } 468 469 mService.maybeRunPendingJobsLocked(); 470 } 471 } 472 473 @GuardedBy("mLock") isJobRunningLocked(JobStatus job)474 boolean isJobRunningLocked(JobStatus job) { 475 return mRunningJobs.contains(job); 476 } 477 478 /** Return {@code true} if the state was updated. */ 479 @GuardedBy("mLock") refreshSystemStateLocked()480 private boolean refreshSystemStateLocked() { 481 final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis(); 482 483 // Only refresh the information every so often. 484 if (nowUptime < mNextSystemStateRefreshTime) { 485 return false; 486 } 487 488 final long start = mStatLogger.getTime(); 489 mNextSystemStateRefreshTime = nowUptime + SYSTEM_STATE_REFRESH_MIN_INTERVAL; 490 491 mLastMemoryTrimLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL; 492 try { 493 mLastMemoryTrimLevel = ActivityManager.getService().getMemoryTrimLevel(); 494 } catch (RemoteException e) { 495 } 496 497 mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start); 498 return true; 499 } 500 501 @GuardedBy("mLock") updateCounterConfigLocked()502 private void updateCounterConfigLocked() { 503 if (!refreshSystemStateLocked()) { 504 return; 505 } 506 507 final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState 508 ? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF; 509 510 switch (mLastMemoryTrimLevel) { 511 case ProcessStats.ADJ_MEM_FACTOR_MODERATE: 512 mWorkTypeConfig = workConfigs.moderate; 513 break; 514 case ProcessStats.ADJ_MEM_FACTOR_LOW: 515 mWorkTypeConfig = workConfigs.low; 516 break; 517 case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: 518 mWorkTypeConfig = workConfigs.critical; 519 break; 520 default: 521 mWorkTypeConfig = workConfigs.normal; 522 break; 523 } 524 525 mWorkCountTracker.setConfig(mWorkTypeConfig); 526 } 527 528 /** 529 * Takes jobs from pending queue and runs them on available contexts. 530 * If no contexts are available, preempts lower priority jobs to 531 * run higher priority ones. 532 * Lock on mJobs before calling this function. 533 */ 534 @GuardedBy("mLock") assignJobsToContextsLocked()535 void assignJobsToContextsLocked() { 536 final long start = mStatLogger.getTime(); 537 538 assignJobsToContextsInternalLocked(); 539 540 mStatLogger.logDurationStat(Stats.ASSIGN_JOBS_TO_CONTEXTS, start); 541 } 542 543 @GuardedBy("mLock") assignJobsToContextsInternalLocked()544 private void assignJobsToContextsInternalLocked() { 545 if (DEBUG) { 546 Slog.d(TAG, printPendingQueueLocked()); 547 } 548 549 final List<JobStatus> pendingJobs = mService.mPendingJobs; 550 final List<JobServiceContext> activeServices = mService.mActiveServices; 551 552 // To avoid GC churn, we recycle the arrays. 553 JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap; 554 boolean[] slotChanged = mRecycledSlotChanged; 555 int[] preferredUidForContext = mRecycledPreferredUidForContext; 556 int[] workTypeForContext = mRecycledWorkTypeForContext; 557 String[] preemptReasonForContext = mRecycledPreemptReasonForContext; 558 int[] preemptReasonCodeForContext = mRecycledPreemptReasonCodeForContext; 559 String[] shouldStopJobReason = mRecycledShouldStopJobReason; 560 561 updateCounterConfigLocked(); 562 // Reset everything since we'll re-evaluate the current state. 563 mWorkCountTracker.resetCounts(); 564 565 // Update the priorities of jobs that aren't running, and also count the pending work types. 566 // Do this before the following loop to hopefully reduce the cost of 567 // shouldStopRunningJobLocked(). 568 updateNonRunningPrioritiesLocked(pendingJobs, true); 569 570 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { 571 final JobServiceContext js = activeServices.get(i); 572 final JobStatus status = js.getRunningJobLocked(); 573 574 if ((contextIdToJobMap[i] = status) != null) { 575 mWorkCountTracker.incrementRunningJobCount(js.getRunningJobWorkType()); 576 workTypeForContext[i] = js.getRunningJobWorkType(); 577 } 578 579 slotChanged[i] = false; 580 preferredUidForContext[i] = js.getPreferredUid(); 581 preemptReasonForContext[i] = null; 582 preemptReasonCodeForContext[i] = JobParameters.STOP_REASON_UNDEFINED; 583 shouldStopJobReason[i] = shouldStopRunningJobLocked(js); 584 } 585 if (DEBUG) { 586 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial")); 587 } 588 589 mWorkCountTracker.onCountDone(); 590 591 for (int i = 0; i < pendingJobs.size(); i++) { 592 final JobStatus nextPending = pendingJobs.get(i); 593 594 if (mRunningJobs.contains(nextPending)) { 595 continue; 596 } 597 598 // Find an available slot for nextPending. The context should be available OR 599 // it should have lowest priority among all running jobs 600 // (sharing the same Uid as nextPending) 601 int minPriorityForPreemption = Integer.MAX_VALUE; 602 int selectedContextId = -1; 603 int allWorkTypes = getJobWorkTypes(nextPending); 604 int workType = mWorkCountTracker.canJobStart(allWorkTypes); 605 boolean startingJob = false; 606 int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; 607 String preemptReason = null; 608 final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending); 609 // TODO(141645789): rewrite this to look at empty contexts first so we don't 610 // unnecessarily preempt 611 for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) { 612 JobStatus job = contextIdToJobMap[j]; 613 int preferredUid = preferredUidForContext[j]; 614 if (job == null) { 615 final boolean preferredUidOkay = (preferredUid == nextPending.getUid()) 616 || (preferredUid == JobServiceContext.NO_PREFERRED_UID); 617 618 if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) { 619 // This slot is free, and we haven't yet hit the limit on 620 // concurrent jobs... we can just throw the job in to here. 621 selectedContextId = j; 622 startingJob = true; 623 break; 624 } 625 // No job on this context, but nextPending can't run here because 626 // the context has a preferred Uid or we have reached the limit on 627 // concurrent jobs. 628 continue; 629 } 630 if (job.getUid() != nextPending.getUid()) { 631 // Maybe stop the job if it has had its day in the sun. Don't let a different 632 // app preempt jobs started for TOP apps though. 633 final String reason = shouldStopJobReason[j]; 634 if (job.lastEvaluatedPriority < JobInfo.PRIORITY_TOP_APP 635 && reason != null && mWorkCountTracker.canJobStart(allWorkTypes, 636 activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) { 637 // Right now, the way the code is set up, we don't need to explicitly 638 // assign the new job to this context since we'll reassign when the 639 // preempted job finally stops. 640 preemptReason = reason; 641 preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE; 642 } 643 continue; 644 } 645 646 final int jobPriority = mService.evaluateJobPriorityLocked(job); 647 if (jobPriority >= nextPending.lastEvaluatedPriority) { 648 continue; 649 } 650 651 if (minPriorityForPreemption > jobPriority) { 652 // Step down the preemption threshold - wind up replacing 653 // the lowest-priority running job 654 minPriorityForPreemption = jobPriority; 655 selectedContextId = j; 656 preemptReason = "higher priority job found"; 657 preemptReasonCode = JobParameters.STOP_REASON_PREEMPT; 658 // In this case, we're just going to preempt a low priority job, we're not 659 // actually starting a job, so don't set startingJob. 660 } 661 } 662 final PackageStats packageStats = getPkgStatsLocked( 663 nextPending.getSourceUserId(), nextPending.getSourcePackageName()); 664 if (selectedContextId != -1) { 665 contextIdToJobMap[selectedContextId] = nextPending; 666 slotChanged[selectedContextId] = true; 667 preemptReasonCodeForContext[selectedContextId] = preemptReasonCode; 668 preemptReasonForContext[selectedContextId] = preemptReason; 669 packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob()); 670 } 671 if (startingJob) { 672 // Increase the counters when we're going to start a job. 673 workTypeForContext[selectedContextId] = workType; 674 mWorkCountTracker.stageJob(workType, allWorkTypes); 675 mActivePkgStats.add( 676 nextPending.getSourceUserId(), nextPending.getSourcePackageName(), 677 packageStats); 678 } 679 } 680 if (DEBUG) { 681 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); 682 683 Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString()); 684 } 685 686 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { 687 boolean preservePreferredUid = false; 688 if (slotChanged[i]) { 689 JobStatus js = activeServices.get(i).getRunningJobLocked(); 690 if (js != null) { 691 if (DEBUG) { 692 Slog.d(TAG, "preempting job: " 693 + activeServices.get(i).getRunningJobLocked()); 694 } 695 // preferredUid will be set to uid of currently running job. 696 activeServices.get(i).cancelExecutingJobLocked( 697 preemptReasonCodeForContext[i], 698 JobParameters.INTERNAL_STOP_REASON_PREEMPT, preemptReasonForContext[i]); 699 // Only preserve the UID if we're preempting for the same UID. If we're stopping 700 // the job because something is pending (eg. EJs), then we shouldn't preserve 701 // the UID. 702 preservePreferredUid = 703 preemptReasonCodeForContext[i] == JobParameters.STOP_REASON_PREEMPT; 704 } else { 705 final JobStatus pendingJob = contextIdToJobMap[i]; 706 if (DEBUG) { 707 Slog.d(TAG, "About to run job on context " 708 + i + ", job: " + pendingJob); 709 } 710 startJobLocked(activeServices.get(i), pendingJob, workTypeForContext[i]); 711 } 712 } 713 if (!preservePreferredUid) { 714 activeServices.get(i).clearPreferredUid(); 715 } 716 } 717 mWorkCountTracker.resetStagingCount(); 718 mActivePkgStats.forEach(mPackageStatsStagingCountClearer); 719 noteConcurrency(); 720 } 721 722 @GuardedBy("mLock") stopLongRunningJobsLocked(@onNull String debugReason)723 private void stopLongRunningJobsLocked(@NonNull String debugReason) { 724 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; ++i) { 725 final JobServiceContext jsc = mService.mActiveServices.get(i); 726 final JobStatus jobStatus = jsc.getRunningJobLocked(); 727 728 if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()) { 729 jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE, 730 JobParameters.INTERNAL_STOP_REASON_TIMEOUT, debugReason); 731 } 732 } 733 } 734 noteConcurrency()735 private void noteConcurrency() { 736 mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(), 737 // TODO: log per type instead of only TOP 738 mWorkCountTracker.getRunningJobCount(WORK_TYPE_TOP)); 739 } 740 741 @GuardedBy("mLock") updateNonRunningPrioritiesLocked(@onNull final List<JobStatus> pendingJobs, boolean updateCounter)742 private void updateNonRunningPrioritiesLocked(@NonNull final List<JobStatus> pendingJobs, 743 boolean updateCounter) { 744 for (int i = 0; i < pendingJobs.size(); i++) { 745 final JobStatus pending = pendingJobs.get(i); 746 747 // If job is already running, go to next job. 748 if (mRunningJobs.contains(pending)) { 749 continue; 750 } 751 752 pending.lastEvaluatedPriority = mService.evaluateJobPriorityLocked(pending); 753 754 if (updateCounter) { 755 mWorkCountTracker.incrementPendingJobCount(getJobWorkTypes(pending)); 756 } 757 } 758 } 759 760 @GuardedBy("mLock") 761 @NonNull getPkgStatsLocked(int userId, @NonNull String packageName)762 private PackageStats getPkgStatsLocked(int userId, @NonNull String packageName) { 763 PackageStats packageStats = mActivePkgStats.get(userId, packageName); 764 if (packageStats == null) { 765 packageStats = mPkgStatsPool.acquire(); 766 if (packageStats == null) { 767 packageStats = new PackageStats(); 768 } 769 packageStats.setPackage(userId, packageName); 770 } 771 return packageStats; 772 } 773 774 @GuardedBy("mLock") isPkgConcurrencyLimitedLocked(@onNull JobStatus jobStatus)775 private boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) { 776 if (jobStatus.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { 777 // Don't restrict top apps' concurrency. The work type limits will make sure 778 // background jobs have slots to run if the system has resources. 779 return false; 780 } 781 // Use < instead of <= as that gives us a little wiggle room in case a new job comes 782 // along very shortly. 783 if (mService.mPendingJobs.size() + mRunningJobs.size() < mWorkTypeConfig.getMaxTotal()) { 784 // Don't artificially limit a single package if we don't even have enough jobs to use 785 // the maximum number of slots. We'll preempt the job later if we need the slot. 786 return false; 787 } 788 final PackageStats packageStats = 789 mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 790 if (packageStats == null) { 791 // No currently running jobs. 792 return false; 793 } 794 if (jobStatus.shouldTreatAsExpeditedJob()) { 795 return packageStats.numRunningEj + packageStats.numStagedEj < mPkgConcurrencyLimitEj; 796 } else { 797 return packageStats.numRunningRegular + packageStats.numStagedRegular 798 < mPkgConcurrencyLimitRegular; 799 } 800 } 801 802 @GuardedBy("mLock") startJobLocked(@onNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType)803 private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus, 804 @WorkType final int workType) { 805 final List<StateController> controllers = mService.mControllers; 806 final int numControllers = controllers.size(); 807 for (int ic = 0; ic < numControllers; ic++) { 808 controllers.get(ic).prepareForExecutionLocked(jobStatus); 809 } 810 final PackageStats packageStats = 811 getPkgStatsLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 812 packageStats.adjustStagedCount(false, jobStatus.shouldTreatAsExpeditedJob()); 813 if (!worker.executeRunnableJob(jobStatus, workType)) { 814 Slog.e(TAG, "Error executing " + jobStatus); 815 mWorkCountTracker.onStagedJobFailed(workType); 816 for (int ic = 0; ic < numControllers; ic++) { 817 controllers.get(ic).unprepareFromExecutionLocked(jobStatus); 818 } 819 } else { 820 mRunningJobs.add(jobStatus); 821 mWorkCountTracker.onJobStarted(workType); 822 packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob()); 823 mActivePkgStats.add( 824 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), packageStats); 825 } 826 final List<JobStatus> pendingJobs = mService.mPendingJobs; 827 if (pendingJobs.remove(jobStatus)) { 828 mService.mJobPackageTracker.noteNonpending(jobStatus); 829 } 830 } 831 832 @GuardedBy("mLock") onJobCompletedLocked(@onNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType)833 void onJobCompletedLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus, 834 @WorkType final int workType) { 835 mWorkCountTracker.onJobFinished(workType); 836 mRunningJobs.remove(jobStatus); 837 final PackageStats packageStats = 838 mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 839 if (packageStats == null) { 840 Slog.wtf(TAG, "Running job didn't have an active PackageStats object"); 841 } else { 842 packageStats.adjustRunningCount(false, jobStatus.startedAsExpeditedJob); 843 if (packageStats.numRunningEj <= 0 && packageStats.numRunningRegular <= 0) { 844 mActivePkgStats.delete(packageStats.userId, packageStats.packageName); 845 mPkgStatsPool.release(packageStats); 846 } 847 } 848 849 final List<JobStatus> pendingJobs = mService.mPendingJobs; 850 if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) { 851 updateCounterConfigLocked(); 852 // Preemption case needs special care. 853 updateNonRunningPrioritiesLocked(pendingJobs, false); 854 855 JobStatus highestPriorityJob = null; 856 int highPriWorkType = workType; 857 int highPriAllWorkTypes = workType; 858 JobStatus backupJob = null; 859 int backupWorkType = WORK_TYPE_NONE; 860 int backupAllWorkTypes = WORK_TYPE_NONE; 861 for (int i = 0; i < pendingJobs.size(); i++) { 862 final JobStatus nextPending = pendingJobs.get(i); 863 864 if (mRunningJobs.contains(nextPending)) { 865 continue; 866 } 867 868 if (worker.getPreferredUid() != nextPending.getUid()) { 869 if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) { 870 int allWorkTypes = getJobWorkTypes(nextPending); 871 int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); 872 if (workAsType != WORK_TYPE_NONE) { 873 backupJob = nextPending; 874 backupWorkType = workAsType; 875 backupAllWorkTypes = allWorkTypes; 876 } 877 } 878 continue; 879 } 880 881 // Only bypass the concurrent limit if we had preempted the job due to a higher 882 // priority job. 883 if (nextPending.lastEvaluatedPriority <= jobStatus.lastEvaluatedPriority 884 && isPkgConcurrencyLimitedLocked(nextPending)) { 885 continue; 886 } 887 888 if (highestPriorityJob == null 889 || highestPriorityJob.lastEvaluatedPriority 890 < nextPending.lastEvaluatedPriority) { 891 highestPriorityJob = nextPending; 892 } else { 893 continue; 894 } 895 896 // In this path, we pre-empted an existing job. We don't fully care about the 897 // reserved slots. We should just run the highest priority job we can find, 898 // though it would be ideal to use an available WorkType slot instead of 899 // overloading slots. 900 highPriAllWorkTypes = getJobWorkTypes(nextPending); 901 final int workAsType = mWorkCountTracker.canJobStart(highPriAllWorkTypes); 902 if (workAsType == WORK_TYPE_NONE) { 903 // Just use the preempted job's work type since this new one is technically 904 // replacing it anyway. 905 highPriWorkType = workType; 906 } else { 907 highPriWorkType = workAsType; 908 } 909 } 910 if (highestPriorityJob != null) { 911 if (DEBUG) { 912 Slog.d(TAG, "Running job " + jobStatus + " as preemption"); 913 } 914 mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes); 915 startJobLocked(worker, highestPriorityJob, highPriWorkType); 916 } else { 917 if (DEBUG) { 918 Slog.d(TAG, "Couldn't find preemption job for uid " + worker.getPreferredUid()); 919 } 920 worker.clearPreferredUid(); 921 if (backupJob != null) { 922 if (DEBUG) { 923 Slog.d(TAG, "Running job " + jobStatus + " instead"); 924 } 925 mWorkCountTracker.stageJob(backupWorkType, backupAllWorkTypes); 926 startJobLocked(worker, backupJob, backupWorkType); 927 } 928 } 929 } else if (pendingJobs.size() > 0) { 930 updateCounterConfigLocked(); 931 updateNonRunningPrioritiesLocked(pendingJobs, false); 932 933 // This slot is now free and we have pending jobs. Start the highest priority job we 934 // find. 935 JobStatus highestPriorityJob = null; 936 int highPriWorkType = workType; 937 int highPriAllWorkTypes = workType; 938 for (int i = 0; i < pendingJobs.size(); i++) { 939 final JobStatus nextPending = pendingJobs.get(i); 940 941 if (mRunningJobs.contains(nextPending)) { 942 continue; 943 } 944 945 if (isPkgConcurrencyLimitedLocked(nextPending)) { 946 continue; 947 } 948 949 final int allWorkTypes = getJobWorkTypes(nextPending); 950 final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); 951 if (workAsType == WORK_TYPE_NONE) { 952 continue; 953 } 954 if (highestPriorityJob == null 955 || highestPriorityJob.lastEvaluatedPriority 956 < nextPending.lastEvaluatedPriority) { 957 highestPriorityJob = nextPending; 958 highPriWorkType = workAsType; 959 highPriAllWorkTypes = allWorkTypes; 960 } 961 } 962 963 if (highestPriorityJob != null) { 964 // This slot is free, and we haven't yet hit the limit on 965 // concurrent jobs... we can just throw the job in to here. 966 if (DEBUG) { 967 Slog.d(TAG, "About to run job: " + jobStatus); 968 } 969 mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes); 970 startJobLocked(worker, highestPriorityJob, highPriWorkType); 971 } 972 } 973 974 noteConcurrency(); 975 } 976 977 /** 978 * Returns {@code null} if the job can continue running and a non-null String if the job should 979 * be stopped. The non-null String details the reason for stopping the job. A job will generally 980 * be stopped if there similar job types waiting to be run and stopping this job would allow 981 * another job to run, or if system state suggests the job should stop. 982 */ 983 @Nullable 984 @GuardedBy("mLock") shouldStopRunningJobLocked(@onNull JobServiceContext context)985 String shouldStopRunningJobLocked(@NonNull JobServiceContext context) { 986 final JobStatus js = context.getRunningJobLocked(); 987 if (js == null) { 988 // This can happen when we try to assign newly found pending jobs to contexts. 989 return null; 990 } 991 992 if (context.isWithinExecutionGuaranteeTime()) { 993 return null; 994 } 995 996 // We're over the minimum guaranteed runtime. Stop the job if we're over config limits, 997 // there are pending jobs that could replace this one, or the device state is not conducive 998 // to long runs. 999 1000 if (mPowerManager.isPowerSaveMode()) { 1001 return "battery saver"; 1002 } 1003 if (mPowerManager.isDeviceIdleMode()) { 1004 return "deep doze"; 1005 } 1006 1007 // Update config in case memory usage has changed significantly. 1008 updateCounterConfigLocked(); 1009 1010 @WorkType final int workType = context.getRunningJobWorkType(); 1011 1012 if (mRunningJobs.size() > mWorkTypeConfig.getMaxTotal() 1013 || mWorkCountTracker.isOverTypeLimit(workType)) { 1014 return "too many jobs running"; 1015 } 1016 1017 final List<JobStatus> pendingJobs = mService.mPendingJobs; 1018 final int numPending = pendingJobs.size(); 1019 if (numPending == 0) { 1020 // All quiet. We can let this job run to completion. 1021 return null; 1022 } 1023 1024 // Only expedited jobs can replace expedited jobs. 1025 if (js.shouldTreatAsExpeditedJob() || js.startedAsExpeditedJob) { 1026 // Keep fg/bg user distinction. 1027 if (workType == WORK_TYPE_BGUSER_IMPORTANT || workType == WORK_TYPE_BGUSER) { 1028 // Let any important bg user job replace a bg user expedited job. 1029 if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_BGUSER_IMPORTANT) > 0) { 1030 return "blocking " + workTypeToString(WORK_TYPE_BGUSER_IMPORTANT) + " queue"; 1031 } 1032 // Let a fg user EJ preempt a bg user EJ (if able), but not the other way around. 1033 if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0 1034 && mWorkCountTracker.canJobStart(WORK_TYPE_EJ, workType) 1035 != WORK_TYPE_NONE) { 1036 return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue"; 1037 } 1038 } else if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) { 1039 return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue"; 1040 } 1041 // No other pending EJs. Return null so we don't let regular jobs preempt an EJ. 1042 return null; 1043 } 1044 1045 // Easy check. If there are pending jobs of the same work type, then we know that 1046 // something will replace this. 1047 if (mWorkCountTracker.getPendingJobCount(workType) > 0) { 1048 return "blocking " + workTypeToString(workType) + " queue"; 1049 } 1050 1051 // Harder check. We need to see if a different work type can replace this job. 1052 int remainingWorkTypes = ALL_WORK_TYPES; 1053 for (int i = 0; i < numPending; ++i) { 1054 final JobStatus pending = pendingJobs.get(i); 1055 final int workTypes = getJobWorkTypes(pending); 1056 if ((workTypes & remainingWorkTypes) > 0 1057 && mWorkCountTracker.canJobStart(workTypes, workType) != WORK_TYPE_NONE) { 1058 return "blocking other pending jobs"; 1059 } 1060 1061 remainingWorkTypes = remainingWorkTypes & ~workTypes; 1062 if (remainingWorkTypes == 0) { 1063 break; 1064 } 1065 } 1066 1067 return null; 1068 } 1069 1070 @GuardedBy("mLock") printPendingQueueLocked()1071 private String printPendingQueueLocked() { 1072 StringBuilder s = new StringBuilder("Pending queue: "); 1073 Iterator<JobStatus> it = mService.mPendingJobs.iterator(); 1074 while (it.hasNext()) { 1075 JobStatus js = it.next(); 1076 s.append("(") 1077 .append(js.getJob().getId()) 1078 .append(", ") 1079 .append(js.getUid()) 1080 .append(") "); 1081 } 1082 return s.toString(); 1083 } 1084 printContextIdToJobMap(JobStatus[] map, String initial)1085 private static String printContextIdToJobMap(JobStatus[] map, String initial) { 1086 StringBuilder s = new StringBuilder(initial + ": "); 1087 for (int i=0; i<map.length; i++) { 1088 s.append("(") 1089 .append(map[i] == null? -1: map[i].getJobId()) 1090 .append(map[i] == null? -1: map[i].getUid()) 1091 .append(")" ); 1092 } 1093 return s.toString(); 1094 } 1095 1096 @GuardedBy("mLock") updateConfigLocked()1097 void updateConfigLocked() { 1098 DeviceConfig.Properties properties = 1099 DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER); 1100 1101 mScreenOffAdjustmentDelayMs = properties.getLong( 1102 KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS); 1103 1104 CONFIG_LIMITS_SCREEN_ON.normal.update(properties); 1105 CONFIG_LIMITS_SCREEN_ON.moderate.update(properties); 1106 CONFIG_LIMITS_SCREEN_ON.low.update(properties); 1107 CONFIG_LIMITS_SCREEN_ON.critical.update(properties); 1108 1109 CONFIG_LIMITS_SCREEN_OFF.normal.update(properties); 1110 CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties); 1111 CONFIG_LIMITS_SCREEN_OFF.low.update(properties); 1112 CONFIG_LIMITS_SCREEN_OFF.critical.update(properties); 1113 1114 // Package concurrency limits must in the range [1, MAX_JOB_CONTEXTS_COUNT]. 1115 mPkgConcurrencyLimitEj = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT, 1116 properties.getInt(KEY_PKG_CONCURRENCY_LIMIT_EJ, DEFAULT_PKG_CONCURRENCY_LIMIT_EJ))); 1117 mPkgConcurrencyLimitRegular = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT, 1118 properties.getInt( 1119 KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR))); 1120 } 1121 1122 @GuardedBy("mLock") dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime)1123 public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) { 1124 pw.println("Concurrency:"); 1125 1126 pw.increaseIndent(); 1127 try { 1128 pw.println("Configuration:"); 1129 pw.increaseIndent(); 1130 pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println(); 1131 pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println(); 1132 pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println(); 1133 pw.println(); 1134 CONFIG_LIMITS_SCREEN_ON.normal.dump(pw); 1135 pw.println(); 1136 CONFIG_LIMITS_SCREEN_ON.moderate.dump(pw); 1137 pw.println(); 1138 CONFIG_LIMITS_SCREEN_ON.low.dump(pw); 1139 pw.println(); 1140 CONFIG_LIMITS_SCREEN_ON.critical.dump(pw); 1141 pw.println(); 1142 CONFIG_LIMITS_SCREEN_OFF.normal.dump(pw); 1143 pw.println(); 1144 CONFIG_LIMITS_SCREEN_OFF.moderate.dump(pw); 1145 pw.println(); 1146 CONFIG_LIMITS_SCREEN_OFF.low.dump(pw); 1147 pw.println(); 1148 CONFIG_LIMITS_SCREEN_OFF.critical.dump(pw); 1149 pw.println(); 1150 pw.decreaseIndent(); 1151 1152 pw.print("Screen state: current "); 1153 pw.print(mCurrentInteractiveState ? "ON" : "OFF"); 1154 pw.print(" effective "); 1155 pw.print(mEffectiveInteractiveState ? "ON" : "OFF"); 1156 pw.println(); 1157 1158 pw.print("Last screen ON: "); 1159 TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOnRealtime, now); 1160 pw.println(); 1161 1162 pw.print("Last screen OFF: "); 1163 TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOffRealtime, now); 1164 pw.println(); 1165 1166 pw.println(); 1167 1168 pw.print("Current work counts: "); 1169 pw.println(mWorkCountTracker); 1170 1171 pw.println(); 1172 1173 pw.print("mLastMemoryTrimLevel: "); 1174 pw.println(mLastMemoryTrimLevel); 1175 pw.println(); 1176 1177 pw.println("Active Package stats:"); 1178 pw.increaseIndent(); 1179 mActivePkgStats.forEach(pkgStats -> pkgStats.dumpLocked(pw)); 1180 pw.decreaseIndent(); 1181 pw.println(); 1182 1183 pw.print("User Grace Period: "); 1184 pw.println(mGracePeriodObserver.mGracePeriodExpiration); 1185 pw.println(); 1186 1187 mStatLogger.dump(pw); 1188 } finally { 1189 pw.decreaseIndent(); 1190 } 1191 } 1192 dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime)1193 public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) { 1194 final long token = proto.start(tag); 1195 1196 proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE_STATE, mCurrentInteractiveState); 1197 proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE_STATE, 1198 mEffectiveInteractiveState); 1199 1200 proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS, 1201 nowRealtime - mLastScreenOnRealtime); 1202 proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS, 1203 nowRealtime - mLastScreenOffRealtime); 1204 1205 proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, mLastMemoryTrimLevel); 1206 1207 mStatLogger.dumpProto(proto, JobConcurrencyManagerProto.STATS); 1208 1209 proto.end(token); 1210 } 1211 1212 /** 1213 * Decides whether a job is from the current foreground user or the equivalent. 1214 */ 1215 @VisibleForTesting shouldRunAsFgUserJob(JobStatus job)1216 boolean shouldRunAsFgUserJob(JobStatus job) { 1217 if (!mShouldRestrictBgUser) return true; 1218 int userId = job.getSourceUserId(); 1219 UserManagerInternal um = LocalServices.getService(UserManagerInternal.class); 1220 UserInfo userInfo = um.getUserInfo(userId); 1221 1222 // If the user has a parent user (e.g. a work profile of another user), the user should be 1223 // treated equivalent as its parent user. 1224 if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID 1225 && userInfo.profileGroupId != userId) { 1226 userId = userInfo.profileGroupId; 1227 userInfo = um.getUserInfo(userId); 1228 } 1229 1230 int currentUser = LocalServices.getService(ActivityManagerInternal.class) 1231 .getCurrentUserId(); 1232 // A user is treated as foreground user if any of the followings is true: 1233 // 1. The user is current user 1234 // 2. The user is primary user 1235 // 3. The user's grace period has not expired 1236 return currentUser == userId || userInfo.isPrimary() 1237 || mGracePeriodObserver.isWithinGracePeriodForUser(userId); 1238 } 1239 getJobWorkTypes(@onNull JobStatus js)1240 int getJobWorkTypes(@NonNull JobStatus js) { 1241 int classification = 0; 1242 1243 if (shouldRunAsFgUserJob(js)) { 1244 if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { 1245 classification |= WORK_TYPE_TOP; 1246 } else if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_FOREGROUND_SERVICE) { 1247 classification |= WORK_TYPE_FGS; 1248 } else { 1249 classification |= WORK_TYPE_BG; 1250 } 1251 1252 if (js.shouldTreatAsExpeditedJob()) { 1253 classification |= WORK_TYPE_EJ; 1254 } 1255 } else { 1256 if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_FOREGROUND_SERVICE 1257 || js.shouldTreatAsExpeditedJob()) { 1258 classification |= WORK_TYPE_BGUSER_IMPORTANT; 1259 } 1260 // BGUSER_IMPORTANT jobs can also run as BGUSER jobs, so not an 'else' here. 1261 classification |= WORK_TYPE_BGUSER; 1262 } 1263 1264 return classification; 1265 } 1266 1267 @VisibleForTesting 1268 static class WorkTypeConfig { 1269 private static final String KEY_PREFIX_MAX_TOTAL = 1270 CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_"; 1271 private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_"; 1272 private static final String KEY_PREFIX_MAX_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "max_fgs_"; 1273 private static final String KEY_PREFIX_MAX_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "max_ej_"; 1274 private static final String KEY_PREFIX_MAX_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_"; 1275 private static final String KEY_PREFIX_MAX_BGUSER = 1276 CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_"; 1277 private static final String KEY_PREFIX_MAX_BGUSER_IMPORTANT = 1278 CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_important_"; 1279 private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_"; 1280 private static final String KEY_PREFIX_MIN_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "min_fgs_"; 1281 private static final String KEY_PREFIX_MIN_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "min_ej_"; 1282 private static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_"; 1283 private static final String KEY_PREFIX_MIN_BGUSER = 1284 CONFIG_KEY_PREFIX_CONCURRENCY + "min_bguser_"; 1285 private static final String KEY_PREFIX_MIN_BGUSER_IMPORTANT = 1286 CONFIG_KEY_PREFIX_CONCURRENCY + "min_bguser_important_"; 1287 private final String mConfigIdentifier; 1288 1289 private int mMaxTotal; 1290 private final SparseIntArray mMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES); 1291 private final SparseIntArray mMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES); 1292 private final int mDefaultMaxTotal; 1293 private final SparseIntArray mDefaultMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES); 1294 private final SparseIntArray mDefaultMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES); 1295 WorkTypeConfig(@onNull String configIdentifier, int defaultMaxTotal, List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax)1296 WorkTypeConfig(@NonNull String configIdentifier, int defaultMaxTotal, 1297 List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax) { 1298 mConfigIdentifier = configIdentifier; 1299 mDefaultMaxTotal = mMaxTotal = Math.min(defaultMaxTotal, MAX_JOB_CONTEXTS_COUNT); 1300 int numReserved = 0; 1301 for (int i = defaultMin.size() - 1; i >= 0; --i) { 1302 mDefaultMinReservedSlots.put(defaultMin.get(i).first, defaultMin.get(i).second); 1303 numReserved += defaultMin.get(i).second; 1304 } 1305 if (mDefaultMaxTotal < 0 || numReserved > mDefaultMaxTotal) { 1306 // We only create new configs on boot, so this should trigger during development 1307 // (before the code gets checked in), so this makes sure the hard-coded defaults 1308 // make sense. DeviceConfig values will be handled gracefully in update(). 1309 throw new IllegalArgumentException("Invalid default config: t=" + defaultMaxTotal 1310 + " min=" + defaultMin + " max=" + defaultMax); 1311 } 1312 for (int i = defaultMax.size() - 1; i >= 0; --i) { 1313 mDefaultMaxAllowedSlots.put(defaultMax.get(i).first, defaultMax.get(i).second); 1314 } 1315 update(new DeviceConfig.Properties.Builder( 1316 DeviceConfig.NAMESPACE_JOB_SCHEDULER).build()); 1317 } 1318 update(@onNull DeviceConfig.Properties properties)1319 void update(@NonNull DeviceConfig.Properties properties) { 1320 // Ensure total in the range [1, MAX_JOB_CONTEXTS_COUNT]. 1321 mMaxTotal = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT, 1322 properties.getInt(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mDefaultMaxTotal))); 1323 1324 mMaxAllowedSlots.clear(); 1325 // Ensure they're in the range [1, total]. 1326 final int maxTop = Math.max(1, Math.min(mMaxTotal, 1327 properties.getInt(KEY_PREFIX_MAX_TOP + mConfigIdentifier, 1328 mDefaultMaxAllowedSlots.get(WORK_TYPE_TOP, mMaxTotal)))); 1329 mMaxAllowedSlots.put(WORK_TYPE_TOP, maxTop); 1330 final int maxFgs = Math.max(1, Math.min(mMaxTotal, 1331 properties.getInt(KEY_PREFIX_MAX_FGS + mConfigIdentifier, 1332 mDefaultMaxAllowedSlots.get(WORK_TYPE_FGS, mMaxTotal)))); 1333 mMaxAllowedSlots.put(WORK_TYPE_FGS, maxFgs); 1334 final int maxEj = Math.max(1, Math.min(mMaxTotal, 1335 properties.getInt(KEY_PREFIX_MAX_EJ + mConfigIdentifier, 1336 mDefaultMaxAllowedSlots.get(WORK_TYPE_EJ, mMaxTotal)))); 1337 mMaxAllowedSlots.put(WORK_TYPE_EJ, maxEj); 1338 final int maxBg = Math.max(1, Math.min(mMaxTotal, 1339 properties.getInt(KEY_PREFIX_MAX_BG + mConfigIdentifier, 1340 mDefaultMaxAllowedSlots.get(WORK_TYPE_BG, mMaxTotal)))); 1341 mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg); 1342 final int maxBgUserImp = Math.max(1, Math.min(mMaxTotal, 1343 properties.getInt(KEY_PREFIX_MAX_BGUSER_IMPORTANT + mConfigIdentifier, 1344 mDefaultMaxAllowedSlots.get(WORK_TYPE_BGUSER_IMPORTANT, mMaxTotal)))); 1345 mMaxAllowedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, maxBgUserImp); 1346 final int maxBgUser = Math.max(1, Math.min(mMaxTotal, 1347 properties.getInt(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier, 1348 mDefaultMaxAllowedSlots.get(WORK_TYPE_BGUSER, mMaxTotal)))); 1349 mMaxAllowedSlots.put(WORK_TYPE_BGUSER, maxBgUser); 1350 1351 int remaining = mMaxTotal; 1352 mMinReservedSlots.clear(); 1353 // Ensure top is in the range [1, min(maxTop, total)] 1354 final int minTop = Math.max(1, Math.min(Math.min(maxTop, mMaxTotal), 1355 properties.getInt(KEY_PREFIX_MIN_TOP + mConfigIdentifier, 1356 mDefaultMinReservedSlots.get(WORK_TYPE_TOP)))); 1357 mMinReservedSlots.put(WORK_TYPE_TOP, minTop); 1358 remaining -= minTop; 1359 // Ensure fgs is in the range [0, min(maxFgs, remaining)] 1360 final int minFgs = Math.max(0, Math.min(Math.min(maxFgs, remaining), 1361 properties.getInt(KEY_PREFIX_MIN_FGS + mConfigIdentifier, 1362 mDefaultMinReservedSlots.get(WORK_TYPE_FGS)))); 1363 mMinReservedSlots.put(WORK_TYPE_FGS, minFgs); 1364 remaining -= minFgs; 1365 // Ensure ej is in the range [0, min(maxEj, remaining)] 1366 final int minEj = Math.max(0, Math.min(Math.min(maxEj, remaining), 1367 properties.getInt(KEY_PREFIX_MIN_EJ + mConfigIdentifier, 1368 mDefaultMinReservedSlots.get(WORK_TYPE_EJ)))); 1369 mMinReservedSlots.put(WORK_TYPE_EJ, minEj); 1370 remaining -= minEj; 1371 // Ensure bg is in the range [0, min(maxBg, remaining)] 1372 final int minBg = Math.max(0, Math.min(Math.min(maxBg, remaining), 1373 properties.getInt(KEY_PREFIX_MIN_BG + mConfigIdentifier, 1374 mDefaultMinReservedSlots.get(WORK_TYPE_BG)))); 1375 mMinReservedSlots.put(WORK_TYPE_BG, minBg); 1376 remaining -= minBg; 1377 // Ensure bg user imp is in the range [0, min(maxBgUserImp, remaining)] 1378 final int minBgUserImp = Math.max(0, Math.min(Math.min(maxBgUserImp, remaining), 1379 properties.getInt(KEY_PREFIX_MIN_BGUSER_IMPORTANT + mConfigIdentifier, 1380 mDefaultMinReservedSlots.get(WORK_TYPE_BGUSER_IMPORTANT, 0)))); 1381 mMinReservedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, minBgUserImp); 1382 // Ensure bg user is in the range [0, min(maxBgUser, remaining)] 1383 final int minBgUser = Math.max(0, Math.min(Math.min(maxBgUser, remaining), 1384 properties.getInt(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier, 1385 mDefaultMinReservedSlots.get(WORK_TYPE_BGUSER, 0)))); 1386 mMinReservedSlots.put(WORK_TYPE_BGUSER, minBgUser); 1387 } 1388 getMaxTotal()1389 int getMaxTotal() { 1390 return mMaxTotal; 1391 } 1392 getMax(@orkType int workType)1393 int getMax(@WorkType int workType) { 1394 return mMaxAllowedSlots.get(workType, mMaxTotal); 1395 } 1396 getMinReserved(@orkType int workType)1397 int getMinReserved(@WorkType int workType) { 1398 return mMinReservedSlots.get(workType); 1399 } 1400 dump(IndentingPrintWriter pw)1401 void dump(IndentingPrintWriter pw) { 1402 pw.print(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mMaxTotal).println(); 1403 pw.print(KEY_PREFIX_MIN_TOP + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_TOP)) 1404 .println(); 1405 pw.print(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_TOP)) 1406 .println(); 1407 pw.print(KEY_PREFIX_MIN_FGS + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_FGS)) 1408 .println(); 1409 pw.print(KEY_PREFIX_MAX_FGS + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_FGS)) 1410 .println(); 1411 pw.print(KEY_PREFIX_MIN_EJ + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_EJ)) 1412 .println(); 1413 pw.print(KEY_PREFIX_MAX_EJ + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_EJ)) 1414 .println(); 1415 pw.print(KEY_PREFIX_MIN_BG + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_BG)) 1416 .println(); 1417 pw.print(KEY_PREFIX_MAX_BG + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_BG)) 1418 .println(); 1419 pw.print(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier, 1420 mMinReservedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println(); 1421 pw.print(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier, 1422 mMaxAllowedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println(); 1423 pw.print(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier, 1424 mMinReservedSlots.get(WORK_TYPE_BGUSER)).println(); 1425 pw.print(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier, 1426 mMaxAllowedSlots.get(WORK_TYPE_BGUSER)).println(); 1427 } 1428 } 1429 1430 /** {@link WorkTypeConfig} for each memory trim level. */ 1431 static class WorkConfigLimitsPerMemoryTrimLevel { 1432 public final WorkTypeConfig normal; 1433 public final WorkTypeConfig moderate; 1434 public final WorkTypeConfig low; 1435 public final WorkTypeConfig critical; 1436 WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate, WorkTypeConfig low, WorkTypeConfig critical)1437 WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate, 1438 WorkTypeConfig low, WorkTypeConfig critical) { 1439 this.normal = normal; 1440 this.moderate = moderate; 1441 this.low = low; 1442 this.critical = critical; 1443 } 1444 } 1445 1446 /** 1447 * This class keeps the track of when a user's grace period expires. 1448 */ 1449 @VisibleForTesting 1450 static class GracePeriodObserver extends UserSwitchObserver { 1451 // Key is UserId and Value is the time when grace period expires 1452 @VisibleForTesting 1453 final SparseLongArray mGracePeriodExpiration = new SparseLongArray(); 1454 private int mCurrentUserId; 1455 @VisibleForTesting 1456 int mGracePeriod; 1457 private final UserManagerInternal mUserManagerInternal; 1458 final Object mLock = new Object(); 1459 1460 GracePeriodObserver(Context context)1461 GracePeriodObserver(Context context) { 1462 mCurrentUserId = LocalServices.getService(ActivityManagerInternal.class) 1463 .getCurrentUserId(); 1464 mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); 1465 mGracePeriod = Math.max(0, context.getResources().getInteger( 1466 R.integer.config_jobSchedulerUserGracePeriod)); 1467 } 1468 1469 @Override onUserSwitchComplete(int newUserId)1470 public void onUserSwitchComplete(int newUserId) { 1471 final long expiration = sElapsedRealtimeClock.millis() + mGracePeriod; 1472 synchronized (mLock) { 1473 if (mCurrentUserId != UserHandle.USER_NULL 1474 && mUserManagerInternal.exists(mCurrentUserId)) { 1475 mGracePeriodExpiration.append(mCurrentUserId, expiration); 1476 } 1477 mGracePeriodExpiration.delete(newUserId); 1478 mCurrentUserId = newUserId; 1479 } 1480 } 1481 onUserRemoved(int userId)1482 void onUserRemoved(int userId) { 1483 synchronized (mLock) { 1484 mGracePeriodExpiration.delete(userId); 1485 } 1486 } 1487 1488 @VisibleForTesting isWithinGracePeriodForUser(int userId)1489 public boolean isWithinGracePeriodForUser(int userId) { 1490 synchronized (mLock) { 1491 return userId == mCurrentUserId 1492 || sElapsedRealtimeClock.millis() 1493 < mGracePeriodExpiration.get(userId, Long.MAX_VALUE); 1494 } 1495 } 1496 } 1497 1498 /** 1499 * This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs 1500 * are running/pending, how many more job can start. 1501 * 1502 * Extracted for testing and logging. 1503 */ 1504 @VisibleForTesting 1505 static class WorkCountTracker { 1506 private int mConfigMaxTotal; 1507 private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES); 1508 private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES); 1509 private final SparseIntArray mRecycledReserved = new SparseIntArray(NUM_WORK_TYPES); 1510 1511 /** 1512 * Numbers may be lower in this than in {@link #mConfigNumReservedSlots} if there aren't 1513 * enough ready jobs of a type to take up all of the desired reserved slots. 1514 */ 1515 private final SparseIntArray mNumActuallyReservedSlots = new SparseIntArray(NUM_WORK_TYPES); 1516 private final SparseIntArray mNumPendingJobs = new SparseIntArray(NUM_WORK_TYPES); 1517 private final SparseIntArray mNumRunningJobs = new SparseIntArray(NUM_WORK_TYPES); 1518 private final SparseIntArray mNumStartingJobs = new SparseIntArray(NUM_WORK_TYPES); 1519 private int mNumUnspecializedRemaining = 0; 1520 setConfig(@onNull WorkTypeConfig workTypeConfig)1521 void setConfig(@NonNull WorkTypeConfig workTypeConfig) { 1522 mConfigMaxTotal = workTypeConfig.getMaxTotal(); 1523 for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { 1524 mConfigNumReservedSlots.put(workType, workTypeConfig.getMinReserved(workType)); 1525 mConfigAbsoluteMaxSlots.put(workType, workTypeConfig.getMax(workType)); 1526 } 1527 1528 mNumUnspecializedRemaining = mConfigMaxTotal; 1529 for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) { 1530 mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i), 1531 mConfigNumReservedSlots.get(mNumRunningJobs.keyAt(i))); 1532 } 1533 } 1534 resetCounts()1535 void resetCounts() { 1536 mNumActuallyReservedSlots.clear(); 1537 mNumPendingJobs.clear(); 1538 mNumRunningJobs.clear(); 1539 resetStagingCount(); 1540 } 1541 resetStagingCount()1542 void resetStagingCount() { 1543 mNumStartingJobs.clear(); 1544 } 1545 incrementRunningJobCount(@orkType int workType)1546 void incrementRunningJobCount(@WorkType int workType) { 1547 mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1); 1548 } 1549 incrementPendingJobCount(int workTypes)1550 void incrementPendingJobCount(int workTypes) { 1551 adjustPendingJobCount(workTypes, true); 1552 } 1553 decrementPendingJobCount(int workTypes)1554 void decrementPendingJobCount(int workTypes) { 1555 if (adjustPendingJobCount(workTypes, false) > 1) { 1556 // We don't need to adjust reservations if only one work type was modified 1557 // because that work type is the one we're using. 1558 1559 for (int workType = 1; workType <= workTypes; workType <<= 1) { 1560 if ((workType & workTypes) == workType) { 1561 maybeAdjustReservations(workType); 1562 } 1563 } 1564 } 1565 } 1566 1567 /** Returns the number of WorkTypes that were modified. */ adjustPendingJobCount(int workTypes, boolean add)1568 private int adjustPendingJobCount(int workTypes, boolean add) { 1569 final int adj = add ? 1 : -1; 1570 1571 int numAdj = 0; 1572 // We don't know which type we'll classify the job as when we run it yet, so make sure 1573 // we have space in all applicable slots. 1574 for (int workType = 1; workType <= workTypes; workType <<= 1) { 1575 if ((workTypes & workType) == workType) { 1576 mNumPendingJobs.put(workType, mNumPendingJobs.get(workType) + adj); 1577 numAdj++; 1578 } 1579 } 1580 1581 return numAdj; 1582 } 1583 stageJob(@orkType int workType, int allWorkTypes)1584 void stageJob(@WorkType int workType, int allWorkTypes) { 1585 final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1; 1586 mNumStartingJobs.put(workType, newNumStartingJobs); 1587 decrementPendingJobCount(allWorkTypes); 1588 if (newNumStartingJobs + mNumRunningJobs.get(workType) 1589 > mNumActuallyReservedSlots.get(workType)) { 1590 mNumUnspecializedRemaining--; 1591 } 1592 } 1593 onStagedJobFailed(@orkType int workType)1594 void onStagedJobFailed(@WorkType int workType) { 1595 final int oldNumStartingJobs = mNumStartingJobs.get(workType); 1596 if (oldNumStartingJobs == 0) { 1597 Slog.e(TAG, "# staged jobs for " + workType + " went negative."); 1598 // We are in a bad state. We will eventually recover when the pending list is 1599 // regenerated. 1600 return; 1601 } 1602 mNumStartingJobs.put(workType, oldNumStartingJobs - 1); 1603 maybeAdjustReservations(workType); 1604 } 1605 maybeAdjustReservations(@orkType int workType)1606 private void maybeAdjustReservations(@WorkType int workType) { 1607 // Always make sure we reserve the minimum number of slots in case new jobs become ready 1608 // soon. 1609 final int numRemainingForType = Math.max(mConfigNumReservedSlots.get(workType), 1610 mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType) 1611 + mNumPendingJobs.get(workType)); 1612 if (numRemainingForType < mNumActuallyReservedSlots.get(workType)) { 1613 // We've run all jobs for this type. Let another type use it now. 1614 mNumActuallyReservedSlots.put(workType, numRemainingForType); 1615 int assignWorkType = WORK_TYPE_NONE; 1616 for (int i = 0; i < mNumActuallyReservedSlots.size(); ++i) { 1617 int wt = mNumActuallyReservedSlots.keyAt(i); 1618 if (assignWorkType == WORK_TYPE_NONE || wt < assignWorkType) { 1619 // Try to give this slot to the highest priority one within its limits. 1620 int total = mNumRunningJobs.get(wt) + mNumStartingJobs.get(wt) 1621 + mNumPendingJobs.get(wt); 1622 if (mNumActuallyReservedSlots.valueAt(i) < mConfigAbsoluteMaxSlots.get(wt) 1623 && total > mNumActuallyReservedSlots.valueAt(i)) { 1624 assignWorkType = wt; 1625 } 1626 } 1627 } 1628 if (assignWorkType != WORK_TYPE_NONE) { 1629 mNumActuallyReservedSlots.put(assignWorkType, 1630 mNumActuallyReservedSlots.get(assignWorkType) + 1); 1631 } else { 1632 mNumUnspecializedRemaining++; 1633 } 1634 } 1635 } 1636 onJobStarted(@orkType int workType)1637 void onJobStarted(@WorkType int workType) { 1638 mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1); 1639 final int oldNumStartingJobs = mNumStartingJobs.get(workType); 1640 if (oldNumStartingJobs == 0) { 1641 Slog.e(TAG, "# stated jobs for " + workType + " went negative."); 1642 // We are in a bad state. We will eventually recover when the pending list is 1643 // regenerated. For now, only modify the running count. 1644 } else { 1645 mNumStartingJobs.put(workType, oldNumStartingJobs - 1); 1646 } 1647 } 1648 onJobFinished(@orkType int workType)1649 void onJobFinished(@WorkType int workType) { 1650 final int newNumRunningJobs = mNumRunningJobs.get(workType) - 1; 1651 if (newNumRunningJobs < 0) { 1652 // We are in a bad state. We will eventually recover when the pending list is 1653 // regenerated. 1654 Slog.e(TAG, "# running jobs for " + workType + " went negative."); 1655 return; 1656 } 1657 mNumRunningJobs.put(workType, newNumRunningJobs); 1658 maybeAdjustReservations(workType); 1659 } 1660 onCountDone()1661 void onCountDone() { 1662 // Calculate how many slots to reserve for each work type. "Unspecialized" slots will 1663 // be reserved for higher importance types first (ie. top before ej before bg). 1664 // Steps: 1665 // 1. Account for slots for already running jobs 1666 // 2. Use remaining unaccounted slots to try and ensure minimum reserved slots 1667 // 3. Allocate remaining up to max, based on importance 1668 1669 mNumUnspecializedRemaining = mConfigMaxTotal; 1670 1671 // Step 1 1672 for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { 1673 int run = mNumRunningJobs.get(workType); 1674 mRecycledReserved.put(workType, run); 1675 mNumUnspecializedRemaining -= run; 1676 } 1677 1678 // Step 2 1679 for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { 1680 int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType); 1681 int res = mRecycledReserved.get(workType); 1682 int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, 1683 Math.min(num, mConfigNumReservedSlots.get(workType) - res))); 1684 res += fillUp; 1685 mRecycledReserved.put(workType, res); 1686 mNumUnspecializedRemaining -= fillUp; 1687 } 1688 1689 // Step 3 1690 for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { 1691 int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType); 1692 int res = mRecycledReserved.get(workType); 1693 int unspecializedAssigned = Math.max(0, 1694 Math.min(mNumUnspecializedRemaining, 1695 Math.min(mConfigAbsoluteMaxSlots.get(workType), num) - res)); 1696 mNumActuallyReservedSlots.put(workType, res + unspecializedAssigned); 1697 mNumUnspecializedRemaining -= unspecializedAssigned; 1698 } 1699 } 1700 canJobStart(int workTypes)1701 int canJobStart(int workTypes) { 1702 for (int workType = 1; workType <= workTypes; workType <<= 1) { 1703 if ((workTypes & workType) == workType) { 1704 final int maxAllowed = Math.min( 1705 mConfigAbsoluteMaxSlots.get(workType), 1706 mNumActuallyReservedSlots.get(workType) + mNumUnspecializedRemaining); 1707 if (mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType) 1708 < maxAllowed) { 1709 return workType; 1710 } 1711 } 1712 } 1713 return WORK_TYPE_NONE; 1714 } 1715 canJobStart(int workTypes, @WorkType int replacingWorkType)1716 int canJobStart(int workTypes, @WorkType int replacingWorkType) { 1717 final boolean changedNums; 1718 int oldNumRunning = mNumRunningJobs.get(replacingWorkType); 1719 if (replacingWorkType != WORK_TYPE_NONE && oldNumRunning > 0) { 1720 mNumRunningJobs.put(replacingWorkType, oldNumRunning - 1); 1721 // Lazy implementation to avoid lots of processing. Best way would be to go 1722 // through the whole process of adjusting reservations, but the processing cost 1723 // is likely not worth it. 1724 mNumUnspecializedRemaining++; 1725 changedNums = true; 1726 } else { 1727 changedNums = false; 1728 } 1729 1730 final int ret = canJobStart(workTypes); 1731 if (changedNums) { 1732 mNumRunningJobs.put(replacingWorkType, oldNumRunning); 1733 mNumUnspecializedRemaining--; 1734 } 1735 return ret; 1736 } 1737 getPendingJobCount(@orkType final int workType)1738 int getPendingJobCount(@WorkType final int workType) { 1739 return mNumPendingJobs.get(workType, 0); 1740 } 1741 getRunningJobCount(@orkType final int workType)1742 int getRunningJobCount(@WorkType final int workType) { 1743 return mNumRunningJobs.get(workType, 0); 1744 } 1745 isOverTypeLimit(@orkType final int workType)1746 boolean isOverTypeLimit(@WorkType final int workType) { 1747 return getRunningJobCount(workType) > mConfigAbsoluteMaxSlots.get(workType); 1748 } 1749 toString()1750 public String toString() { 1751 StringBuilder sb = new StringBuilder(); 1752 1753 sb.append("Config={"); 1754 sb.append("tot=").append(mConfigMaxTotal); 1755 sb.append(" mins="); 1756 sb.append(mConfigNumReservedSlots); 1757 sb.append(" maxs="); 1758 sb.append(mConfigAbsoluteMaxSlots); 1759 sb.append("}"); 1760 1761 sb.append(", act res=").append(mNumActuallyReservedSlots); 1762 sb.append(", Pending=").append(mNumPendingJobs); 1763 sb.append(", Running=").append(mNumRunningJobs); 1764 sb.append(", Staged=").append(mNumStartingJobs); 1765 sb.append(", # unspecialized remaining=").append(mNumUnspecializedRemaining); 1766 1767 return sb.toString(); 1768 } 1769 } 1770 1771 private static class PackageStats { 1772 public int userId; 1773 public String packageName; 1774 public int numRunningEj; 1775 public int numRunningRegular; 1776 public int numStagedEj; 1777 public int numStagedRegular; 1778 setPackage(int userId, @NonNull String packageName)1779 private void setPackage(int userId, @NonNull String packageName) { 1780 this.userId = userId; 1781 this.packageName = packageName; 1782 numRunningEj = numRunningRegular = 0; 1783 resetStagedCount(); 1784 } 1785 resetStagedCount()1786 private void resetStagedCount() { 1787 numStagedEj = numStagedRegular = 0; 1788 } 1789 adjustRunningCount(boolean add, boolean forEj)1790 private void adjustRunningCount(boolean add, boolean forEj) { 1791 if (forEj) { 1792 numRunningEj = Math.max(0, numRunningEj + (add ? 1 : -1)); 1793 } else { 1794 numRunningRegular = Math.max(0, numRunningRegular + (add ? 1 : -1)); 1795 } 1796 } 1797 adjustStagedCount(boolean add, boolean forEj)1798 private void adjustStagedCount(boolean add, boolean forEj) { 1799 if (forEj) { 1800 numStagedEj = Math.max(0, numStagedEj + (add ? 1 : -1)); 1801 } else { 1802 numStagedRegular = Math.max(0, numStagedRegular + (add ? 1 : -1)); 1803 } 1804 } 1805 1806 @GuardedBy("mLock") dumpLocked(IndentingPrintWriter pw)1807 private void dumpLocked(IndentingPrintWriter pw) { 1808 pw.print("PackageStats{"); 1809 pw.print(userId); 1810 pw.print("-"); 1811 pw.print(packageName); 1812 pw.print("#runEJ", numRunningEj); 1813 pw.print("#runReg", numRunningRegular); 1814 pw.print("#stagedEJ", numStagedEj); 1815 pw.print("#stagedReg", numStagedRegular); 1816 pw.println("}"); 1817 } 1818 } 1819 } 1820