Merge "Start reclaiming assets when circulation is too high."

This commit is contained in:
Kweku Adams
2021-10-27 18:23:23 +00:00
committed by Android (Google) Code Review
3 changed files with 153 additions and 36 deletions

View File

@@ -16,8 +16,6 @@
package com.android.server.tare;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static com.android.server.tare.EconomicPolicy.REGULATION_BASIC_INCOME;
import static com.android.server.tare.EconomicPolicy.REGULATION_BIRTHRIGHT;
import static com.android.server.tare.EconomicPolicy.REGULATION_WEALTH_RECLAMATION;
@@ -69,12 +67,6 @@ class Agent {
private static final boolean DEBUG = InternalResourceService.DEBUG
|| Log.isLoggable(TAG, Log.DEBUG);
/**
* The minimum amount of time an app must not have been used by the user before we start
* regularly reclaiming ARCs from it.
*/
private static final long MIN_UNUSED_TIME_MS = 3 * 24 * HOUR_IN_MILLIS;
private static final String ALARM_TAG_AFFORDABILITY_CHECK = "*tare.affordability_check*";
private final Object mLock;
@@ -550,41 +542,62 @@ class Agent {
/**
* Reclaim a percentage of unused ARCs from every app that hasn't been used recently. The
* reclamation will not reduce an app's balance below its minimum balance as dictated by the
* EconomicPolicy.
* reclamation will not reduce an app's balance below its minimum balance as dictated by
* {@code scaleMinBalance}.
*
* @param percentage A value between 0 and 1 to indicate how much of the unused balance should
* be reclaimed.
* @param percentage A value between 0 and 1 to indicate how much of the unused balance
* should be reclaimed.
* @param minUnusedTimeMs The minimum amount of time (in milliseconds) that must have
* transpired since the last user usage event before we will consider
* reclaiming ARCs from the app.
* @param scaleMinBalance Whether or not to used the scaled minimum app balance. If false,
* this will use the constant min balance floor given by
* {@link EconomicPolicy#getMinSatiatedBalance(int, String)}. If true,
* this will use the scaled balance given by
* {@link InternalResourceService#getMinBalanceLocked(int, String)}.
*/
@GuardedBy("mLock")
void reclaimUnusedAssetsLocked(double percentage) {
void reclaimUnusedAssetsLocked(double percentage, long minUnusedTimeMs,
boolean scaleMinBalance) {
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
final List<PackageInfo> pkgs = mIrs.getInstalledPackages();
final SparseArrayMap<String, Ledger> ledgers = mScribe.getLedgersLocked();
final long now = getCurrentTimeMillis();
for (int i = 0; i < pkgs.size(); ++i) {
final int userId = UserHandle.getUserId(pkgs.get(i).applicationInfo.uid);
final String pkgName = pkgs.get(i).packageName;
final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
// AppStandby only counts elapsed time for things like this
// TODO: should we use clock time instead?
final long timeSinceLastUsedMs =
mAppStandbyInternal.getTimeSinceLastUsedByUser(pkgName, userId);
if (timeSinceLastUsedMs >= MIN_UNUSED_TIME_MS) {
// Use a constant floor instead of the scaled floor from the IRS.
final long minBalance = economicPolicy.getMinSatiatedBalance(userId, pkgName);
for (int u = 0; u < ledgers.numMaps(); ++u) {
final int userId = ledgers.keyAt(u);
for (int p = 0; p < ledgers.numElementsForKey(userId); ++p) {
final Ledger ledger = ledgers.valueAt(u, p);
final long curBalance = ledger.getCurrentBalance();
long toReclaim = (long) (curBalance * percentage);
if (curBalance - toReclaim < minBalance) {
toReclaim = curBalance - minBalance;
if (curBalance <= 0) {
continue;
}
if (toReclaim > 0) {
Slog.i(TAG, "Reclaiming unused wealth! Taking " + toReclaim
+ " from " + appToString(userId, pkgName));
final String pkgName = ledgers.keyAt(u, p);
// AppStandby only counts elapsed time for things like this
// TODO: should we use clock time instead?
final long timeSinceLastUsedMs =
mAppStandbyInternal.getTimeSinceLastUsedByUser(pkgName, userId);
if (timeSinceLastUsedMs >= minUnusedTimeMs) {
final long minBalance;
if (!scaleMinBalance) {
// Use a constant floor instead of the scaled floor from the IRS.
minBalance = economicPolicy.getMinSatiatedBalance(userId, pkgName);
} else {
minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
}
long toReclaim = (long) (curBalance * percentage);
if (curBalance - toReclaim < minBalance) {
toReclaim = curBalance - minBalance;
}
if (toReclaim > 0) {
if (DEBUG) {
Slog.i(TAG, "Reclaiming unused wealth! Taking " + toReclaim
+ " from " + appToString(userId, pkgName));
}
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(
now, now, REGULATION_WEALTH_RECLAMATION, null, -toReclaim),
true);
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(
now, now, REGULATION_WEALTH_RECLAMATION, null, -toReclaim),
true);
}
}
}
}

View File

@@ -18,6 +18,7 @@ package com.android.server.tare;
import static android.provider.Settings.Global.TARE_ALARM_MANAGER_CONSTANTS;
import static android.provider.Settings.Global.TARE_JOB_SCHEDULER_CONSTANTS;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -85,6 +86,11 @@ public class InternalResourceService extends SystemService {
static final long UNUSED_RECLAMATION_PERIOD_MS = 24 * HOUR_IN_MILLIS;
/** How much of an app's unused wealth should be reclaimed periodically. */
private static final float DEFAULT_UNUSED_RECLAMATION_PERCENTAGE = .1f;
/**
* The minimum amount of time an app must not have been used by the user before we start
* periodically reclaiming ARCs from it.
*/
private static final long MIN_UNUSED_TIME_MS = 3 * DAY_IN_MILLIS;
/** The amount of time to delay reclamation by after boot. */
private static final long RECLAMATION_STARTUP_DELAY_MS = 30_000L;
private static final int PACKAGE_QUERY_FLAGS =
@@ -107,6 +113,44 @@ public class InternalResourceService extends SystemService {
@GuardedBy("mLock")
private CompleteEconomicPolicy mCompleteEconomicPolicy;
private static final class ReclamationConfig {
/**
* ARC circulation threshold (% circulating vs scaled maximum) above which this config
* should come into play.
*/
public final double circulationPercentageThreshold;
/** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
public final double reclamationPercentage;
/** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
public final long minUsedTimeMs;
/** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
public final boolean scaleMinBalance;
ReclamationConfig(double circulationPercentageThreshold, double reclamationPercentage,
long minUsedTimeMs, boolean scaleMinBalance) {
this.circulationPercentageThreshold = circulationPercentageThreshold;
this.reclamationPercentage = reclamationPercentage;
this.minUsedTimeMs = minUsedTimeMs;
this.scaleMinBalance = scaleMinBalance;
}
}
/**
* Sorted list of reclamation configs used to determine how many credits to force reclaim when
* the circulation percentage is too high. The list should *always* be sorted in descending
* order of {@link ReclamationConfig#circulationPercentageThreshold}.
*/
@GuardedBy("mLock")
private final List<ReclamationConfig> mReclamationConfigs = List.of(
new ReclamationConfig(2, .75, 12 * HOUR_IN_MILLIS, true),
new ReclamationConfig(1.6, .5, DAY_IN_MILLIS, true),
new ReclamationConfig(1.4, .25, DAY_IN_MILLIS, true),
new ReclamationConfig(1.2, .25, 2 * DAY_IN_MILLIS, true),
new ReclamationConfig(1, .25, MIN_UNUSED_TIME_MS, false),
new ReclamationConfig(
.9, DEFAULT_UNUSED_RECLAMATION_PERCENTAGE, MIN_UNUSED_TIME_MS, false)
);
@NonNull
@GuardedBy("mLock")
private final List<PackageInfo> mPkgCache = new ArrayList<>();
@@ -190,7 +234,8 @@ public class InternalResourceService extends SystemService {
@Override
public void onAlarm() {
synchronized (mLock) {
mAgent.reclaimUnusedAssetsLocked(DEFAULT_UNUSED_RECLAMATION_PERCENTAGE);
mAgent.reclaimUnusedAssetsLocked(
DEFAULT_UNUSED_RECLAMATION_PERCENTAGE, MIN_UNUSED_TIME_MS, false);
mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
scheduleUnusedWealthReclamationLocked();
}
@@ -200,6 +245,7 @@ public class InternalResourceService extends SystemService {
private static final int MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER = 0;
private static final int MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT = 1;
private static final int MSG_PROCESS_USAGE_EVENT = 2;
private static final int MSG_MAYBE_FOCE_RECLAIM = 3;
private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";
private static final String KEY_PKG = "pkg";
@@ -317,6 +363,8 @@ public class InternalResourceService extends SystemService {
final int newBatteryLevel = getCurrentBatteryLevel();
if (newBatteryLevel > mCurrentBatteryLevel) {
mAgent.distributeBasicIncomeLocked(newBatteryLevel);
} else if (newBatteryLevel < mCurrentBatteryLevel) {
mHandler.obtainMessage(MSG_MAYBE_FOCE_RECLAIM).sendToTarget();
}
mCurrentBatteryLevel = newBatteryLevel;
}
@@ -511,6 +559,48 @@ public class InternalResourceService extends SystemService {
}
}
/**
* Reclaim unused ARCs above apps' minimum balances if there are too many credits currently
* in circulation.
*/
@GuardedBy("mLock")
private void maybeForceReclaimLocked() {
final long maxCirculation = getMaxCirculationLocked();
if (maxCirculation == 0) {
Slog.wtf(TAG, "Max scaled circulation is 0...");
mAgent.reclaimUnusedAssetsLocked(1, HOUR_IN_MILLIS, true);
mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
scheduleUnusedWealthReclamationLocked();
return;
}
final long curCirculation = mScribe.getNarcsInCirculationLocked();
final double circulationPerc = 1.0 * curCirculation / maxCirculation;
if (DEBUG) {
Slog.d(TAG, "Circulation %: " + circulationPerc);
}
final int numConfigs = mReclamationConfigs.size();
if (numConfigs == 0) {
return;
}
// The configs are sorted in descending order of circulationPercentageThreshold, so we can
// short-circuit if the current circulation is lower than the lowest threshold.
if (circulationPerc
< mReclamationConfigs.get(numConfigs - 1).circulationPercentageThreshold) {
return;
}
// TODO: maybe exclude apps we think will be launched in the next few hours
for (int i = 0; i < numConfigs; ++i) {
final ReclamationConfig config = mReclamationConfigs.get(i);
if (circulationPerc >= config.circulationPercentageThreshold) {
mAgent.reclaimUnusedAssetsLocked(
config.reclamationPercentage, config.minUsedTimeMs, config.scaleMinBalance);
mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
scheduleUnusedWealthReclamationLocked();
break;
}
}
}
private void registerListeners() {
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED);
@@ -594,6 +684,14 @@ public class InternalResourceService extends SystemService {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_MAYBE_FOCE_RECLAIM: {
removeMessages(MSG_MAYBE_FOCE_RECLAIM);
synchronized (mLock) {
maybeForceReclaimLocked();
}
}
break;
case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: {
Bundle data = msg.getData();
final int userId = msg.arg1;

View File

@@ -147,6 +147,12 @@ public class Scribe {
return ledger;
}
@GuardedBy("mIrs.getLock()")
@NonNull
SparseArrayMap<String, Ledger> getLedgersLocked() {
return mLedgers;
}
/** Returns the total amount of narcs currently allocated to apps. */
@GuardedBy("mIrs.getLock()")
long getNarcsInCirculationLocked() {