Merge "Changing default limits, clarify flags, fix bug." into qt-dev
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 3f8ddff..2873379 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -273,9 +273,11 @@
// The maximum number of jobs an app can run within this particular standby bucket's
// window size.
optional int32 max_job_count_rare = 11;
+ // The period of time used to rate limit recently run jobs.
+ optional int32 rate_limiting_window_ms = 19;
// The maximum number of jobs that should be allowed to run in the past
- // {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
- optional int32 max_job_count_per_allowed_time = 12;
+ // rate_limiting_window_ms.
+ optional int32 max_job_count_per_rate_limiting_window = 12;
// The maximum number of timing sessions an app can run within this particular standby
// bucket's window size.
optional int32 max_session_count_active = 13;
@@ -289,8 +291,8 @@
// bucket's window size.
optional int32 max_session_count_rare = 16;
// The maximum number of timing sessions that should be allowed to run in the past
- // {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
- optional int32 max_session_count_per_allowed_time = 17;
+ // rate_limiting_window_ms.
+ optional int32 max_session_count_per_rate_limiting_window = 17;
// Treat two distinct {@link TimingSession}s as the same if they start and end within this
// amount of time of each other.
optional int64 timing_session_coalescing_duration_ms = 18;
@@ -517,63 +519,56 @@
optional int64 expiration_time_elapsed = 2;
optional int64 window_size_ms = 3;
- /** The total amount of time the app ran in its respective bucket window size. */
+ optional int32 job_count_limit = 14;
+ optional int32 session_count_limit = 15;
+
+ // The total amount of time the app ran in its respective bucket window size.
optional int64 execution_time_in_window_ms = 4;
optional int32 bg_job_count_in_window = 5;
- /**
- * The total amount of time the app ran in the last
- * {@link QuotaController#MAX_PERIOD_MS}.
- */
+ // The total amount of time the app ran in the last
+ // {@link QuotaController#MAX_PERIOD_MS}.
optional int64 execution_time_in_max_period_ms = 6;
optional int32 bg_job_count_in_max_period = 7;
- /**
- * The number of {@link TimingSession}s within the bucket window size. This will include
- * sessions that started before the window as long as they end within the window.
- */
+ // The number of {@link TimingSession}s within the bucket window size. This will include
+ // sessions that started before the window as long as they end within the window.
optional int32 session_count_in_window = 11;
- /**
- * The time after which the sum of all the app's sessions plus
- * ConstantsProto.QuotaController.in_quota_buffer_ms equals the quota. This is only
- * valid if
- * execution_time_in_window_ms >=
- * ConstantsProto.QuotaController.allowed_time_per_period_ms
- * or
- * execution_time_in_max_period_ms >=
- * ConstantsProto.QuotaController.max_execution_time_ms.
- */
- optional int64 quota_cutoff_time_elapsed = 8;
+ // The time after which the app will be under the bucket quota. This is only valid if
+ // execution_time_in_window_ms >=
+ // ConstantsProto.QuotaController.allowed_time_per_period_ms
+ // or
+ // execution_time_in_max_period_ms >=
+ // ConstantsProto.QuotaController.max_execution_time_ms
+ // or
+ // bg_job_count_in_window >= job_count_limit
+ // or
+ // session_count_in_window >= session_count_limit.
+ optional int64 in_quota_time_elapsed = 8;
- /**
- * The time after which job_count_in_allowed_time should be considered invalid, in the
- * elapsed realtime timebase.
- */
+ // The time after which job_count_in_rate_limiting_window should be considered invalid,
+ // in the elapsed realtime timebase.
optional int64 job_count_expiration_time_elapsed = 9;
- /**
- * The number of jobs that ran in at least the last
- * ConstantsProto.QuotaController.allowed_time_per_period_ms.
- * It may contain a few stale entries since cleanup won't happen exactly every
- * ConstantsProto.QuotaController.allowed_time_per_period_ms.
- */
- optional int32 job_count_in_allowed_time = 10;
+ // The number of jobs that ran in at least the last
+ // ConstantsProto.QuotaController.rate_limiting_window_ms.
+ // It may contain a few stale entries since cleanup won't happen exactly every
+ // ConstantsProto.QuotaController.rate_limiting_window_ms. This should only be
+ // considered valid before elapsed realtime has reached
+ // job_count_expiration_time_elapsed.
+ optional int32 job_count_in_rate_limiting_window = 10;
- /**
- * The time after which {@link #timingSessionCountInAllowedTime} should be considered
- * invalid, in the elapsed realtime timebase.
- */
+ // The time after which {@link #timingSessionCountInAllowedTime} should be considered
+ // invalid, in the elapsed realtime timebase.
optional int64 session_count_expiration_time_elapsed = 12;
- /**
- * The number of {@link TimingSession}s that ran in at least the last
- * {@link #mAllowedTimePerPeriodMs}. It may contain a few stale entries since cleanup won't
- * happen exactly every {@link #mAllowedTimePerPeriodMs}. This should only be considered
- * valid before elapsed realtime has reached
- * {@link #timingSessionCountExpirationTimeElapsed}.
- */
- optional int32 session_count_in_allowed_time = 13;
+ // The number of {@link TimingSession}s that ran in at least the last
+ // ConstantsProto.QuotaController.rate_limiting_window_ms. It may contain a few stale
+ // entries since cleanup won't happen exactly every
+ // ConstantsProto.QuotaController.rate_limiting_window_ms. This should only be considered
+ // valid before elapsed realtime has reached session_count_expiration_time_elapsed.
+ optional int32 session_count_in_rate_limiting_window = 13;
}
message Package {
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
index f560d69..ef6944e 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -16,6 +16,10 @@
package com.android.server.job.controllers;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
@@ -75,17 +79,28 @@
/**
* Controller that tracks whether an app has exceeded its standby bucket quota.
*
- * Each job in each bucket is given 10 minutes to run within its respective time window. Active
- * jobs can run indefinitely, working set jobs can run for 10 minutes within a 2 hour window,
- * frequent jobs get to run 10 minutes in an 8 hour window, and rare jobs get to run 10 minutes in
- * a 24 hour window. The windows are rolling, so as soon as a job would have some quota based on its
- * bucket, it will be eligible to run. When a job's bucket changes, its new quota is immediately
- * applied to it.
+ * With initial defaults, each app in each bucket is given 10 minutes to run within its respective
+ * time window. Active jobs can run indefinitely, working set jobs can run for 10 minutes within a
+ * 2 hour window, frequent jobs get to run 10 minutes in an 8 hour window, and rare jobs get to run
+ * 10 minutes in a 24 hour window. The windows are rolling, so as soon as a job would have some
+ * quota based on its bucket, it will be eligible to run. When a job's bucket changes, its new
+ * quota is immediately applied to it.
+ *
+ * Job and session count limits are included to prevent abuse/spam. Each bucket has its own limit on
+ * the number of jobs or sessions that can run within the window. Regardless of bucket, apps will
+ * not be allowed to run more than 20 jobs within the past 10 minutes.
*
* Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run
* freely when an app enters the foreground state and are restricted when the app leaves the
- * foreground state. However, jobs that are started while the app is in the TOP state are not
- * restricted regardless of the app's state change.
+ * foreground state. However, jobs that are started while the app is in the TOP state do not count
+ * towards any quota and are not restricted regardless of the app's state change.
+ *
+ * Jobs will not be throttled when the device is charging. The device is considered to be charging
+ * once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast.
+ *
+ * Note: all limits are enforced per bucket window unless explicitly stated otherwise.
+ * All stated values are configurable and subject to change. See {@link QcConstants} for current
+ * defaults.
*
* Test: atest com.android.server.job.controllers.QuotaControllerTest
*/
@@ -94,8 +109,6 @@
private static final boolean DEBUG = JobSchedulerService.DEBUG
|| Log.isLoggable(TAG, Log.DEBUG);
- private static final long MINUTE_IN_MILLIS = 60 * 1000L;
-
private static final String ALARM_TAG_CLEANUP = "*job.cleanup*";
private static final String ALARM_TAG_QUOTA_CHECK = "*job.quota_check*";
@@ -244,6 +257,8 @@
public long expirationTimeElapsed;
public long windowSizeMs;
+ public int jobCountLimit;
+ public int sessionCountLimit;
/** The total amount of time the app ran in its respective bucket window size. */
public long executionTimeInWindowMs;
@@ -260,54 +275,58 @@
public int sessionCountInWindow;
/**
- * The time after which the sum of all the app's sessions plus {@link #mQuotaBufferMs}
- * equals the quota. This is only valid if
- * executionTimeInWindowMs >= {@link #mAllowedTimePerPeriodMs} or
- * executionTimeInMaxPeriodMs >= {@link #mMaxExecutionTimeMs}.
+ * The time after which the app will be under the bucket quota and can start running jobs
+ * again. This is only valid if
+ * {@link #executionTimeInWindowMs} >= {@link #mAllowedTimePerPeriodMs},
+ * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs},
+ * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or
+ * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}.
*/
- public long quotaCutoffTimeElapsed;
+ public long inQuotaTimeElapsed;
/**
- * The time after which {@link #jobCountInAllowedTime} should be considered invalid, in the
- * elapsed realtime timebase.
+ * The time after which {@link #jobCountInRateLimitingWindow} should be considered invalid,
+ * in the elapsed realtime timebase.
*/
- public long jobCountExpirationTimeElapsed;
+ public long jobRateLimitExpirationTimeElapsed;
/**
- * The number of jobs that ran in at least the last {@link #mAllowedTimePerPeriodMs}.
+ * The number of jobs that ran in at least the last {@link #mRateLimitingWindowMs}.
* It may contain a few stale entries since cleanup won't happen exactly every
- * {@link #mAllowedTimePerPeriodMs}.
+ * {@link #mRateLimitingWindowMs}.
*/
- public int jobCountInAllowedTime;
+ public int jobCountInRateLimitingWindow;
/**
- * The time after which {@link #sessionCountInAllowedTime} should be considered
+ * The time after which {@link #sessionCountInRateLimitingWindow} should be considered
* invalid, in the elapsed realtime timebase.
*/
- public long sessionCountExpirationTimeElapsed;
+ public long sessionRateLimitExpirationTimeElapsed;
/**
* The number of {@link TimingSession}s that ran in at least the last
- * {@link #mAllowedTimePerPeriodMs}. It may contain a few stale entries since cleanup won't
- * happen exactly every {@link #mAllowedTimePerPeriodMs}. This should only be considered
- * valid before elapsed realtime has reached {@link #sessionCountExpirationTimeElapsed}.
+ * {@link #mRateLimitingWindowMs}. It may contain a few stale entries since cleanup won't
+ * happen exactly every {@link #mRateLimitingWindowMs}. This should only be considered
+ * valid before elapsed realtime has reached {@link #sessionRateLimitExpirationTimeElapsed}.
*/
- public int sessionCountInAllowedTime;
+ public int sessionCountInRateLimitingWindow;
@Override
public String toString() {
return "expirationTime=" + expirationTimeElapsed + ", "
- + "windowSize=" + windowSizeMs + ", "
+ + "windowSizeMs=" + windowSizeMs + ", "
+ + "jobCountLimit=" + jobCountLimit + ", "
+ + "sessionCountLimit=" + sessionCountLimit + ", "
+ "executionTimeInWindow=" + executionTimeInWindowMs + ", "
+ "bgJobCountInWindow=" + bgJobCountInWindow + ", "
+ "executionTimeInMaxPeriod=" + executionTimeInMaxPeriodMs + ", "
+ "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", "
+ "sessionCountInWindow=" + sessionCountInWindow + ", "
- + "quotaCutoffTime=" + quotaCutoffTimeElapsed + ", "
- + "jobCountExpirationTime=" + jobCountExpirationTimeElapsed + ", "
- + "jobCountInAllowedTime=" + jobCountInAllowedTime + ", "
- + "sessionCountExpirationTime=" + sessionCountExpirationTimeElapsed + ", "
- + "sessionCountInAllowedTime=" + sessionCountInAllowedTime;
+ + "inQuotaTime=" + inQuotaTimeElapsed + ", "
+ + "jobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", "
+ + "jobCountInRateLimitingWindow=" + jobCountInRateLimitingWindow + ", "
+ + "sessionCountExpirationTime=" + sessionRateLimitExpirationTimeElapsed + ", "
+ + "sessionCountInRateLimitingWindow=" + sessionCountInRateLimitingWindow;
}
@Override
@@ -316,18 +335,21 @@
ExecutionStats other = (ExecutionStats) obj;
return this.expirationTimeElapsed == other.expirationTimeElapsed
&& this.windowSizeMs == other.windowSizeMs
+ && this.jobCountLimit == other.jobCountLimit
+ && this.sessionCountLimit == other.sessionCountLimit
&& this.executionTimeInWindowMs == other.executionTimeInWindowMs
&& this.bgJobCountInWindow == other.bgJobCountInWindow
&& this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs
&& this.sessionCountInWindow == other.sessionCountInWindow
&& this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod
- && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed
- && this.jobCountExpirationTimeElapsed == other.jobCountExpirationTimeElapsed
- && this.jobCountInAllowedTime == other.jobCountInAllowedTime
- && this.sessionCountExpirationTimeElapsed
- == other.sessionCountExpirationTimeElapsed
- && this.sessionCountInAllowedTime
- == other.sessionCountInAllowedTime;
+ && this.inQuotaTimeElapsed == other.inQuotaTimeElapsed
+ && this.jobRateLimitExpirationTimeElapsed
+ == other.jobRateLimitExpirationTimeElapsed
+ && this.jobCountInRateLimitingWindow == other.jobCountInRateLimitingWindow
+ && this.sessionRateLimitExpirationTimeElapsed
+ == other.sessionRateLimitExpirationTimeElapsed
+ && this.sessionCountInRateLimitingWindow
+ == other.sessionCountInRateLimitingWindow;
} else {
return false;
}
@@ -338,16 +360,18 @@
int result = 0;
result = 31 * result + hashLong(expirationTimeElapsed);
result = 31 * result + hashLong(windowSizeMs);
+ result = 31 * result + hashLong(jobCountLimit);
+ result = 31 * result + hashLong(sessionCountLimit);
result = 31 * result + hashLong(executionTimeInWindowMs);
result = 31 * result + bgJobCountInWindow;
result = 31 * result + hashLong(executionTimeInMaxPeriodMs);
result = 31 * result + bgJobCountInMaxPeriod;
result = 31 * result + sessionCountInWindow;
- result = 31 * result + hashLong(quotaCutoffTimeElapsed);
- result = 31 * result + hashLong(jobCountExpirationTimeElapsed);
- result = 31 * result + jobCountInAllowedTime;
- result = 31 * result + hashLong(sessionCountExpirationTimeElapsed);
- result = 31 * result + sessionCountInAllowedTime;
+ result = 31 * result + hashLong(inQuotaTimeElapsed);
+ result = 31 * result + hashLong(jobRateLimitExpirationTimeElapsed);
+ result = 31 * result + jobCountInRateLimitingWindow;
+ result = 31 * result + hashLong(sessionRateLimitExpirationTimeElapsed);
+ result = 31 * result + sessionCountInRateLimitingWindow;
return result;
}
}
@@ -399,19 +423,19 @@
private boolean mShouldThrottle;
/** How much time each app will have to run jobs within their standby bucket window. */
- private long mAllowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
+ private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
/**
* The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS}
* window.
*/
- private long mMaxExecutionTimeMs = 4 * 60 * MINUTE_IN_MILLIS;
+ private long mMaxExecutionTimeMs = QcConstants.DEFAULT_MAX_EXECUTION_TIME_MS;
/**
* How much time the app should have before transitioning from out-of-quota to in-quota.
* This should not affect processing if the app is already in-quota.
*/
- private long mQuotaBufferMs = 30 * 1000L; // 30 seconds
+ private long mQuotaBufferMs = QcConstants.DEFAULT_IN_QUOTA_BUFFER_MS;
/**
* {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine
@@ -425,14 +449,19 @@
*/
private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
- /** The maximum number of jobs that can run within the past {@link #mAllowedTimePerPeriodMs}. */
- private int mMaxJobCountPerAllowedTime = 20;
+ /** The period of time used to rate limit recently run jobs. */
+ private long mRateLimitingWindowMs = QcConstants.DEFAULT_RATE_LIMITING_WINDOW_MS;
+
+ /** The maximum number of jobs that can run within the past {@link #mRateLimitingWindowMs}. */
+ private int mMaxJobCountPerRateLimitingWindow =
+ QcConstants.DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
/**
* The maximum number of {@link TimingSession}s that can run within the past {@link
- * #mAllowedTimePerPeriodMs}.
+ * #mRateLimitingWindowMs}.
*/
- private int mMaxSessionCountPerAllowedTime = 20;
+ private int mMaxSessionCountPerRateLimitingWindow =
+ QcConstants.DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
private long mNextCleanupTimeElapsed = 0;
private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener =
@@ -486,11 +515,11 @@
* The rolling window size for each standby bucket. Within each window, an app will have 10
* minutes to run its jobs.
*/
- private final long[] mBucketPeriodsMs = new long[] {
- 10 * MINUTE_IN_MILLIS, // 10 minutes for ACTIVE -- ACTIVE apps can run jobs at any time
- 2 * 60 * MINUTE_IN_MILLIS, // 2 hours for WORKING
- 8 * 60 * MINUTE_IN_MILLIS, // 8 hours for FREQUENT
- 24 * 60 * MINUTE_IN_MILLIS // 24 hours for RARE
+ private final long[] mBucketPeriodsMs = new long[]{
+ QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS,
+ QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS,
+ QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS,
+ QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS
};
/** The maximum period any bucket can have. */
@@ -503,16 +532,13 @@
*
* @see #mBucketPeriodsMs
*/
- private final int[] mMaxBucketJobCounts = new int[] {
- 200, // ACTIVE -- 1200/hr
- 1200, // WORKING -- 600/hr
- 1800, // FREQUENT -- 225/hr
- 2400 // RARE -- 100/hr
+ private final int[] mMaxBucketJobCounts = new int[]{
+ QcConstants.DEFAULT_MAX_JOB_COUNT_ACTIVE,
+ QcConstants.DEFAULT_MAX_JOB_COUNT_WORKING,
+ QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT,
+ QcConstants.DEFAULT_MAX_JOB_COUNT_RARE
};
- /** The minimum number of jobs that any bucket will be allowed to run. */
- private static final int MIN_BUCKET_JOB_COUNT = 100;
-
/**
* The maximum number of {@link TimingSession}s based on its standby bucket. For each max value
* count in the array, the app will not be allowed to have more than that many number of
@@ -527,14 +553,12 @@
QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE
};
- /** The minimum number of {@link TimingSession}s that any bucket will be allowed to run. */
- private static final int MIN_BUCKET_SESSION_COUNT = 3;
-
/**
* Treat two distinct {@link TimingSession}s as the same if they start and end within this
* amount of time of each other.
*/
- private long mTimingSessionCoalescingDurationMs = 0;
+ private long mTimingSessionCoalescingDurationMs =
+ QcConstants.DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS;
/** An app has reached its quota. The message should contain a {@link Package} object. */
private static final int MSG_REACHED_QUOTA = 0;
@@ -761,8 +785,8 @@
final int standbyBucket) {
final long now = sElapsedRealtimeClock.millis();
final boolean isUnderAllowedTimeQuota =
- (stats.jobCountExpirationTimeElapsed <= now
- || stats.jobCountInAllowedTime < mMaxJobCountPerAllowedTime);
+ (stats.jobRateLimitExpirationTimeElapsed <= now
+ || stats.jobCountInRateLimitingWindow < mMaxJobCountPerRateLimitingWindow);
return isUnderAllowedTimeQuota
&& (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]);
}
@@ -770,10 +794,8 @@
private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
final int standbyBucket) {
final long now = sElapsedRealtimeClock.millis();
- final boolean isUnderAllowedTimeQuota =
- (stats.sessionCountExpirationTimeElapsed <= now
- || stats.sessionCountInAllowedTime
- < mMaxSessionCountPerAllowedTime);
+ final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
+ || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
return isUnderAllowedTimeQuota
&& stats.sessionCountInWindow < mMaxBucketSessionCounts[standbyBucket];
}
@@ -924,12 +946,18 @@
}
if (refreshStatsIfOld) {
final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
+ final int jobCountLimit = mMaxBucketJobCounts[standbyBucket];
+ final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket];
Timer timer = mPkgTimers.get(userId, packageName);
if ((timer != null && timer.isActive())
|| stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis()
- || stats.windowSizeMs != bucketWindowSizeMs) {
+ || stats.windowSizeMs != bucketWindowSizeMs
+ || stats.jobCountLimit != jobCountLimit
+ || stats.sessionCountLimit != sessionCountLimit) {
// The stats are no longer valid.
stats.windowSizeMs = bucketWindowSizeMs;
+ stats.jobCountLimit = jobCountLimit;
+ stats.sessionCountLimit = sessionCountLimit;
updateExecutionStatsLocked(userId, packageName, stats);
}
}
@@ -945,7 +973,7 @@
stats.executionTimeInMaxPeriodMs = 0;
stats.bgJobCountInMaxPeriod = 0;
stats.sessionCountInWindow = 0;
- stats.quotaCutoffTimeElapsed = 0;
+ stats.inQuotaTimeElapsed = 0;
Timer timer = mPkgTimers.get(userId, packageName);
final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -958,12 +986,12 @@
// invalidate now.
stats.expirationTimeElapsed = nowElapsed;
if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
- stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
- nowElapsed - mAllowedTimeIntoQuotaMs);
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+ nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs);
}
if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
- stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
- nowElapsed - mMaxExecutionTimeIntoQuotaMs);
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+ nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
}
}
@@ -985,36 +1013,40 @@
TimingSession session = sessions.get(i);
// Window management.
- if (startWindowElapsed < session.startTimeElapsed) {
- stats.executionTimeInWindowMs += session.endTimeElapsed - session.startTimeElapsed;
+ if (startWindowElapsed < session.endTimeElapsed) {
+ final long start;
+ if (startWindowElapsed < session.startTimeElapsed) {
+ start = session.startTimeElapsed;
+ emptyTimeMs =
+ Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed);
+ } else {
+ // The session started before the window but ended within the window. Only
+ // include the portion that was within the window.
+ start = startWindowElapsed;
+ emptyTimeMs = 0;
+ }
+
+ stats.executionTimeInWindowMs += session.endTimeElapsed - start;
stats.bgJobCountInWindow += session.bgJobCount;
- emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed);
if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
- stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
- session.startTimeElapsed + stats.executionTimeInWindowMs
- - mAllowedTimeIntoQuotaMs);
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+ start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs
+ + stats.windowSizeMs);
+ }
+ if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+ session.endTimeElapsed + stats.windowSizeMs);
}
if (i == loopStart
|| (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed)
> mTimingSessionCoalescingDurationMs) {
// Coalesce sessions if they are very close to each other in time
sessionCountInWindow++;
- }
- } else if (startWindowElapsed < session.endTimeElapsed) {
- // The session started before the window but ended within the window. Only include
- // the portion that was within the window.
- stats.executionTimeInWindowMs += session.endTimeElapsed - startWindowElapsed;
- stats.bgJobCountInWindow += session.bgJobCount;
- emptyTimeMs = 0;
- if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
- stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
- startWindowElapsed + stats.executionTimeInWindowMs
- - mAllowedTimeIntoQuotaMs);
- }
- if (i == loopStart
- || (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed)
- > mTimingSessionCoalescingDurationMs) {
- sessionCountInWindow++;
+
+ if (sessionCountInWindow >= stats.sessionCountLimit) {
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+ session.endTimeElapsed + stats.windowSizeMs);
+ }
}
}
@@ -1025,9 +1057,9 @@
stats.bgJobCountInMaxPeriod += session.bgJobCount;
emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startMaxElapsed);
if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
- stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
session.startTimeElapsed + stats.executionTimeInMaxPeriodMs
- - mMaxExecutionTimeIntoQuotaMs);
+ - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
}
} else if (startMaxElapsed < session.endTimeElapsed) {
// The session started before the window but ended within the window. Only include
@@ -1036,9 +1068,9 @@
stats.bgJobCountInMaxPeriod += session.bgJobCount;
emptyTimeMs = 0;
if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
- stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
startMaxElapsed + stats.executionTimeInMaxPeriodMs
- - mMaxExecutionTimeIntoQuotaMs);
+ - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
}
} else {
// This session ended before the window. No point in going any further.
@@ -1094,11 +1126,11 @@
stats = new ExecutionStats();
appStats[i] = stats;
}
- if (stats.jobCountExpirationTimeElapsed <= now) {
- stats.jobCountExpirationTimeElapsed = now + mAllowedTimePerPeriodMs;
- stats.jobCountInAllowedTime = 0;
+ if (stats.jobRateLimitExpirationTimeElapsed <= now) {
+ stats.jobRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs;
+ stats.jobCountInRateLimitingWindow = 0;
}
- stats.jobCountInAllowedTime += count;
+ stats.jobCountInRateLimitingWindow += count;
}
}
@@ -1115,11 +1147,11 @@
stats = new ExecutionStats();
appStats[i] = stats;
}
- if (stats.sessionCountExpirationTimeElapsed <= now) {
- stats.sessionCountExpirationTimeElapsed = now + mAllowedTimePerPeriodMs;
- stats.sessionCountInAllowedTime = 0;
+ if (stats.sessionRateLimitExpirationTimeElapsed <= now) {
+ stats.sessionRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs;
+ stats.sessionCountInRateLimitingWindow = 0;
}
- stats.sessionCountInAllowedTime++;
+ stats.sessionCountInRateLimitingWindow++;
}
}
@@ -1367,18 +1399,17 @@
}
// The time this app will have quota again.
- long inQuotaTimeElapsed = stats.quotaCutoffTimeElapsed + stats.windowSizeMs;
- if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeMs) {
+ long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
+ if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
+ // App hit the rate limit.
inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
- stats.quotaCutoffTimeElapsed + MAX_PERIOD_MS);
+ stats.jobRateLimitExpirationTimeElapsed);
}
- if (!isUnderJobCountQuota) {
+ if (!isUnderTimingSessionCountQuota
+ && stats.sessionCountInWindow < stats.sessionCountLimit) {
+ // App hit the rate limit.
inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
- stats.jobCountExpirationTimeElapsed + mAllowedTimePerPeriodMs);
- }
- if (!isUnderTimingSessionCountQuota) {
- inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
- stats.sessionCountExpirationTimeElapsed + mAllowedTimePerPeriodMs);
+ stats.sessionRateLimitExpirationTimeElapsed);
}
// Only schedule the alarm if:
// 1. There isn't one currently scheduled
@@ -1399,7 +1430,7 @@
ALARM_TAG_QUOTA_CHECK, alarmListener, mHandler);
alarmListener.setTriggerTime(inQuotaTimeElapsed);
} else if (DEBUG) {
- Slog.d(TAG, "No need to scheduling start alarm for " + pkgString);
+ Slog.d(TAG, "No need to schedule start alarm for " + pkgString);
}
}
@@ -1968,14 +1999,15 @@
private static final String KEY_MAX_JOB_COUNT_WORKING = "max_job_count_working";
private static final String KEY_MAX_JOB_COUNT_FREQUENT = "max_job_count_frequent";
private static final String KEY_MAX_JOB_COUNT_RARE = "max_job_count_rare";
- private static final String KEY_MAX_JOB_COUNT_PER_ALLOWED_TIME =
- "max_count_per_allowed_time";
+ private static final String KEY_RATE_LIMITING_WINDOW_MS = "rate_limiting_window_ms";
+ private static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
+ "max_job_count_per_rate_limiting_window";
private static final String KEY_MAX_SESSION_COUNT_ACTIVE = "max_session_count_active";
private static final String KEY_MAX_SESSION_COUNT_WORKING = "max_session_count_working";
private static final String KEY_MAX_SESSION_COUNT_FREQUENT = "max_session_count_frequent";
private static final String KEY_MAX_SESSION_COUNT_RARE = "max_session_count_rare";
- private static final String KEY_MAX_SESSION_COUNT_PER_ALLOWED_TIME =
- "max_session_count_per_allowed_time";
+ private static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
+ "max_session_count_per_rate_limiting_window";
private static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS =
"timing_session_coalescing_duration_ms";
@@ -1984,7 +2016,7 @@
private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
30 * 1000L; // 30 seconds
private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS =
- 10 * 60 * 1000L; // 10 minutes for ACTIVE -- ACTIVE apps can run jobs at any time
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time
private static final long DEFAULT_WINDOW_SIZE_WORKING_MS =
2 * 60 * 60 * 1000L; // 2 hours
private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS =
@@ -1992,16 +2024,18 @@
private static final long DEFAULT_WINDOW_SIZE_RARE_MS =
24 * 60 * 60 * 1000L; // 24 hours
private static final long DEFAULT_MAX_EXECUTION_TIME_MS =
- 4 * 60 * 60 * 1000L; // 4 hours
- private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE =
- 200; // 1200/hr
- private static final int DEFAULT_MAX_JOB_COUNT_WORKING =
- 1200; // 600/hr
- private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT =
- 1800; // 225/hr
- private static final int DEFAULT_MAX_JOB_COUNT_RARE =
- 2400; // 100/hr
- private static final int DEFAULT_MAX_JOB_COUNT_PER_ALLOWED_TIME = 20;
+ 4 * HOUR_IN_MILLIS;
+ private static final long DEFAULT_RATE_LIMITING_WINDOW_MS =
+ 10 * MINUTE_IN_MILLIS;
+ private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20;
+ private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = // 20/window = 120/hr = 1/session
+ DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
+ private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session
+ (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
+ private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session
+ (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS);
+ private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
+ (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
20; // 120/hr
private static final int DEFAULT_MAX_SESSION_COUNT_WORKING =
@@ -2010,8 +2044,8 @@
8; // 1/hr
private static final int DEFAULT_MAX_SESSION_COUNT_RARE =
3; // .125/hr
- private static final int DEFAULT_MAX_SESSION_COUNT_PER_ALLOWED_TIME = 20;
- private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 0;
+ private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20;
+ private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds
/** How much time each app will have to run jobs within their standby bucket window. */
public long ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
@@ -2079,11 +2113,14 @@
*/
public int MAX_JOB_COUNT_RARE = DEFAULT_MAX_JOB_COUNT_RARE;
+ /** The period of time used to rate limit recently run jobs. */
+ public long RATE_LIMITING_WINDOW_MS = DEFAULT_RATE_LIMITING_WINDOW_MS;
+
/**
- * The maximum number of jobs that can run within the past
- * {@link #ALLOWED_TIME_PER_PERIOD_MS}.
+ * The maximum number of jobs that can run within the past {@link #RATE_LIMITING_WINDOW_MS}.
*/
- public int MAX_JOB_COUNT_PER_ALLOWED_TIME = DEFAULT_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+ public int MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
+ DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
/**
* The maximum number of {@link TimingSession}s an app can run within this particular
@@ -2113,7 +2150,8 @@
* The maximum number of {@link TimingSession}s that can run within the past
* {@link #ALLOWED_TIME_PER_PERIOD_MS}.
*/
- public int MAX_SESSION_COUNT_PER_ALLOWED_TIME = DEFAULT_MAX_SESSION_COUNT_PER_ALLOWED_TIME;
+ public int MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
+ DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
/**
* Treat two distinct {@link TimingSession}s as the same if they start and end within this
@@ -2122,6 +2160,29 @@
public long TIMING_SESSION_COALESCING_DURATION_MS =
DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS;
+ // Safeguards
+
+ /** The minimum number of jobs that any bucket will be allowed to run within its window. */
+ private static final int MIN_BUCKET_JOB_COUNT = 10;
+
+ /**
+ * The minimum number of {@link TimingSession}s that any bucket will be allowed to run
+ * within its window.
+ */
+ private static final int MIN_BUCKET_SESSION_COUNT = 1;
+
+ /** The minimum value that {@link #MAX_EXECUTION_TIME_MS} can have. */
+ private static final long MIN_MAX_EXECUTION_TIME_MS = 60 * MINUTE_IN_MILLIS;
+
+ /** The minimum value that {@link #MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW} can have. */
+ private static final int MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 10;
+
+ /** The minimum value that {@link #MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW} can have. */
+ private static final int MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 10;
+
+ /** The minimum value that {@link #RATE_LIMITING_WINDOW_MS} can have. */
+ private static final long MIN_RATE_LIMITING_WINDOW_MS = 30 * SECOND_IN_MILLIS;
+
QcConstants(Handler handler) {
super(handler);
}
@@ -2167,8 +2228,11 @@
KEY_MAX_JOB_COUNT_FREQUENT, DEFAULT_MAX_JOB_COUNT_FREQUENT);
MAX_JOB_COUNT_RARE = mParser.getInt(
KEY_MAX_JOB_COUNT_RARE, DEFAULT_MAX_JOB_COUNT_RARE);
- MAX_JOB_COUNT_PER_ALLOWED_TIME = mParser.getInt(
- KEY_MAX_JOB_COUNT_PER_ALLOWED_TIME, DEFAULT_MAX_JOB_COUNT_PER_ALLOWED_TIME);
+ RATE_LIMITING_WINDOW_MS = mParser.getLong(
+ KEY_RATE_LIMITING_WINDOW_MS, DEFAULT_RATE_LIMITING_WINDOW_MS);
+ MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
+ KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+ DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
MAX_SESSION_COUNT_ACTIVE = mParser.getInt(
KEY_MAX_SESSION_COUNT_ACTIVE, DEFAULT_MAX_SESSION_COUNT_ACTIVE);
MAX_SESSION_COUNT_WORKING = mParser.getInt(
@@ -2177,9 +2241,9 @@
KEY_MAX_SESSION_COUNT_FREQUENT, DEFAULT_MAX_SESSION_COUNT_FREQUENT);
MAX_SESSION_COUNT_RARE = mParser.getInt(
KEY_MAX_SESSION_COUNT_RARE, DEFAULT_MAX_SESSION_COUNT_RARE);
- MAX_SESSION_COUNT_PER_ALLOWED_TIME = mParser.getInt(
- KEY_MAX_SESSION_COUNT_PER_ALLOWED_TIME,
- DEFAULT_MAX_SESSION_COUNT_PER_ALLOWED_TIME);
+ MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
+ KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+ DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
TIMING_SESSION_COALESCING_DURATION_MS = mParser.getLong(
KEY_TIMING_SESSION_COALESCING_DURATION_MS,
DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS);
@@ -2192,7 +2256,14 @@
synchronized (mLock) {
boolean changed = false;
- long newAllowedTimeMs = Math.min(MAX_PERIOD_MS,
+ long newMaxExecutionTimeMs = Math.max(MIN_MAX_EXECUTION_TIME_MS,
+ Math.min(MAX_PERIOD_MS, MAX_EXECUTION_TIME_MS));
+ if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) {
+ mMaxExecutionTimeMs = newMaxExecutionTimeMs;
+ mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+ changed = true;
+ }
+ long newAllowedTimeMs = Math.min(mMaxExecutionTimeMs,
Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_MS));
if (mAllowedTimePerPeriodMs != newAllowedTimeMs) {
mAllowedTimePerPeriodMs = newAllowedTimeMs;
@@ -2231,47 +2302,44 @@
mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
changed = true;
}
- long newMaxExecutionTimeMs = Math.max(60 * MINUTE_IN_MILLIS,
- Math.min(MAX_PERIOD_MS, MAX_EXECUTION_TIME_MS));
- if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) {
- mMaxExecutionTimeMs = newMaxExecutionTimeMs;
- mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+ long newRateLimitingWindowMs = Math.min(MAX_PERIOD_MS,
+ Math.max(MIN_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS));
+ if (mRateLimitingWindowMs != newRateLimitingWindowMs) {
+ mRateLimitingWindowMs = newRateLimitingWindowMs;
changed = true;
}
- int newMaxCountPerAllowedPeriod = Math.max(10,
- MAX_JOB_COUNT_PER_ALLOWED_TIME);
- if (mMaxJobCountPerAllowedTime != newMaxCountPerAllowedPeriod) {
- mMaxJobCountPerAllowedTime = newMaxCountPerAllowedPeriod;
+ int newMaxJobCountPerRateLimitingWindow = Math.max(
+ MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+ MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
+ if (mMaxJobCountPerRateLimitingWindow != newMaxJobCountPerRateLimitingWindow) {
+ mMaxJobCountPerRateLimitingWindow = newMaxJobCountPerRateLimitingWindow;
changed = true;
}
- int newActiveMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
- Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE));
+ int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE);
if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) {
mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount;
changed = true;
}
- int newWorkingMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
- Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_WORKING));
+ int newWorkingMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_WORKING);
if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) {
mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount;
changed = true;
}
- int newFrequentMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
- Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_FREQUENT));
+ int newFrequentMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_FREQUENT);
if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) {
mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount;
changed = true;
}
- int newRareMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
- Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RARE));
+ int newRareMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RARE);
if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) {
mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
changed = true;
}
- int newMaxSessionCountPerAllowedPeriod = Math.max(10,
- MAX_SESSION_COUNT_PER_ALLOWED_TIME);
- if (mMaxSessionCountPerAllowedTime != newMaxSessionCountPerAllowedPeriod) {
- mMaxSessionCountPerAllowedTime = newMaxSessionCountPerAllowedPeriod;
+ int newMaxSessionCountPerRateLimitPeriod = Math.max(
+ MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+ MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
+ if (mMaxSessionCountPerRateLimitingWindow != newMaxSessionCountPerRateLimitPeriod) {
+ mMaxSessionCountPerRateLimitingWindow = newMaxSessionCountPerRateLimitPeriod;
changed = true;
}
int newActiveMaxSessionCount =
@@ -2332,14 +2400,15 @@
pw.printPair(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println();
pw.printPair(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println();
pw.printPair(KEY_MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE).println();
- pw.printPair(KEY_MAX_JOB_COUNT_PER_ALLOWED_TIME, MAX_JOB_COUNT_PER_ALLOWED_TIME)
- .println();
+ pw.printPair(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println();
+ pw.printPair(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+ MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println();
pw.printPair(KEY_MAX_SESSION_COUNT_ACTIVE, MAX_SESSION_COUNT_ACTIVE).println();
pw.printPair(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println();
pw.printPair(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println();
pw.printPair(KEY_MAX_SESSION_COUNT_RARE, MAX_SESSION_COUNT_RARE).println();
- pw.printPair(KEY_MAX_SESSION_COUNT_PER_ALLOWED_TIME, MAX_SESSION_COUNT_PER_ALLOWED_TIME)
- .println();
+ pw.printPair(KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+ MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW).println();
pw.printPair(KEY_TIMING_SESSION_COALESCING_DURATION_MS,
TIMING_SESSION_COALESCING_DURATION_MS).println();
pw.decreaseIndent();
@@ -2365,8 +2434,10 @@
proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT,
MAX_JOB_COUNT_FREQUENT);
proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE);
- proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_ALLOWED_TIME,
- MAX_JOB_COUNT_PER_ALLOWED_TIME);
+ proto.write(ConstantsProto.QuotaController.RATE_LIMITING_WINDOW_MS,
+ RATE_LIMITING_WINDOW_MS);
+ proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+ MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_ACTIVE,
MAX_SESSION_COUNT_ACTIVE);
proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_WORKING,
@@ -2375,8 +2446,8 @@
MAX_SESSION_COUNT_FREQUENT);
proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RARE,
MAX_SESSION_COUNT_RARE);
- proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_ALLOWED_TIME,
- MAX_SESSION_COUNT_PER_ALLOWED_TIME);
+ proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+ MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
proto.write(ConstantsProto.QuotaController.TIMING_SESSION_COALESCING_DURATION_MS,
TIMING_SESSION_COALESCING_DURATION_MS);
proto.end(qcToken);
@@ -2431,8 +2502,18 @@
}
@VisibleForTesting
- int getMaxJobCountPerAllowedTime() {
- return mMaxJobCountPerAllowedTime;
+ int getMaxJobCountPerRateLimitingWindow() {
+ return mMaxJobCountPerRateLimitingWindow;
+ }
+
+ @VisibleForTesting
+ int getMaxSessionCountPerRateLimitingWindow() {
+ return mMaxSessionCountPerRateLimitingWindow;
+ }
+
+ @VisibleForTesting
+ long getRateLimitingWindowMs() {
+ return mRateLimitingWindowMs;
}
@VisibleForTesting
@@ -2441,11 +2522,6 @@
}
@VisibleForTesting
- int getMaxSessionCountPerAllowedTime() {
- return mMaxSessionCountPerAllowedTime;
- }
-
- @VisibleForTesting
@Nullable
List<TimingSession> getTimingSessions(int userId, String packageName) {
return mTimingSessions.get(userId, packageName);
@@ -2659,6 +2735,12 @@
StateControllerProto.QuotaController.ExecutionStats.WINDOW_SIZE_MS,
es.windowSizeMs);
proto.write(
+ StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_LIMIT,
+ es.jobCountLimit);
+ proto.write(
+ StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_LIMIT,
+ es.sessionCountLimit);
+ proto.write(
StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_WINDOW_MS,
es.executionTimeInWindowMs);
proto.write(
@@ -2674,20 +2756,20 @@
StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_WINDOW,
es.sessionCountInWindow);
proto.write(
- StateControllerProto.QuotaController.ExecutionStats.QUOTA_CUTOFF_TIME_ELAPSED,
- es.quotaCutoffTimeElapsed);
+ StateControllerProto.QuotaController.ExecutionStats.IN_QUOTA_TIME_ELAPSED,
+ es.inQuotaTimeElapsed);
proto.write(
StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_EXPIRATION_TIME_ELAPSED,
- es.jobCountExpirationTimeElapsed);
+ es.jobRateLimitExpirationTimeElapsed);
proto.write(
- StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_IN_ALLOWED_TIME,
- es.jobCountInAllowedTime);
+ StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_IN_RATE_LIMITING_WINDOW,
+ es.jobCountInRateLimitingWindow);
proto.write(
StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_EXPIRATION_TIME_ELAPSED,
- es.sessionCountExpirationTimeElapsed);
+ es.sessionRateLimitExpirationTimeElapsed);
proto.write(
- StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_ALLOWED_TIME,
- es.sessionCountInAllowedTime);
+ StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_RATE_LIMITING_WINDOW,
+ es.sessionCountInRateLimitingWindow);
proto.end(esToken);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 4e89357..1db6b8e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -386,6 +386,8 @@
ExecutionStats expectedStats = new ExecutionStats();
expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
final int uid = 10001;
mQuotaController.onAppRemovedLocked("com.android.test.remove", uid);
@@ -424,6 +426,8 @@
ExecutionStats expectedStats = new ExecutionStats();
expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
mQuotaController.onUserRemovedLocked(0);
assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
@@ -456,6 +460,8 @@
ExecutionStats inputStats = new ExecutionStats();
inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
+ inputStats.jobCountLimit = expectedStats.jobCountLimit = 100;
+ inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100;
// Invalid time is now +24 hours since there are no sessions at all for the app.
expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
@@ -520,6 +526,7 @@
assertEquals(expectedStats, inputStats);
inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
+ inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 2;
// Invalid time is now since the start of the session is at the very edge of the window
// cutoff time.
expectedStats.expirationTimeElapsed = now;
@@ -528,10 +535,13 @@
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInMaxPeriod = 15;
expectedStats.sessionCountInWindow = 3;
+ expectedStats.inQuotaTimeElapsed = now + 11 * MINUTE_IN_MILLIS;
mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
assertEquals(expectedStats, inputStats);
inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+ inputStats.jobCountLimit = expectedStats.jobCountLimit = 6;
+ inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100;
// Invalid time is now since the session straddles the window cutoff time.
expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
@@ -539,8 +549,7 @@
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInMaxPeriod = 15;
expectedStats.sessionCountInWindow = 4;
- expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
- + mQcConstants.IN_QUOTA_BUFFER_MS;
+ expectedStats.inQuotaTimeElapsed = now + 5 * MINUTE_IN_MILLIS;
mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
assertEquals(expectedStats, inputStats);
@@ -553,8 +562,9 @@
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInMaxPeriod = 15;
expectedStats.sessionCountInWindow = 4;
- expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
- + mQcConstants.IN_QUOTA_BUFFER_MS;
+ // App goes under job execution time limit in ~61 minutes, but will be under job count limit
+ // in 65 minutes.
+ expectedStats.inQuotaTimeElapsed = now + 65 * MINUTE_IN_MILLIS;
mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
assertEquals(expectedStats, inputStats);
@@ -567,8 +577,7 @@
expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInMaxPeriod = 15;
expectedStats.sessionCountInWindow = 5;
- expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
- + mQcConstants.IN_QUOTA_BUFFER_MS;
+ expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS + 5 * MINUTE_IN_MILLIS;
mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
assertEquals(expectedStats, inputStats);
@@ -577,6 +586,7 @@
.add(0,
createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+ inputStats.jobCountLimit = expectedStats.jobCountLimit = 100;
// Invalid time is now +1 hour since the earliest session in the max period is 1 hour
// before the end of the max period cutoff time.
expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
@@ -585,7 +595,7 @@
expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInMaxPeriod = 18;
expectedStats.sessionCountInWindow = 5;
- expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+ expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS
+ mQcConstants.IN_QUOTA_BUFFER_MS;
mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
assertEquals(expectedStats, inputStats);
@@ -602,7 +612,7 @@
expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInMaxPeriod = 20;
expectedStats.sessionCountInWindow = 5;
- expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+ expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS
+ mQcConstants.IN_QUOTA_BUFFER_MS;
mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
assertEquals(expectedStats, inputStats);
@@ -627,6 +637,8 @@
// Active
expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 5;
@@ -638,45 +650,103 @@
// Working
expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
expectedStats.expirationTimeElapsed = now;
expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 10;
expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInMaxPeriod = 20;
expectedStats.sessionCountInWindow = 2;
- expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+ expectedStats.inQuotaTimeElapsed = now + 3 * MINUTE_IN_MILLIS
+ mQcConstants.IN_QUOTA_BUFFER_MS;
assertEquals(expectedStats,
mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
// Frequent
expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 15;
expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInMaxPeriod = 20;
expectedStats.sessionCountInWindow = 3;
- expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+ expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
+ mQcConstants.IN_QUOTA_BUFFER_MS;
assertEquals(expectedStats,
mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
// Rare
expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 20;
expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
expectedStats.bgJobCountInMaxPeriod = 20;
expectedStats.sessionCountInWindow = 4;
- expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+ expectedStats.inQuotaTimeElapsed = now + 22 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
+ mQcConstants.IN_QUOTA_BUFFER_MS;
assertEquals(expectedStats,
mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
}
/**
+ * Tests that getExecutionStatsLocked returns the correct stats soon after device startup.
+ */
+ @Test
+ public void testGetExecutionStatsLocked_Values_BeginningOfTime() {
+ // Set time to 3 minutes after boot.
+ advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
+ advanceElapsedClock(3 * MINUTE_IN_MILLIS);
+
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2));
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ // Active
+ expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+ expectedStats.expirationTimeElapsed = 11 * MINUTE_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 2;
+ expectedStats.executionTimeInMaxPeriodMs = MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 2;
+ expectedStats.sessionCountInWindow = 1;
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
+
+ // Working
+ expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
+ expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
+
+ // Frequent
+ expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
+ expectedStats.expirationTimeElapsed = 8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
+
+ // Rare
+ expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
+ expectedStats.expirationTimeElapsed = 24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
+ }
+
+ /**
* Tests that getExecutionStatsLocked returns the correct timing session stats when coalescing.
*/
@Test
@@ -850,13 +920,15 @@
ExecutionStats expectedStats = new ExecutionStats();
expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
+ expectedStats.jobCountLimit = originalStatsActive.jobCountLimit;
+ expectedStats.sessionCountLimit = originalStatsActive.sessionCountLimit;
expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed;
expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod;
expectedStats.sessionCountInWindow = originalStatsActive.sessionCountInWindow;
- expectedStats.quotaCutoffTimeElapsed = originalStatsActive.quotaCutoffTimeElapsed;
+ expectedStats.inQuotaTimeElapsed = originalStatsActive.inQuotaTimeElapsed;
final ExecutionStats newStatsActive = mQuotaController.getExecutionStatsLocked(0,
"com.android.test", ACTIVE_INDEX);
// Stats for the same bucket should use the same object.
@@ -864,33 +936,39 @@
assertEquals(expectedStats, newStatsActive);
expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
+ expectedStats.jobCountLimit = originalStatsWorking.jobCountLimit;
+ expectedStats.sessionCountLimit = originalStatsWorking.sessionCountLimit;
expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed;
expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
expectedStats.sessionCountInWindow = originalStatsWorking.sessionCountInWindow;
- expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
+ expectedStats.inQuotaTimeElapsed = originalStatsWorking.inQuotaTimeElapsed;
final ExecutionStats newStatsWorking = mQuotaController.getExecutionStatsLocked(0,
"com.android.test", WORKING_INDEX);
assertTrue(originalStatsWorking == newStatsWorking);
assertNotEquals(expectedStats, newStatsWorking);
expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
+ expectedStats.jobCountLimit = originalStatsFrequent.jobCountLimit;
+ expectedStats.sessionCountLimit = originalStatsFrequent.sessionCountLimit;
expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed;
expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
expectedStats.sessionCountInWindow = originalStatsFrequent.sessionCountInWindow;
- expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
+ expectedStats.inQuotaTimeElapsed = originalStatsFrequent.inQuotaTimeElapsed;
final ExecutionStats newStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
"com.android.test", FREQUENT_INDEX);
assertTrue(originalStatsFrequent == newStatsFrequent);
assertNotEquals(expectedStats, newStatsFrequent);
expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
+ expectedStats.jobCountLimit = originalStatsRare.jobCountLimit;
+ expectedStats.sessionCountLimit = originalStatsRare.sessionCountLimit;
expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed;
expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
expectedStats.sessionCountInWindow = originalStatsRare.sessionCountInWindow;
- expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
+ expectedStats.inQuotaTimeElapsed = originalStatsRare.inQuotaTimeElapsed;
final ExecutionStats newStatsRare = mQuotaController.getExecutionStatsLocked(0,
"com.android.test", RARE_INDEX);
assertTrue(originalStatsRare == newStatsRare);
@@ -1065,7 +1143,7 @@
public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME;
+ final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
mQuotaController.saveTimingSession(0, "com.android.test.spam",
createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
mQuotaController.saveTimingSession(0, "com.android.test.spam",
@@ -1100,7 +1178,7 @@
public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME;
+ final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
mQuotaController.saveTimingSession(0, "com.android.test",
@@ -1141,7 +1219,7 @@
advanceElapsedClock(10 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS);
assertEquals(2, mQuotaController.getExecutionStatsLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX).jobCountInAllowedTime);
+ SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX).jobCountInRateLimitingWindow);
assertTrue(mQuotaController.isWithinQuotaLocked(jobStatus));
}
@@ -1212,9 +1290,9 @@
mQuotaController.getTimingSessions(SOURCE_USER_ID, fgChangerPkgName).size());
for (int i = ACTIVE_INDEX; i < RARE_INDEX; ++i) {
assertEquals(42, mQuotaController.getExecutionStatsLocked(
- SOURCE_USER_ID, fgChangerPkgName, i).jobCountInAllowedTime);
+ SOURCE_USER_ID, fgChangerPkgName, i).jobCountInRateLimitingWindow);
assertEquals(1, mQuotaController.getExecutionStatsLocked(
- SOURCE_USER_ID, unaffectedPkgName, i).jobCountInAllowedTime);
+ SOURCE_USER_ID, unaffectedPkgName, i).jobCountInRateLimitingWindow);
}
}
@@ -1555,26 +1633,29 @@
}
@Test
- public void testMaybeScheduleStartAlarmLocked_JobCount_AllowedTime() {
+ public void testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow() {
+ // Set rate limiting period different from allowed time to confirm code sets based on
+ // the former.
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 10 * MINUTE_IN_MILLIS;
+ mQcConstants.RATE_LIMITING_WINDOW_MS = 5 * MINUTE_IN_MILLIS;
+ mQcConstants.updateConstants();
+
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int standbyBucket = WORKING_INDEX;
ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
SOURCE_PACKAGE, standbyBucket);
- stats.jobCountInAllowedTime =
- mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME + 2;
+ stats.jobCountInRateLimitingWindow =
+ mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW + 2;
// Invalid time in the past, so the count shouldn't be used.
- stats.jobCountExpirationTimeElapsed =
- now - mQuotaController.getAllowedTimePerPeriodMs() / 2;
+ stats.jobRateLimitExpirationTimeElapsed = now - 5 * MINUTE_IN_MILLIS / 2;
mQuotaController.maybeScheduleStartAlarmLocked(
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
- // Invalid time in the future, so the count should be used.
- stats.jobCountExpirationTimeElapsed =
- now + mQuotaController.getAllowedTimePerPeriodMs() / 2;
- final long expectedWorkingAlarmTime =
- stats.jobCountExpirationTimeElapsed + mQuotaController.getAllowedTimePerPeriodMs();
+ // Valid time in the future, so the count should be used.
+ stats.jobRateLimitExpirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS / 2;
+ final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed;
mQuotaController.maybeScheduleStartAlarmLocked(
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
verify(mAlarmManager, times(1))
@@ -1732,12 +1813,13 @@
mQcConstants.MAX_JOB_COUNT_WORKING = 4000;
mQcConstants.MAX_JOB_COUNT_FREQUENT = 3000;
mQcConstants.MAX_JOB_COUNT_RARE = 2000;
- mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME = 500;
+ mQcConstants.RATE_LIMITING_WINDOW_MS = 15 * MINUTE_IN_MILLIS;
+ mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 500;
mQcConstants.MAX_SESSION_COUNT_ACTIVE = 500;
mQcConstants.MAX_SESSION_COUNT_WORKING = 400;
mQcConstants.MAX_SESSION_COUNT_FREQUENT = 300;
mQcConstants.MAX_SESSION_COUNT_RARE = 200;
- mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME = 50;
+ mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 50;
mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 10 * SECOND_IN_MILLIS;
mQcConstants.updateConstants();
@@ -1750,12 +1832,13 @@
mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
- assertEquals(500, mQuotaController.getMaxJobCountPerAllowedTime());
+ assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
+ assertEquals(500, mQuotaController.getMaxJobCountPerRateLimitingWindow());
assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
- assertEquals(50, mQuotaController.getMaxSessionCountPerAllowedTime());
+ assertEquals(50, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
assertEquals(500, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
assertEquals(400, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
assertEquals(300, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
@@ -1778,12 +1861,13 @@
mQcConstants.MAX_JOB_COUNT_WORKING = 1;
mQcConstants.MAX_JOB_COUNT_FREQUENT = 1;
mQcConstants.MAX_JOB_COUNT_RARE = 1;
- mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME = 0;
+ mQcConstants.RATE_LIMITING_WINDOW_MS = 15 * SECOND_IN_MILLIS;
+ mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 0;
mQcConstants.MAX_SESSION_COUNT_ACTIVE = -1;
- mQcConstants.MAX_SESSION_COUNT_WORKING = 1;
- mQcConstants.MAX_SESSION_COUNT_FREQUENT = 2;
- mQcConstants.MAX_SESSION_COUNT_RARE = 1;
- mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME = 0;
+ mQcConstants.MAX_SESSION_COUNT_WORKING = 0;
+ mQcConstants.MAX_SESSION_COUNT_FREQUENT = -3;
+ mQcConstants.MAX_SESSION_COUNT_RARE = 0;
+ mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 0;
mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = -1;
mQcConstants.updateConstants();
@@ -1795,16 +1879,17 @@
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
- assertEquals(10, mQuotaController.getMaxJobCountPerAllowedTime());
- assertEquals(100, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
- assertEquals(100, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
- assertEquals(100, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
- assertEquals(100, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
- assertEquals(10, mQuotaController.getMaxSessionCountPerAllowedTime());
- assertEquals(3, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
- assertEquals(3, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
- assertEquals(3, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
- assertEquals(3, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]);
+ assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
+ assertEquals(10, mQuotaController.getMaxJobCountPerRateLimitingWindow());
+ assertEquals(10, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
+ assertEquals(10, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
+ assertEquals(10, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
+ assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
+ assertEquals(10, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
+ assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
+ assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
+ assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
+ assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]);
assertEquals(0, mQuotaController.getTimingSessionCoalescingDurationMs());
// Test larger than a day. Controller should cap at one day.
@@ -1815,6 +1900,7 @@
mQcConstants.WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS;
mQcConstants.WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS;
mQcConstants.MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS;
+ mQcConstants.RATE_LIMITING_WINDOW_MS = 25 * HOUR_IN_MILLIS;
mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 25 * HOUR_IN_MILLIS;
mQcConstants.updateConstants();
@@ -1826,6 +1912,7 @@
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
+ assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
assertEquals(15 * MINUTE_IN_MILLIS,
mQuotaController.getTimingSessionCoalescingDurationMs());
}
@@ -2126,7 +2213,7 @@
assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
SOURCE_PACKAGE, standbyBucket);
- assertEquals(0, stats.jobCountInAllowedTime);
+ assertEquals(0, stats.jobCountInRateLimitingWindow);
setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
mQuotaController.prepareForExecutionLocked(jobFg1);
@@ -2138,7 +2225,7 @@
mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false);
assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(0, stats.jobCountInAllowedTime);
+ assertEquals(0, stats.jobCountInRateLimitingWindow);
}
/**
@@ -2154,7 +2241,7 @@
ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
SOURCE_PACKAGE, standbyBucket);
- assertEquals(0, stats.jobCountInAllowedTime);
+ assertEquals(0, stats.jobCountInRateLimitingWindow);
setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
mQuotaController.prepareForExecutionLocked(jobBg1);
@@ -2165,7 +2252,7 @@
advanceElapsedClock(10 * SECOND_IN_MILLIS);
mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
- assertEquals(2, stats.jobCountInAllowedTime);
+ assertEquals(2, stats.jobCountInRateLimitingWindow);
}
/**
@@ -2421,10 +2508,10 @@
/**
* Tests that the start alarm is properly scheduled when a job has been throttled due to the job
- * count quota.
+ * count rate limiting.
*/
@Test
- public void testStartAlarmScheduled_JobCount_AllowedTime() {
+ public void testStartAlarmScheduled_JobCount_RateLimitingWindow() {
// saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
// because it schedules an alarm too. Prevent it from doing so.
spyOn(mQuotaController);
@@ -2432,7 +2519,7 @@
// Essentially disable session throttling.
mQcConstants.MAX_SESSION_COUNT_WORKING =
- mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME = Integer.MAX_VALUE;
+ mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = Integer.MAX_VALUE;
mQcConstants.updateConstants();
final int standbyBucket = WORKING_INDEX;
@@ -2444,7 +2531,7 @@
verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Ran jobs up to the job limit. All of them should be allowed to run.
- for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME; ++i) {
+ for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
JobStatus job = createJobStatus("testStartAlarmScheduled_JobCount_AllowedTime", i);
setStandbyBucket(WORKING_INDEX, job);
mQuotaController.maybeStartTrackingJobLocked(job, null);
@@ -2466,18 +2553,17 @@
ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
SOURCE_PACKAGE, standbyBucket);
- final long expectedWorkingAlarmTime =
- stats.jobCountExpirationTimeElapsed + mQcConstants.ALLOWED_TIME_PER_PERIOD_MS;
+ final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed;
verify(mAlarmManager, times(1))
.set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
}
/**
- * Tests that the start alarm is properly scheduled when a job has been throttled due to the job
- * count quota.
+ * Tests that the start alarm is properly scheduled when a job has been throttled due to the
+ * session count rate limiting.
*/
@Test
- public void testStartAlarmScheduled_TimingSessionCount_AllowedTime() {
+ public void testStartAlarmScheduled_TimingSessionCount_RateLimitingWindow() {
// saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
// because it schedules an alarm too. Prevent it from doing so.
spyOn(mQuotaController);
@@ -2485,10 +2571,10 @@
// Essentially disable job count throttling.
mQcConstants.MAX_JOB_COUNT_FREQUENT =
- mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME = Integer.MAX_VALUE;
- // Make sure throttling is because of COUNT_PER_ALLOWED_TIME.
+ mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = Integer.MAX_VALUE;
+ // Make sure throttling is because of COUNT_PER_RATE_LIMITING_WINDOW.
mQcConstants.MAX_SESSION_COUNT_FREQUENT =
- mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME + 1;
+ mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW + 1;
mQcConstants.updateConstants();
final int standbyBucket = FREQUENT_INDEX;
@@ -2500,7 +2586,7 @@
verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Ran jobs up to the job limit. All of them should be allowed to run.
- for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME; ++i) {
+ for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
JobStatus job = createJobStatus(
"testStartAlarmScheduled_TimingSessionCount_AllowedTime", i);
setStandbyBucket(FREQUENT_INDEX, job);
@@ -2515,7 +2601,7 @@
// Start alarm shouldn't have been scheduled since the app was in quota up until this point.
verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
- // The app is now out of job count quota
+ // The app is now out of session count quota
JobStatus throttledJob = createJobStatus(
"testStartAlarmScheduled_TimingSessionCount_AllowedTime", 42);
mQuotaController.maybeStartTrackingJobLocked(throttledJob, null);
@@ -2523,8 +2609,7 @@
ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
SOURCE_PACKAGE, standbyBucket);
- final long expectedWorkingAlarmTime =
- stats.sessionCountExpirationTimeElapsed + mQcConstants.ALLOWED_TIME_PER_PERIOD_MS;
+ final long expectedWorkingAlarmTime = stats.sessionRateLimitExpirationTimeElapsed;
verify(mAlarmManager, times(1))
.set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
}