Loading apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +28 −8 Original line number Diff line number Diff line Loading @@ -241,10 +241,11 @@ public final class QuotaController extends StateController { + "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", " + "sessionCountInWindow=" + sessionCountInWindow + ", " + "inQuotaTime=" + inQuotaTimeElapsed + ", " + "jobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", " + "jobCountInRateLimitingWindow=" + jobCountInRateLimitingWindow + ", " + "sessionCountExpirationTime=" + sessionRateLimitExpirationTimeElapsed + ", " + "sessionCountInRateLimitingWindow=" + sessionCountInRateLimitingWindow; + "rateLimitJobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", " + "rateLimitJobCountWindow=" + jobCountInRateLimitingWindow + ", " + "rateLimitSessionCountExpirationTime=" + sessionRateLimitExpirationTimeElapsed + ", " + "rateLimitSessionCountWindow=" + sessionCountInRateLimitingWindow; } @Override Loading Loading @@ -863,12 +864,19 @@ public final class QuotaController extends StateController { stats.executionTimeInMaxPeriodMs = 0; stats.bgJobCountInMaxPeriod = 0; stats.sessionCountInWindow = 0; if (stats.jobCountLimit == 0 || stats.sessionCountLimit == 0) { // App won't be in quota until configuration changes. stats.inQuotaTimeElapsed = Long.MAX_VALUE; } else { stats.inQuotaTimeElapsed = 0; } Timer timer = mPkgTimers.get(userId, packageName); final long nowElapsed = sElapsedRealtimeClock.millis(); stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS; if (timer != null && timer.isActive()) { // Exclude active sessions from the session count so that new jobs aren't prevented // from starting due to an app hitting the session limit. stats.executionTimeInWindowMs = stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed); stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount(); Loading @@ -883,6 +891,10 @@ public final class QuotaController extends StateController { stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS); } if (stats.bgJobCountInWindow >= stats.jobCountLimit) { stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, nowElapsed + stats.windowSizeMs); } } List<TimingSession> sessions = mTimingSessions.get(userId, packageName); Loading Loading @@ -1303,6 +1315,13 @@ public final class QuotaController extends StateController { inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed, stats.sessionRateLimitExpirationTimeElapsed); } if (inQuotaTimeElapsed <= sElapsedRealtimeClock.millis()) { final long nowElapsed = sElapsedRealtimeClock.millis(); Slog.wtf(TAG, "In quota time is " + (nowElapsed - inQuotaTimeElapsed) + "ms old. Now=" + nowElapsed + ", inQuotaTime=" + inQuotaTimeElapsed + ": " + stats); inQuotaTimeElapsed = nowElapsed + 5 * MINUTE_IN_MILLIS; } mInQuotaAlarmListener.addAlarmLocked(userId, packageName, inQuotaTimeElapsed); } Loading Loading @@ -1916,8 +1935,8 @@ public final class QuotaController extends StateController { @GuardedBy("mLock") private void setNextAlarmLocked(long earliestTriggerElapsed) { if (mAlarmQueue.size() > 0) { final long nextTriggerTimeElapsed = Math.max(earliestTriggerElapsed, mAlarmQueue.peek().second); final Pair<Package, Long> alarm = mAlarmQueue.peek(); final long nextTriggerTimeElapsed = Math.max(earliestTriggerElapsed, alarm.second); // Only schedule the alarm if one of the following is true: // 1. There isn't one currently scheduled // 2. The new alarm is significantly earlier than the previous alarm. If it's Loading @@ -1928,7 +1947,8 @@ public final class QuotaController extends StateController { || nextTriggerTimeElapsed < mTriggerTimeElapsed - 3 * MINUTE_IN_MILLIS || mTriggerTimeElapsed < nextTriggerTimeElapsed) { if (DEBUG) { Slog.d(TAG, "Scheduling start alarm at " + nextTriggerTimeElapsed); Slog.d(TAG, "Scheduling start alarm at " + nextTriggerTimeElapsed + " for app " + alarm.first); } mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextTriggerTimeElapsed, ALARM_TAG_QUOTA_CHECK, this, mHandler); Loading services/core/java/com/android/server/utils/quota/CountQuotaTracker.java +6 −1 Original line number Diff line number Diff line Loading @@ -408,7 +408,12 @@ public class CountQuotaTracker extends QuotaTracker { void updateExecutionStatsLocked(final int userId, @NonNull final String packageName, @Nullable final String tag, @NonNull ExecutionStats stats) { stats.countInWindow = 0; if (stats.countLimit == 0) { // UPTC won't be in quota until configuration changes. stats.inQuotaTimeElapsed = Long.MAX_VALUE; } else { stats.inQuotaTimeElapsed = 0; } // This can be used to determine when an app will have enough quota to transition from // out-of-quota to in-quota. Loading services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +71 −0 Original line number Diff line number Diff line Loading @@ -620,6 +620,77 @@ public class QuotaControllerTest { assertEquals(expectedStats, inputStats); } @Test public void testUpdateExecutionStatsLocked_WithTimer() { final long now = sElapsedRealtimeClock.millis(); setProcessState(ActivityManager.PROCESS_STATE_SERVICE); ExecutionStats expectedStats = new ExecutionStats(); ExecutionStats inputStats = new ExecutionStats(); inputStats.windowSizeMs = expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; inputStats.jobCountLimit = expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; inputStats.sessionCountLimit = expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; // Active timer isn't counted as session yet. expectedStats.sessionCountInWindow = 0; // Timer only, under quota. for (int i = 1; i < mQcConstants.MAX_JOB_COUNT_RARE; ++i) { JobStatus jobStatus = createJobStatus("testUpdateExecutionStatsLocked_WithTimer", i); setStandbyBucket(RARE_INDEX, jobStatus); // 24 hour window mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); mQuotaController.prepareForExecutionLocked(jobStatus); advanceElapsedClock(7000); expectedStats.expirationTimeElapsed = sElapsedRealtimeClock.millis(); expectedStats.executionTimeInWindowMs = expectedStats.executionTimeInMaxPeriodMs = 7000 * i; expectedStats.bgJobCountInWindow = expectedStats.bgJobCountInMaxPeriod = i; mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats); assertEquals(expectedStats, inputStats); assertTrue(mQuotaController.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); } // Add old session. Make sure values are combined correctly. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(sElapsedRealtimeClock.millis() - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); expectedStats.sessionCountInWindow = 1; expectedStats.expirationTimeElapsed = sElapsedRealtimeClock.millis() + 18 * HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs += 10 * MINUTE_IN_MILLIS; expectedStats.executionTimeInMaxPeriodMs += 10 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow += 5; expectedStats.bgJobCountInMaxPeriod += 5; // Active timer is under quota, so out of quota due to old session. expectedStats.inQuotaTimeElapsed = sElapsedRealtimeClock.millis() + 18 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS; mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats); assertEquals(expectedStats, inputStats); assertFalse( mQuotaController.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); // Quota should be exceeded due to activity in active timer. JobStatus jobStatus = createJobStatus("testUpdateExecutionStatsLocked_WithTimer", 0); setStandbyBucket(RARE_INDEX, jobStatus); // 24 hour window mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); mQuotaController.prepareForExecutionLocked(jobStatus); advanceElapsedClock(10000); expectedStats.executionTimeInWindowMs += 10000; expectedStats.executionTimeInMaxPeriodMs += 10000; expectedStats.bgJobCountInWindow++; expectedStats.bgJobCountInMaxPeriod++; // Out of quota due to activity in active timer, so in quota time should be when enough // time has passed since active timer. expectedStats.inQuotaTimeElapsed = sElapsedRealtimeClock.millis() + expectedStats.windowSizeMs; mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats); assertEquals(expectedStats, inputStats); assertFalse( mQuotaController.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); } /** * Tests that getExecutionStatsLocked returns the correct stats. */ Loading Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +28 −8 Original line number Diff line number Diff line Loading @@ -241,10 +241,11 @@ public final class QuotaController extends StateController { + "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", " + "sessionCountInWindow=" + sessionCountInWindow + ", " + "inQuotaTime=" + inQuotaTimeElapsed + ", " + "jobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", " + "jobCountInRateLimitingWindow=" + jobCountInRateLimitingWindow + ", " + "sessionCountExpirationTime=" + sessionRateLimitExpirationTimeElapsed + ", " + "sessionCountInRateLimitingWindow=" + sessionCountInRateLimitingWindow; + "rateLimitJobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", " + "rateLimitJobCountWindow=" + jobCountInRateLimitingWindow + ", " + "rateLimitSessionCountExpirationTime=" + sessionRateLimitExpirationTimeElapsed + ", " + "rateLimitSessionCountWindow=" + sessionCountInRateLimitingWindow; } @Override Loading Loading @@ -863,12 +864,19 @@ public final class QuotaController extends StateController { stats.executionTimeInMaxPeriodMs = 0; stats.bgJobCountInMaxPeriod = 0; stats.sessionCountInWindow = 0; if (stats.jobCountLimit == 0 || stats.sessionCountLimit == 0) { // App won't be in quota until configuration changes. stats.inQuotaTimeElapsed = Long.MAX_VALUE; } else { stats.inQuotaTimeElapsed = 0; } Timer timer = mPkgTimers.get(userId, packageName); final long nowElapsed = sElapsedRealtimeClock.millis(); stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS; if (timer != null && timer.isActive()) { // Exclude active sessions from the session count so that new jobs aren't prevented // from starting due to an app hitting the session limit. stats.executionTimeInWindowMs = stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed); stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount(); Loading @@ -883,6 +891,10 @@ public final class QuotaController extends StateController { stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS); } if (stats.bgJobCountInWindow >= stats.jobCountLimit) { stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, nowElapsed + stats.windowSizeMs); } } List<TimingSession> sessions = mTimingSessions.get(userId, packageName); Loading Loading @@ -1303,6 +1315,13 @@ public final class QuotaController extends StateController { inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed, stats.sessionRateLimitExpirationTimeElapsed); } if (inQuotaTimeElapsed <= sElapsedRealtimeClock.millis()) { final long nowElapsed = sElapsedRealtimeClock.millis(); Slog.wtf(TAG, "In quota time is " + (nowElapsed - inQuotaTimeElapsed) + "ms old. Now=" + nowElapsed + ", inQuotaTime=" + inQuotaTimeElapsed + ": " + stats); inQuotaTimeElapsed = nowElapsed + 5 * MINUTE_IN_MILLIS; } mInQuotaAlarmListener.addAlarmLocked(userId, packageName, inQuotaTimeElapsed); } Loading Loading @@ -1916,8 +1935,8 @@ public final class QuotaController extends StateController { @GuardedBy("mLock") private void setNextAlarmLocked(long earliestTriggerElapsed) { if (mAlarmQueue.size() > 0) { final long nextTriggerTimeElapsed = Math.max(earliestTriggerElapsed, mAlarmQueue.peek().second); final Pair<Package, Long> alarm = mAlarmQueue.peek(); final long nextTriggerTimeElapsed = Math.max(earliestTriggerElapsed, alarm.second); // Only schedule the alarm if one of the following is true: // 1. There isn't one currently scheduled // 2. The new alarm is significantly earlier than the previous alarm. If it's Loading @@ -1928,7 +1947,8 @@ public final class QuotaController extends StateController { || nextTriggerTimeElapsed < mTriggerTimeElapsed - 3 * MINUTE_IN_MILLIS || mTriggerTimeElapsed < nextTriggerTimeElapsed) { if (DEBUG) { Slog.d(TAG, "Scheduling start alarm at " + nextTriggerTimeElapsed); Slog.d(TAG, "Scheduling start alarm at " + nextTriggerTimeElapsed + " for app " + alarm.first); } mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextTriggerTimeElapsed, ALARM_TAG_QUOTA_CHECK, this, mHandler); Loading
services/core/java/com/android/server/utils/quota/CountQuotaTracker.java +6 −1 Original line number Diff line number Diff line Loading @@ -408,7 +408,12 @@ public class CountQuotaTracker extends QuotaTracker { void updateExecutionStatsLocked(final int userId, @NonNull final String packageName, @Nullable final String tag, @NonNull ExecutionStats stats) { stats.countInWindow = 0; if (stats.countLimit == 0) { // UPTC won't be in quota until configuration changes. stats.inQuotaTimeElapsed = Long.MAX_VALUE; } else { stats.inQuotaTimeElapsed = 0; } // This can be used to determine when an app will have enough quota to transition from // out-of-quota to in-quota. Loading
services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +71 −0 Original line number Diff line number Diff line Loading @@ -620,6 +620,77 @@ public class QuotaControllerTest { assertEquals(expectedStats, inputStats); } @Test public void testUpdateExecutionStatsLocked_WithTimer() { final long now = sElapsedRealtimeClock.millis(); setProcessState(ActivityManager.PROCESS_STATE_SERVICE); ExecutionStats expectedStats = new ExecutionStats(); ExecutionStats inputStats = new ExecutionStats(); inputStats.windowSizeMs = expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; inputStats.jobCountLimit = expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; inputStats.sessionCountLimit = expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; // Active timer isn't counted as session yet. expectedStats.sessionCountInWindow = 0; // Timer only, under quota. for (int i = 1; i < mQcConstants.MAX_JOB_COUNT_RARE; ++i) { JobStatus jobStatus = createJobStatus("testUpdateExecutionStatsLocked_WithTimer", i); setStandbyBucket(RARE_INDEX, jobStatus); // 24 hour window mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); mQuotaController.prepareForExecutionLocked(jobStatus); advanceElapsedClock(7000); expectedStats.expirationTimeElapsed = sElapsedRealtimeClock.millis(); expectedStats.executionTimeInWindowMs = expectedStats.executionTimeInMaxPeriodMs = 7000 * i; expectedStats.bgJobCountInWindow = expectedStats.bgJobCountInMaxPeriod = i; mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats); assertEquals(expectedStats, inputStats); assertTrue(mQuotaController.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); } // Add old session. Make sure values are combined correctly. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(sElapsedRealtimeClock.millis() - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); expectedStats.sessionCountInWindow = 1; expectedStats.expirationTimeElapsed = sElapsedRealtimeClock.millis() + 18 * HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs += 10 * MINUTE_IN_MILLIS; expectedStats.executionTimeInMaxPeriodMs += 10 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow += 5; expectedStats.bgJobCountInMaxPeriod += 5; // Active timer is under quota, so out of quota due to old session. expectedStats.inQuotaTimeElapsed = sElapsedRealtimeClock.millis() + 18 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS; mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats); assertEquals(expectedStats, inputStats); assertFalse( mQuotaController.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); // Quota should be exceeded due to activity in active timer. JobStatus jobStatus = createJobStatus("testUpdateExecutionStatsLocked_WithTimer", 0); setStandbyBucket(RARE_INDEX, jobStatus); // 24 hour window mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); mQuotaController.prepareForExecutionLocked(jobStatus); advanceElapsedClock(10000); expectedStats.executionTimeInWindowMs += 10000; expectedStats.executionTimeInMaxPeriodMs += 10000; expectedStats.bgJobCountInWindow++; expectedStats.bgJobCountInMaxPeriod++; // Out of quota due to activity in active timer, so in quota time should be when enough // time has passed since active timer. expectedStats.inQuotaTimeElapsed = sElapsedRealtimeClock.millis() + expectedStats.windowSizeMs; mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats); assertEquals(expectedStats, inputStats); assertFalse( mQuotaController.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); } /** * Tests that getExecutionStatsLocked returns the correct stats. */ Loading