Merge "Add ThresholdAlarmQueue."
This commit is contained in:
@@ -113,7 +113,6 @@ public class ComponentController extends StateController {
|
||||
userFilter.addAction(Intent.ACTION_USER_STOPPED);
|
||||
mContext.registerReceiverAsUser(
|
||||
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.job.controllers;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/** Wrapper class to represent a userId-pkgName combo. */
|
||||
final class Package {
|
||||
public final String packageName;
|
||||
public final int userId;
|
||||
|
||||
Package(int userId, String packageName) {
|
||||
this.userId = userId;
|
||||
this.packageName = packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return packageToString(userId, packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof Package)) {
|
||||
return false;
|
||||
}
|
||||
Package other = (Package) obj;
|
||||
return userId == other.userId && Objects.equals(packageName, other.packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return packageName.hashCode() + userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standardize the output of userId-packageName combo.
|
||||
*/
|
||||
static String packageToString(int userId, String packageName) {
|
||||
return "<" + userId + ">" + packageName;
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,13 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
||||
|
||||
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
||||
import static com.android.server.job.JobSchedulerService.sSystemClock;
|
||||
import static com.android.server.job.controllers.Package.packageToString;
|
||||
|
||||
import android.annotation.CurrentTimeMillisLong;
|
||||
import android.annotation.ElapsedRealtimeLong;
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.util.ArraySet;
|
||||
@@ -36,6 +40,7 @@ import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.JobSchedulerBackgroundThread;
|
||||
import com.android.server.job.JobSchedulerService;
|
||||
import com.android.server.utils.AlarmQueue;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -57,6 +62,7 @@ public class PrefetchController extends StateController {
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
|
||||
private final ThresholdAlarmListener mThresholdAlarmListener;
|
||||
|
||||
/**
|
||||
* The cutoff point to decide if a prefetch job is worth running or not. If the app is expected
|
||||
@@ -69,6 +75,8 @@ public class PrefetchController extends StateController {
|
||||
public PrefetchController(JobSchedulerService service) {
|
||||
super(service);
|
||||
mPcConstants = new PcConstants();
|
||||
mThresholdAlarmListener = new ThresholdAlarmListener(
|
||||
mContext, JobSchedulerBackgroundThread.get().getLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,9 +90,13 @@ public class PrefetchController extends StateController {
|
||||
jobs = new ArraySet<>();
|
||||
mTrackedJobs.add(userId, pkgName, jobs);
|
||||
}
|
||||
jobs.add(jobStatus);
|
||||
updateConstraintLocked(jobStatus,
|
||||
sSystemClock.millis(), sElapsedRealtimeClock.millis());
|
||||
final long now = sSystemClock.millis();
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
if (jobs.add(jobStatus) && jobs.size() == 1
|
||||
&& !willBeLaunchedSoonLocked(userId, pkgName, now)) {
|
||||
updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed);
|
||||
}
|
||||
updateConstraintLocked(jobStatus, now, nowElapsed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,10 +104,11 @@ public class PrefetchController extends StateController {
|
||||
@GuardedBy("mLock")
|
||||
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
|
||||
boolean forUpdate) {
|
||||
final ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
|
||||
jobStatus.getSourcePackageName());
|
||||
if (jobs != null) {
|
||||
jobs.remove(jobStatus);
|
||||
final int userId = jobStatus.getSourceUserId();
|
||||
final String pkgName = jobStatus.getSourcePackageName();
|
||||
final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
|
||||
if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) {
|
||||
mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +122,7 @@ public class PrefetchController extends StateController {
|
||||
final int userId = UserHandle.getUserId(uid);
|
||||
mTrackedJobs.delete(userId, packageName);
|
||||
mEstimatedLaunchTimes.delete(userId, packageName);
|
||||
mThresholdAlarmListener.removeAlarmForKey(new Package(userId, packageName));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,6 +130,7 @@ public class PrefetchController extends StateController {
|
||||
public void onUserRemovedLocked(int userId) {
|
||||
mTrackedJobs.delete(userId);
|
||||
mEstimatedLaunchTimes.delete(userId);
|
||||
mThresholdAlarmListener.removeAlarmsForUserId(userId);
|
||||
}
|
||||
|
||||
/** Return the app's next estimated launch time. */
|
||||
@@ -124,8 +139,14 @@ public class PrefetchController extends StateController {
|
||||
public long getNextEstimatedLaunchTimeLocked(@NonNull JobStatus jobStatus) {
|
||||
final int userId = jobStatus.getSourceUserId();
|
||||
final String pkgName = jobStatus.getSourcePackageName();
|
||||
return getNextEstimatedLaunchTimeLocked(userId, pkgName, sSystemClock.millis());
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@CurrentTimeMillisLong
|
||||
private long getNextEstimatedLaunchTimeLocked(int userId, @NonNull String pkgName,
|
||||
@CurrentTimeMillisLong long now) {
|
||||
Long nextEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
|
||||
final long now = sSystemClock.millis();
|
||||
if (nextEstimatedLaunchTime == null || nextEstimatedLaunchTime < now) {
|
||||
// TODO(194532703): get estimated time from UsageStats
|
||||
nextEstimatedLaunchTime = now + 2 * HOUR_IN_MILLIS;
|
||||
@@ -135,8 +156,8 @@ public class PrefetchController extends StateController {
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean maybeUpdateConstraintForPkgLocked(long now, long nowElapsed, int userId,
|
||||
String pkgName) {
|
||||
private boolean maybeUpdateConstraintForPkgLocked(@CurrentTimeMillisLong long now,
|
||||
@ElapsedRealtimeLong long nowElapsed, int userId, String pkgName) {
|
||||
final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
|
||||
if (jobs == null) {
|
||||
return false;
|
||||
@@ -150,10 +171,43 @@ public class PrefetchController extends StateController {
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean updateConstraintLocked(@NonNull JobStatus jobStatus, long now,
|
||||
long nowElapsed) {
|
||||
private boolean updateConstraintLocked(@NonNull JobStatus jobStatus,
|
||||
@CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
|
||||
return jobStatus.setPrefetchConstraintSatisfied(nowElapsed,
|
||||
getNextEstimatedLaunchTimeLocked(jobStatus) <= now + mLaunchTimeThresholdMs);
|
||||
willBeLaunchedSoonLocked(
|
||||
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), now));
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void updateThresholdAlarmLocked(int userId, @NonNull String pkgName,
|
||||
@CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
|
||||
final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
|
||||
if (jobs == null || jobs.size() == 0) {
|
||||
mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
|
||||
return;
|
||||
}
|
||||
|
||||
final long nextEstimatedLaunchTime = getNextEstimatedLaunchTimeLocked(userId, pkgName, now);
|
||||
if (nextEstimatedLaunchTime - now > mLaunchTimeThresholdMs) {
|
||||
// Set alarm to be notified when this crosses the threshold.
|
||||
final long timeToCrossThresholdMs =
|
||||
nextEstimatedLaunchTime - (now + mLaunchTimeThresholdMs);
|
||||
mThresholdAlarmListener.addAlarm(new Package(userId, pkgName),
|
||||
nowElapsed + timeToCrossThresholdMs);
|
||||
} else {
|
||||
mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the app is expected to be launched soon, where "soon" is within the next
|
||||
* {@link #mLaunchTimeThresholdMs} time.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private boolean willBeLaunchedSoonLocked(int userId, @NonNull String pkgName,
|
||||
@CurrentTimeMillisLong long now) {
|
||||
return getNextEstimatedLaunchTimeLocked(userId, pkgName, now)
|
||||
<= now + mLaunchTimeThresholdMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -186,6 +240,9 @@ public class PrefetchController extends StateController {
|
||||
now, nowElapsed, userId, packageName)) {
|
||||
changedJobs.addAll(mTrackedJobs.valueAt(u, p));
|
||||
}
|
||||
if (!willBeLaunchedSoonLocked(userId, packageName, now)) {
|
||||
updateThresholdAlarmLocked(userId, packageName, now, nowElapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,6 +253,42 @@ public class PrefetchController extends StateController {
|
||||
}
|
||||
}
|
||||
|
||||
/** Track when apps will cross the "will run soon" threshold. */
|
||||
private class ThresholdAlarmListener extends AlarmQueue<Package> {
|
||||
private ThresholdAlarmListener(Context context, Looper looper) {
|
||||
super(context, looper, "*job.prefetch*", "Prefetch threshold", false,
|
||||
PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS / 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isForUser(@NonNull Package key, int userId) {
|
||||
return key.userId == userId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
|
||||
final ArraySet<JobStatus> changedJobs = new ArraySet<>();
|
||||
synchronized (mLock) {
|
||||
final long now = sSystemClock.millis();
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
for (int i = 0; i < expired.size(); ++i) {
|
||||
Package p = expired.valueAt(i);
|
||||
if (!willBeLaunchedSoonLocked(p.userId, p.packageName, now)) {
|
||||
Slog.e(TAG, "Alarm expired for "
|
||||
+ packageToString(p.userId, p.packageName) + " at the wrong time");
|
||||
updateThresholdAlarmLocked(p.userId, p.packageName, now, nowElapsed);
|
||||
} else if (maybeUpdateConstraintForPkgLocked(
|
||||
now, nowElapsed, p.userId, p.packageName)) {
|
||||
changedJobs.addAll(mTrackedJobs.get(p.userId, p.packageName));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changedJobs.size() > 0) {
|
||||
mStateChangedListener.onControllerStateChanged(changedJobs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
class PcConstants {
|
||||
private boolean mShouldReevaluateConstraints = false;
|
||||
@@ -225,6 +318,9 @@ public class PrefetchController extends StateController {
|
||||
if (mLaunchTimeThresholdMs != newLaunchTimeThresholdMs) {
|
||||
mLaunchTimeThresholdMs = newLaunchTimeThresholdMs;
|
||||
mShouldReevaluateConstraints = true;
|
||||
// Give a leeway of 10% of the launch time threshold between alarms.
|
||||
mThresholdAlarmListener.setMinTimeBetweenAlarmsMs(
|
||||
mLaunchTimeThresholdMs / 10);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -294,6 +390,9 @@ public class PrefetchController extends StateController {
|
||||
pw.println();
|
||||
}
|
||||
});
|
||||
|
||||
pw.println();
|
||||
mThresholdAlarmListener.dump(pw);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,6 +27,7 @@ import static com.android.server.job.JobSchedulerService.RARE_INDEX;
|
||||
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
|
||||
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
|
||||
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
||||
import static com.android.server.job.controllers.Package.packageToString;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
@@ -79,7 +80,6 @@ import com.android.server.utils.AlarmQueue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -123,52 +123,6 @@ public final class QuotaController extends StateController {
|
||||
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
|
||||
| PackageManager.GET_PERMISSIONS | PackageManager.MATCH_KNOWN_PACKAGES;
|
||||
|
||||
/**
|
||||
* Standardize the output of userId-packageName combo.
|
||||
*/
|
||||
private static String string(int userId, String packageName) {
|
||||
return "<" + userId + ">" + packageName;
|
||||
}
|
||||
|
||||
private static final class Package {
|
||||
public final String packageName;
|
||||
public final int userId;
|
||||
|
||||
Package(int userId, String packageName) {
|
||||
this.userId = userId;
|
||||
this.packageName = packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return string(userId, packageName);
|
||||
}
|
||||
|
||||
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
|
||||
final long token = proto.start(fieldId);
|
||||
|
||||
proto.write(StateControllerProto.QuotaController.Package.USER_ID, userId);
|
||||
proto.write(StateControllerProto.QuotaController.Package.NAME, packageName);
|
||||
|
||||
proto.end(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Package) {
|
||||
Package other = (Package) obj;
|
||||
return userId == other.userId && Objects.equals(packageName, other.packageName);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return packageName.hashCode() + userId;
|
||||
}
|
||||
}
|
||||
|
||||
private static int hashLong(long val) {
|
||||
return (int) (val ^ (val >>> 32));
|
||||
}
|
||||
@@ -1741,7 +1695,6 @@ public final class QuotaController extends StateController {
|
||||
return;
|
||||
}
|
||||
|
||||
final String pkgString = string(userId, packageName);
|
||||
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
|
||||
final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
|
||||
final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
|
||||
@@ -1755,7 +1708,8 @@ public final class QuotaController extends StateController {
|
||||
if (inRegularQuota && remainingEJQuota > 0) {
|
||||
// Already in quota. Why was this method called?
|
||||
if (DEBUG) {
|
||||
Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
|
||||
Slog.e(TAG, "maybeScheduleStartAlarmLocked called for "
|
||||
+ packageToString(userId, packageName)
|
||||
+ " even though it already has "
|
||||
+ getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
|
||||
+ "ms in its quota.");
|
||||
@@ -1811,8 +1765,8 @@ public final class QuotaController extends StateController {
|
||||
// In some strange cases, an app may end be in the NEVER bucket but could have run
|
||||
// some regular jobs. This results in no EJ timing sessions and QC having a bad
|
||||
// time.
|
||||
Slog.wtf(TAG,
|
||||
string(userId, packageName) + " has 0 EJ quota without running anything");
|
||||
Slog.wtf(TAG, packageToString(userId, packageName)
|
||||
+ " has 0 EJ quota without running anything");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2272,7 +2226,6 @@ public final class QuotaController extends StateController {
|
||||
public void dump(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate) {
|
||||
final long token = proto.start(fieldId);
|
||||
|
||||
mPkg.dumpDebug(proto, StateControllerProto.QuotaController.Timer.PKG);
|
||||
proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive());
|
||||
proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED,
|
||||
mStartTimeElapsed);
|
||||
@@ -2381,7 +2334,6 @@ public final class QuotaController extends StateController {
|
||||
public void dump(ProtoOutputStream proto, long fieldId) {
|
||||
final long token = proto.start(fieldId);
|
||||
|
||||
mPkg.dumpDebug(proto, StateControllerProto.QuotaController.TopAppTimer.PKG);
|
||||
proto.write(StateControllerProto.QuotaController.TopAppTimer.IS_ACTIVE, isActive());
|
||||
proto.write(StateControllerProto.QuotaController.TopAppTimer.START_TIME_ELAPSED,
|
||||
mStartTimeElapsed);
|
||||
@@ -2413,7 +2365,7 @@ public final class QuotaController extends StateController {
|
||||
void updateStandbyBucket(
|
||||
final int userId, final @NonNull String packageName, final int bucketIndex) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Moving pkg " + string(userId, packageName)
|
||||
Slog.i(TAG, "Moving pkg " + packageToString(userId, packageName)
|
||||
+ " to bucketIndex " + bucketIndex);
|
||||
}
|
||||
List<JobStatus> restrictedChanges = new ArrayList<>();
|
||||
@@ -2641,7 +2593,7 @@ public final class QuotaController extends StateController {
|
||||
String packageName = (String) msg.obj;
|
||||
int userId = msg.arg1;
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Checking pkg " + string(userId, packageName));
|
||||
Slog.d(TAG, "Checking pkg " + packageToString(userId, packageName));
|
||||
}
|
||||
if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
|
||||
userId, packageName)) {
|
||||
@@ -2722,7 +2674,7 @@ public final class QuotaController extends StateController {
|
||||
final String pkgName = event.getPackageName();
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Processing event " + event.getEventType()
|
||||
+ " for " + string(userId, pkgName));
|
||||
+ " for " + packageToString(userId, pkgName));
|
||||
}
|
||||
switch (event.getEventType()) {
|
||||
case UsageEvents.Event.ACTIVITY_RESUMED:
|
||||
@@ -4119,7 +4071,7 @@ public final class QuotaController extends StateController {
|
||||
final String pkgName = mExecutionStatsCache.keyAt(u, p);
|
||||
ExecutionStats[] stats = mExecutionStatsCache.valueAt(u, p);
|
||||
|
||||
pw.println(string(userId, pkgName));
|
||||
pw.println(packageToString(userId, pkgName));
|
||||
pw.increaseIndent();
|
||||
for (int i = 0; i < stats.length; ++i) {
|
||||
ExecutionStats executionStats = stats[i];
|
||||
@@ -4143,7 +4095,7 @@ public final class QuotaController extends StateController {
|
||||
final String pkgName = mEJStats.keyAt(u, p);
|
||||
ShrinkableDebits debits = mEJStats.valueAt(u, p);
|
||||
|
||||
pw.print(string(userId, pkgName));
|
||||
pw.print(packageToString(userId, pkgName));
|
||||
pw.print(": ");
|
||||
debits.dumpLocked(pw);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user