Merge "Changing default limits, clarify flags, fix bug." into qt-dev

This commit is contained in:
Kweku Adams
2019-05-29 16:54:52 +00:00
committed by Android (Google) Code Review
3 changed files with 474 additions and 312 deletions

View File

@@ -273,9 +273,11 @@ message ConstantsProto {
// 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 @@ message ConstantsProto {
// 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 @@ message StateControllerProto {
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 {

View File

@@ -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 @@ import java.util.function.Predicate;
/**
* 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 @@ public final class QuotaController extends StateController {
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 final class QuotaController extends StateController {
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 final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
*/
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 @@ public final class QuotaController extends StateController {
* 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 @@ public final class QuotaController extends StateController {
*
* @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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
}
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
// 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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
}
// 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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 final class QuotaController extends StateController {
*/
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 @@ public final class QuotaController extends StateController {
* 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 final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
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 @@ public final class QuotaController extends StateController {
}
@VisibleForTesting
int getMaxJobCountPerAllowedTime() {
return mMaxJobCountPerAllowedTime;
int getMaxJobCountPerRateLimitingWindow() {
return mMaxJobCountPerRateLimitingWindow;
}
@VisibleForTesting
int getMaxSessionCountPerRateLimitingWindow() {
return mMaxSessionCountPerRateLimitingWindow;
}
@VisibleForTesting
long getRateLimitingWindowMs() {
return mRateLimitingWindowMs;
}
@VisibleForTesting
@@ -2440,11 +2521,6 @@ public final class QuotaController extends StateController {
return mTimingSessionCoalescingDurationMs;
}
@VisibleForTesting
int getMaxSessionCountPerAllowedTime() {
return mMaxSessionCountPerAllowedTime;
}
@VisibleForTesting
@Nullable
List<TimingSession> getTimingSessions(int userId, String packageName) {
@@ -2658,6 +2734,12 @@ public final class QuotaController extends StateController {
proto.write(
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);
@@ -2674,20 +2756,20 @@ public final class QuotaController extends StateController {
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);
}
}

View File

@@ -386,6 +386,8 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
.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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
// 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,44 +650,102 @@ public class QuotaControllerTest {
// 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.
*/
@@ -850,13 +920,15 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 class QuotaControllerTest {
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 class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
}
@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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
advanceElapsedClock(10 * SECOND_IN_MILLIS);
mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
assertEquals(2, stats.jobCountInAllowedTime);
assertEquals(2, stats.jobCountInRateLimitingWindow);
}
/**
@@ -2421,10 +2508,10 @@ public class QuotaControllerTest {
/**
* 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 @@ public class QuotaControllerTest {
// 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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
// 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 @@ public class QuotaControllerTest {
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 @@ public class QuotaControllerTest {
// 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 @@ public class QuotaControllerTest {
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());
}