Merge "Reduce individual quota alarms set." into rvc-dev am: 3201a3aab3 am: 1e94ca955f

Change-Id: I8eb0e0fd6bb2ba7a076c791e6a90721e7c6100e1
This commit is contained in:
Automerger Merge Worker
2020-02-28 00:18:17 +00:00
4 changed files with 185 additions and 104 deletions

View File

@@ -3034,7 +3034,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
void resetExecutionQuota(@NonNull String pkgName, int userId) {
mQuotaController.clearAppStats(pkgName, userId);
mQuotaController.clearAppStats(userId, pkgName);
}
void resetScheduleQuota() {

View File

@@ -54,12 +54,14 @@ import android.provider.Settings;
import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.SparseBooleanArray;
import android.util.SparseSetArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
@@ -74,6 +76,7 @@ import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -301,10 +304,10 @@ public final class QuotaController extends StateController {
private final SparseArrayMap<List<TimingSession>> mTimingSessions = new SparseArrayMap<>();
/**
* List of alarm listeners for each package that listen for when each package comes back within
* quota.
* Listener to track and manage when each package comes back within quota.
*/
private final SparseArrayMap<QcAlarmListener> mInQuotaAlarmListeners = new SparseArrayMap<>();
@GuardedBy("mLock")
private final InQuotaAlarmListener mInQuotaAlarmListener = new InQuotaAlarmListener();
/** Cached calculation results for each app, with the standby buckets as the array indices. */
private final SparseArrayMap<ExecutionStats[]> mExecutionStatsCache = new SparseArrayMap<>();
@@ -579,7 +582,7 @@ public final class QuotaController extends StateController {
Slog.wtf(TAG, "Told app removed but given null package name.");
return;
}
clearAppStats(packageName, UserHandle.getUserId(uid));
clearAppStats(UserHandle.getUserId(uid), packageName);
mForegroundUids.delete(uid);
mUidToPackageCache.remove(uid);
}
@@ -589,13 +592,13 @@ public final class QuotaController extends StateController {
mTrackedJobs.delete(userId);
mPkgTimers.delete(userId);
mTimingSessions.delete(userId);
mInQuotaAlarmListeners.delete(userId);
mInQuotaAlarmListener.removeAlarmsLocked(userId);
mExecutionStatsCache.delete(userId);
mUidToPackageCache.clear();
}
/** Drop all historical stats and stop tracking any active sessions for the specified app. */
public void clearAppStats(@NonNull String packageName, int userId) {
public void clearAppStats(int userId, @NonNull String packageName) {
mTrackedJobs.delete(userId, packageName);
Timer timer = mPkgTimers.get(userId, packageName);
if (timer != null) {
@@ -606,11 +609,7 @@ public final class QuotaController extends StateController {
mPkgTimers.delete(userId, packageName);
}
mTimingSessions.delete(userId, packageName);
QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
if (alarmListener != null) {
mAlarmManager.cancel(alarmListener);
mInQuotaAlarmListeners.delete(userId, packageName);
}
mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
mExecutionStatsCache.delete(userId, packageName);
}
@@ -1208,12 +1207,7 @@ public final class QuotaController extends StateController {
// exempted.
maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
} else {
QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
if (alarmListener != null && alarmListener.isWaiting()) {
mAlarmManager.cancel(alarmListener);
// Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
alarmListener.setTriggerTime(0);
}
mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
}
return changed;
}
@@ -1229,12 +1223,7 @@ public final class QuotaController extends StateController {
final String packageName = jobStatus.getSourcePackageName();
final int realStandbyBucket = jobStatus.getStandbyBucket();
if (isWithinQuotaLocked(userId, packageName, realStandbyBucket)) {
QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
if (alarmListener != null && alarmListener.isWaiting()) {
mAlarmManager.cancel(alarmListener);
// Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
alarmListener.setTriggerTime(0);
}
mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
} else {
mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
}
@@ -1285,7 +1274,6 @@ public final class QuotaController extends StateController {
final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
standbyBucket);
QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
&& stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
&& isUnderJobCountQuota
@@ -1297,21 +1285,11 @@ public final class QuotaController extends StateController {
+ getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
+ "ms in its quota.");
}
if (alarmListener != null) {
// Cancel any pending alarm.
mAlarmManager.cancel(alarmListener);
// Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
alarmListener.setTriggerTime(0);
}
mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
return;
}
if (alarmListener == null) {
alarmListener = new QcAlarmListener(userId, packageName);
mInQuotaAlarmListeners.add(userId, packageName, alarmListener);
}
// The time this app will have quota again.
long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
@@ -1325,27 +1303,7 @@ public final class QuotaController extends StateController {
inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
stats.sessionRateLimitExpirationTimeElapsed);
}
// Only schedule the alarm if:
// 1. There isn't one currently scheduled
// 2. The new alarm is significantly earlier than the previous alarm (which could be the
// case if the package moves into a higher standby bucket). If it's earlier but not
// significantly so, then we essentially delay the job a few extra minutes.
// 3. The alarm is after the current alarm by more than the quota buffer.
// TODO: this might be overengineering. Simplify if proven safe.
if (!alarmListener.isWaiting()
|| inQuotaTimeElapsed < alarmListener.getTriggerTimeElapsed() - 3 * MINUTE_IN_MILLIS
|| alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed) {
if (DEBUG) {
Slog.d(TAG, "Scheduling start alarm for " + pkgString);
}
// If the next time this app will have quota is at least 3 minutes before the
// alarm is supposed to go off, reschedule the alarm.
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, inQuotaTimeElapsed,
ALARM_TAG_QUOTA_CHECK, alarmListener, mHandler);
alarmListener.setTriggerTime(inQuotaTimeElapsed);
} else if (DEBUG) {
Slog.d(TAG, "No need to schedule start alarm for " + pkgString);
}
mInQuotaAlarmListener.addAlarmLocked(userId, packageName, inQuotaTimeElapsed);
}
private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, boolean isWithinQuota) {
@@ -1875,32 +1833,161 @@ public final class QuotaController extends StateController {
}
}
private class QcAlarmListener implements AlarmManager.OnAlarmListener {
private final int mUserId;
private final String mPackageName;
private volatile long mTriggerTimeElapsed;
QcAlarmListener(int userId, String packageName) {
mUserId = userId;
mPackageName = packageName;
static class AlarmQueue extends PriorityQueue<Pair<Package, Long>> {
AlarmQueue() {
super(1, (o1, o2) -> (int) (o1.second - o2.second));
}
boolean isWaiting() {
return mTriggerTimeElapsed > 0;
/**
* Remove any instances of the Package from the queue.
*
* @return true if an instance was removed, false otherwise.
*/
boolean remove(@NonNull Package pkg) {
boolean removed = false;
Pair[] alarms = toArray(new Pair[size()]);
for (int i = alarms.length - 1; i >= 0; --i) {
if (pkg.equals(alarms[i].first)) {
remove(alarms[i]);
removed = true;
}
}
return removed;
}
}
/** Track when UPTCs are expected to come back into quota. */
private class InQuotaAlarmListener implements AlarmManager.OnAlarmListener {
@GuardedBy("mLock")
private final AlarmQueue mAlarmQueue = new AlarmQueue();
/** The next time the alarm is set to go off, in the elapsed realtime timebase. */
@GuardedBy("mLock")
private long mTriggerTimeElapsed = 0;
@GuardedBy("mLock")
void addAlarmLocked(int userId, @NonNull String pkgName, long inQuotaTimeElapsed) {
final Package pkg = new Package(userId, pkgName);
mAlarmQueue.remove(pkg);
mAlarmQueue.offer(new Pair<>(pkg, inQuotaTimeElapsed));
setNextAlarmLocked();
}
void setTriggerTime(long timeElapsed) {
mTriggerTimeElapsed = timeElapsed;
@GuardedBy("mLock")
void removeAlarmLocked(@NonNull Package pkg) {
if (mAlarmQueue.remove(pkg)) {
setNextAlarmLocked();
}
}
long getTriggerTimeElapsed() {
return mTriggerTimeElapsed;
@GuardedBy("mLock")
void removeAlarmLocked(int userId, @NonNull String packageName) {
removeAlarmLocked(new Package(userId, packageName));
}
@GuardedBy("mLock")
void removeAlarmsLocked(int userId) {
boolean removed = false;
Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
for (int i = alarms.length - 1; i >= 0; --i) {
final Package pkg = (Package) alarms[i].first;
if (userId == pkg.userId) {
mAlarmQueue.remove(alarms[i]);
removed = true;
}
}
if (removed) {
setNextAlarmLocked();
}
}
@GuardedBy("mLock")
private void setNextAlarmLocked() {
if (mAlarmQueue.size() > 0) {
final long nextTriggerTimeElapsed = mAlarmQueue.peek().second;
// Only schedule the alarm if one of the following is true:
// 1. There isn't one currently scheduled
// 2. The new alarm is significantly earlier than the previous alarm. If it's
// earlier but not significantly so, then we essentially delay the job a few extra
// minutes.
// 3. The alarm is after the current alarm.
if (mTriggerTimeElapsed == 0
|| nextTriggerTimeElapsed < mTriggerTimeElapsed - 3 * MINUTE_IN_MILLIS
|| mTriggerTimeElapsed < nextTriggerTimeElapsed) {
if (DEBUG) {
Slog.d(TAG, "Scheduling start alarm at " + nextTriggerTimeElapsed);
}
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextTriggerTimeElapsed,
ALARM_TAG_QUOTA_CHECK, this, mHandler);
mTriggerTimeElapsed = nextTriggerTimeElapsed;
}
} else {
mAlarmManager.cancel(this);
mTriggerTimeElapsed = 0;
}
}
@Override
public void onAlarm() {
mHandler.obtainMessage(MSG_CHECK_PACKAGE, mUserId, 0, mPackageName).sendToTarget();
mTriggerTimeElapsed = 0;
synchronized (mLock) {
while (mAlarmQueue.size() > 0) {
final Pair<Package, Long> alarm = mAlarmQueue.peek();
if (alarm.second <= sElapsedRealtimeClock.millis()) {
mHandler.obtainMessage(MSG_CHECK_PACKAGE, alarm.first.userId, 0,
alarm.first.packageName).sendToTarget();
mAlarmQueue.remove(alarm);
} else {
break;
}
}
setNextAlarmLocked();
}
}
@GuardedBy("mLock")
void dumpLocked(IndentingPrintWriter pw) {
pw.println("In quota alarms:");
pw.increaseIndent();
if (mAlarmQueue.size() == 0) {
pw.println("NOT WAITING");
} else {
Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
for (int i = 0; i < alarms.length; ++i) {
final Package pkg = (Package) alarms[i].first;
pw.print(pkg);
pw.print(": ");
pw.print(alarms[i].second);
pw.println();
}
}
pw.decreaseIndent();
}
@GuardedBy("mLock")
void dumpLocked(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(
StateControllerProto.QuotaController.InQuotaAlarmListener.TRIGGER_TIME_ELAPSED,
mTriggerTimeElapsed);
Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
for (int i = 0; i < alarms.length; ++i) {
final long aToken = proto.start(
StateControllerProto.QuotaController.InQuotaAlarmListener.ALARMS);
final Package pkg = (Package) alarms[i].first;
pkg.dumpDebug(proto,
StateControllerProto.QuotaController.InQuotaAlarmListener.Alarm.PKG);
proto.write(
StateControllerProto.QuotaController.InQuotaAlarmListener.Alarm.IN_QUOTA_TIME_ELAPSED,
(Long) alarms[i].second);
proto.end(aToken);
}
proto.end(token);
}
}
@@ -2618,23 +2705,7 @@ public final class QuotaController extends StateController {
pw.decreaseIndent();
pw.println();
pw.println("In quota alarms:");
pw.increaseIndent();
for (int u = 0; u < mInQuotaAlarmListeners.numMaps(); ++u) {
final int userId = mInQuotaAlarmListeners.keyAt(u);
for (int p = 0; p < mInQuotaAlarmListeners.numElementsForKey(userId); ++p) {
final String pkgName = mInQuotaAlarmListeners.keyAt(u, p);
QcAlarmListener alarmListener = mInQuotaAlarmListeners.valueAt(u, p);
pw.print(string(userId, pkgName));
pw.print(": ");
if (alarmListener.isWaiting()) {
pw.println(alarmListener.getTriggerTimeElapsed());
} else {
pw.println("NOT WAITING");
}
}
}
mInQuotaAlarmListener.dumpLocked(pw);
pw.decreaseIndent();
}
@@ -2768,22 +2839,13 @@ public final class QuotaController extends StateController {
}
}
QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, pkgName);
if (alarmListener != null) {
final long alToken = proto.start(
StateControllerProto.QuotaController.PackageStats.IN_QUOTA_ALARM_LISTENER);
proto.write(StateControllerProto.QuotaController.AlarmListener.IS_WAITING,
alarmListener.isWaiting());
proto.write(
StateControllerProto.QuotaController.AlarmListener.TRIGGER_TIME_ELAPSED,
alarmListener.getTriggerTimeElapsed());
proto.end(alToken);
}
proto.end(psToken);
}
}
mInQuotaAlarmListener.dumpLocked(proto,
StateControllerProto.QuotaController.IN_QUOTA_ALARM_LISTENER);
proto.end(mToken);
proto.end(token);
}

View File

@@ -670,7 +670,7 @@ message StateControllerProto {
repeated ExecutionStats execution_stats = 4;
optional AlarmListener in_quota_alarm_listener = 5;
reserved 5; // in_quota_alarm_listener
}
repeated PackageStats package_stats = 5;
@@ -683,7 +683,25 @@ message StateControllerProto {
}
repeated UidPackageMapping uid_to_package_cache = 7;
// Next tag: 8
message InQuotaAlarmListener {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
// The time at which the alarm is set to go off, in the elapsed realtime timebase.
optional int64 trigger_time_elapsed = 1;
message Alarm {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional Package pkg = 1;
// The time at which the package will be in quota, in the elapsed realtime timebase.
optional int64 in_quota_time_elapsed = 2;
}
repeated Alarm alarms = 2;
}
optional InQuotaAlarmListener in_quota_alarm_listener = 8;
// Next tag: 9
}
message StorageController {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;

View File

@@ -552,6 +552,7 @@ abstract class QuotaTracker {
mTriggerTimeElapsed = nextTriggerTimeElapsed;
}
} else {
cancelAlarm(this);
mTriggerTimeElapsed = 0;
}
}