Implementing TIP1.

Tare Improvement Proposal #1 (TIP1) separates supply from allocation.
By separating the two, apps are able to accrue credits regardless of the
balances of other apps, and the limited nature of the resource is
shifted to when apps try to perform actions (and thus consume
resources). We remove the "maximum circulation" that limited the total
number of credits that could be allocated across all apps at one time
and replace it with a "consumption limit" that limits the total number
of credits that can be consumed across all apps within a full discharge
cycle. The consumption limit continues to scale down with the battery
level. This change should help reduce concerns over misallocation.

Bug: 158300259
Test: Spot check dumpsys of tare, jobscheduler, and alarmmanager
Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/tare
Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/tare
Change-Id: I104eef812889ffb543132ae46c7ac869b12c9882
This commit is contained in:
Kweku Adams
2022-02-16 17:24:48 +00:00
parent 35a35495fd
commit 5cb3d10f97
14 changed files with 721 additions and 379 deletions

View File

@@ -39,7 +39,9 @@ public class EconomyManager {
/** @hide */
public static final String KEY_AM_MAX_SATIATED_BALANCE = "am_max_satiated_balance";
/** @hide */
public static final String KEY_AM_MAX_CIRCULATION = "am_max_circulation";
public static final String KEY_AM_INITIAL_CONSUMPTION_LIMIT = "am_initial_consumption_limit";
/** @hide */
public static final String KEY_AM_HARD_CONSUMPTION_LIMIT = "am_hard_consumption_limit";
// TODO: Add AlarmManager modifier keys
/** @hide */
public static final String KEY_AM_REWARD_TOP_ACTIVITY_INSTANT =
@@ -163,7 +165,9 @@ public class EconomyManager {
public static final String KEY_JS_MAX_SATIATED_BALANCE =
"js_max_satiated_balance";
/** @hide */
public static final String KEY_JS_MAX_CIRCULATION = "js_max_circulation";
public static final String KEY_JS_INITIAL_CONSUMPTION_LIMIT = "js_initial_consumption_limit";
/** @hide */
public static final String KEY_JS_HARD_CONSUMPTION_LIMIT = "js_hard_consumption_limit";
// TODO: Add JobScheduler modifier keys
/** @hide */
public static final String KEY_JS_REWARD_TOP_ACTIVITY_INSTANT =
@@ -280,7 +284,9 @@ public class EconomyManager {
/** @hide */
public static final int DEFAULT_AM_MAX_SATIATED_BALANCE = 1440;
/** @hide */
public static final int DEFAULT_AM_MAX_CIRCULATION = 52000;
public static final int DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT = 28800;
/** @hide */
public static final int DEFAULT_AM_HARD_CONSUMPTION_LIMIT = 52000;
// TODO: add AlarmManager modifier default values
/** @hide */
public static final int DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT = 0;
@@ -359,7 +365,7 @@ public class EconomyManager {
// Default values JobScheduler factors
// TODO: add time_since_usage variable to min satiated balance factors
/** @hide */
public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED = 50000;
public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED = 20000;
/** @hide */
public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP = 10000;
/** @hide */
@@ -367,7 +373,9 @@ public class EconomyManager {
/** @hide */
public static final int DEFAULT_JS_MAX_SATIATED_BALANCE = 60000;
/** @hide */
public static final int DEFAULT_JS_MAX_CIRCULATION = 691200;
public static final int DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT = 460_000;
/** @hide */
public static final int DEFAULT_JS_HARD_CONSUMPTION_LIMIT = 900_000;
// TODO: add JobScheduler modifier default values
/** @hide */
public static final int DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT = 0;

View File

@@ -3007,7 +3007,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
} else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Disconnected from power @ " + sElapsedRealtimeClock.millis());
Slog.d(TAG, "Battery discharging @ " + sElapsedRealtimeClock.millis());
}
if (mCharging) {
mCharging = false;

View File

@@ -101,7 +101,14 @@ class Agent {
@GuardedBy("mLock")
private final BalanceThresholdAlarmQueue mBalanceThresholdAlarmQueue;
private static final int MSG_CHECK_BALANCE = 0;
/**
* Check the affordability notes of all apps.
*/
private static final int MSG_CHECK_ALL_AFFORDABILITY = 0;
/**
* Check the affordability notes of a single app.
*/
private static final int MSG_CHECK_INDIVIDUAL_AFFORDABILITY = 1;
Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe) {
mLock = irs.getLock();
@@ -128,7 +135,7 @@ class Agent {
@Override
public void accept(OngoingEvent ongoingEvent) {
mTotal += getActualDeltaLocked(ongoingEvent, mLedger, mNowElapsed, mNow);
mTotal += getActualDeltaLocked(ongoingEvent, mLedger, mNowElapsed, mNow).price;
}
}
@@ -153,8 +160,8 @@ class Agent {
}
@GuardedBy("mLock")
private boolean isAffordableLocked(long balance, long price) {
return balance >= price;
private boolean isAffordableLocked(long balance, long price, long ctp) {
return balance >= price && mScribe.getRemainingConsumableNarcsLocked() >= ctp;
}
@GuardedBy("mLock")
@@ -172,10 +179,13 @@ class Agent {
final int eventType = getEventType(eventId);
switch (eventType) {
case TYPE_ACTION:
final long actionCost = economicPolicy.getCostOfAction(eventId, userId, pkgName);
final EconomicPolicy.Cost actionCost =
economicPolicy.getCostOfAction(eventId, userId, pkgName);
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, eventId, tag, -actionCost), true);
new Ledger.Transaction(now, now, eventId, tag,
-actionCost.price, actionCost.costToProduce),
true);
break;
case TYPE_REWARD:
@@ -185,7 +195,7 @@ class Agent {
final long rewardVal = Math.max(0,
Math.min(reward.maxDailyReward - rewardSum, reward.instantReward));
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, eventId, tag, rewardVal), true);
new Ledger.Transaction(now, now, eventId, tag, rewardVal, 0), true);
}
break;
@@ -222,11 +232,12 @@ class Agent {
final int eventType = getEventType(eventId);
switch (eventType) {
case TYPE_ACTION:
final long actionCost = economicPolicy.getCostOfAction(eventId, userId, pkgName);
final EconomicPolicy.Cost actionCost =
economicPolicy.getCostOfAction(eventId, userId, pkgName);
if (ongoingEvent == null) {
ongoingEvents.add(eventId, tag,
new OngoingEvent(eventId, tag, null, startElapsed, -actionCost));
new OngoingEvent(eventId, tag, startElapsed, actionCost));
} else {
ongoingEvent.refCount++;
}
@@ -237,7 +248,7 @@ class Agent {
if (reward != null) {
if (ongoingEvent == null) {
ongoingEvents.add(eventId, tag, new OngoingEvent(
eventId, tag, reward, startElapsed, reward.ongoingRewardPerSecond));
eventId, tag, startElapsed, reward));
} else {
ongoingEvent.refCount++;
}
@@ -287,7 +298,7 @@ class Agent {
note.recalculateCosts(economicPolicy, userId, pkgName);
final boolean isAffordable =
isAffordableLocked(newBalance,
note.getCachedModifiedPrice());
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -341,7 +352,7 @@ class Agent {
note.recalculateCosts(economicPolicy, userId, pkgName);
final boolean isAffordable =
isAffordableLocked(newBalance,
note.getCachedModifiedPrice());
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -396,9 +407,11 @@ class Agent {
if (ongoingEvent.refCount <= 0) {
final long startElapsed = ongoingEvent.startTimeElapsed;
final long startTime = now - (nowElapsed - startElapsed);
final long actualDelta = getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now);
final EconomicPolicy.Cost actualDelta =
getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now);
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(startTime, now, eventId, tag, actualDelta),
new Ledger.Transaction(startTime, now, eventId, tag, actualDelta.price,
actualDelta.costToProduce),
notifyOnAffordabilityChange);
ongoingEvents.delete(eventId, tag);
@@ -409,17 +422,20 @@ class Agent {
}
@GuardedBy("mLock")
private long getActualDeltaLocked(@NonNull OngoingEvent ongoingEvent, @NonNull Ledger ledger,
long nowElapsed, long now) {
@NonNull
private EconomicPolicy.Cost getActualDeltaLocked(@NonNull OngoingEvent ongoingEvent,
@NonNull Ledger ledger, long nowElapsed, long now) {
final long startElapsed = ongoingEvent.startTimeElapsed;
final long durationSecs = (nowElapsed - startElapsed) / 1000;
final long computedDelta = durationSecs * ongoingEvent.deltaPerSec;
final long computedDelta = durationSecs * ongoingEvent.getDeltaPerSec();
if (ongoingEvent.reward == null) {
return computedDelta;
return new EconomicPolicy.Cost(
durationSecs * ongoingEvent.getCtpPerSec(), computedDelta);
}
final long rewardSum = ledger.get24HourSum(ongoingEvent.eventId, now);
return Math.max(0,
Math.min(ongoingEvent.reward.maxDailyReward - rewardSum, computedDelta));
return new EconomicPolicy.Cost(0,
Math.max(0,
Math.min(ongoingEvent.reward.maxDailyReward - rewardSum, computedDelta)));
}
@VisibleForTesting
@@ -437,22 +453,6 @@ class Agent {
return;
}
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
final long maxCirculationAllowed = mIrs.getMaxCirculationLocked();
final long curNarcsInCirculation = mScribe.getNarcsInCirculationLocked();
final long newArcsInCirculation = curNarcsInCirculation + transaction.delta;
if (transaction.delta > 0 && newArcsInCirculation > maxCirculationAllowed) {
// Set lower bound at 0 so we don't accidentally take away credits when we were trying
// to _give_ the app credits.
final long newDelta = Math.max(0, maxCirculationAllowed - curNarcsInCirculation);
Slog.i(TAG, "Would result in too many credits in circulation. Decreasing transaction "
+ eventToString(transaction.eventId)
+ (transaction.tag == null ? "" : ":" + transaction.tag)
+ " for " + appToString(userId, pkgName)
+ " by " + narcToString(transaction.delta - newDelta));
transaction = new Ledger.Transaction(
transaction.startTimeMs, transaction.endTimeMs,
transaction.eventId, transaction.tag, newDelta);
}
final long originalBalance = ledger.getCurrentBalance();
if (transaction.delta > 0
&& originalBalance + transaction.delta > economicPolicy.getMaxSatiatedBalance()) {
@@ -467,10 +467,10 @@ class Agent {
+ " by " + narcToString(transaction.delta - newDelta));
transaction = new Ledger.Transaction(
transaction.startTimeMs, transaction.endTimeMs,
transaction.eventId, transaction.tag, newDelta);
transaction.eventId, transaction.tag, newDelta, transaction.ctp);
}
ledger.recordTransaction(transaction);
mScribe.adjustNarcsInCirculationLocked(transaction.delta);
mScribe.adjustRemainingConsumableNarcsLocked(-transaction.ctp);
if (transaction.delta != 0 && notifyOnAffordabilityChange) {
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
mActionAffordabilityNotes.get(userId, pkgName);
@@ -480,7 +480,7 @@ class Agent {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
final boolean isAffordable =
isAffordableLocked(newBalance,
note.getCachedModifiedPrice());
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -488,6 +488,10 @@ class Agent {
}
}
}
if (transaction.ctp != 0) {
mHandler.sendEmptyMessage(MSG_CHECK_ALL_AFFORDABILITY);
mIrs.maybePerformQuantitativeEasingLocked();
}
}
/**
@@ -544,8 +548,8 @@ class Agent {
}
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(
now, now, REGULATION_WEALTH_RECLAMATION, null, -toReclaim),
new Ledger.Transaction(now, now, REGULATION_WEALTH_RECLAMATION,
null, -toReclaim, 0),
true);
}
}
@@ -593,7 +597,7 @@ class Agent {
final long now = getCurrentTimeMillis();
final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, REGULATION_DEMOTION, null, -toReclaim),
new Ledger.Transaction(now, now, REGULATION_DEMOTION, null, -toReclaim, 0),
true);
}
}
@@ -610,6 +614,10 @@ class Agent {
return !mIrs.isSystem(userId, packageInfo.packageName);
}
void onCreditSupplyChanged() {
mHandler.sendEmptyMessage(MSG_CHECK_ALL_AFFORDABILITY);
}
@GuardedBy("mLock")
void distributeBasicIncomeLocked(int batteryLevel) {
List<PackageInfo> pkgs = mIrs.getInstalledPackages();
@@ -630,7 +638,7 @@ class Agent {
if (shortfall > 0) {
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, REGULATION_BASIC_INCOME,
null, (long) (perc * shortfall)), true);
null, (long) (perc * shortfall), 0), true);
}
}
}
@@ -649,8 +657,6 @@ class Agent {
@GuardedBy("mLock")
void grantBirthrightsLocked(final int userId) {
final List<PackageInfo> pkgs = mIrs.getInstalledPackages(userId);
final long maxBirthright =
mIrs.getMaxCirculationLocked() / mIrs.getInstalledPackages().size();
final long now = getCurrentTimeMillis();
for (int i = 0; i < pkgs.size(); ++i) {
@@ -668,7 +674,7 @@ class Agent {
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, REGULATION_BIRTHRIGHT, null,
Math.min(maxBirthright, mIrs.getMinBalanceLocked(userId, pkgName))),
mIrs.getMinBalanceLocked(userId, pkgName), 0),
true);
}
}
@@ -682,14 +688,11 @@ class Agent {
return;
}
List<PackageInfo> pkgs = mIrs.getInstalledPackages();
final int numPackages = pkgs.size();
final long maxBirthright = mIrs.getMaxCirculationLocked() / numPackages;
final long now = getCurrentTimeMillis();
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, REGULATION_BIRTHRIGHT, null,
Math.min(maxBirthright, mIrs.getMinBalanceLocked(userId, pkgName))), true);
mIrs.getMinBalanceLocked(userId, pkgName), 0), true);
}
@GuardedBy("mLock")
@@ -704,7 +707,7 @@ class Agent {
final long now = getCurrentTimeMillis();
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, REGULATION_PROMOTION, null, missing), true);
new Ledger.Transaction(now, now, REGULATION_PROMOTION, null, missing, 0), true);
}
@GuardedBy("mLock")
@@ -721,7 +724,7 @@ class Agent {
private void reclaimAssetsLocked(final int userId, @NonNull final String pkgName) {
final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
if (ledger.getCurrentBalance() != 0) {
mScribe.adjustNarcsInCirculationLocked(-ledger.getCurrentBalance());
mScribe.adjustRemainingConsumableNarcsLocked(-ledger.getCurrentBalance());
}
mScribe.discardLedgerLocked(userId, pkgName);
mCurrentOngoingEvents.delete(userId, pkgName);
@@ -745,6 +748,7 @@ class Agent {
static final long WILL_NOT_CROSS_THRESHOLD = -1;
private long mCurBalance;
private long mRemainingConsumableCredits;
/**
* The maximum change in credits per second towards the upper threshold
* {@link #mUpperThreshold}. A value of 0 means the current ongoing events will never
@@ -757,15 +761,25 @@ class Agent {
* result in the app crossing the lower threshold.
*/
private long mMaxDeltaPerSecToLowerThreshold;
/**
* The maximum change in credits per second towards the highest CTP threshold below the
* remaining consumable credits (cached in {@link #mCtpThreshold}). A value of 0 means
* the current ongoing events will never result in the app crossing the lower threshold.
*/
private long mMaxDeltaPerSecToCtpThreshold;
private long mUpperThreshold;
private long mLowerThreshold;
private long mCtpThreshold;
void reset(long curBalance,
void reset(long curBalance, long remainingConsumableCredits,
@Nullable ArraySet<ActionAffordabilityNote> actionAffordabilityNotes) {
mCurBalance = curBalance;
mRemainingConsumableCredits = remainingConsumableCredits;
mMaxDeltaPerSecToUpperThreshold = mMaxDeltaPerSecToLowerThreshold = 0;
mMaxDeltaPerSecToCtpThreshold = 0;
mUpperThreshold = Long.MIN_VALUE;
mLowerThreshold = Long.MAX_VALUE;
mCtpThreshold = 0;
if (actionAffordabilityNotes != null) {
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
@@ -777,6 +791,10 @@ class Agent {
mUpperThreshold = (mUpperThreshold == Long.MIN_VALUE)
? price : Math.min(mUpperThreshold, price);
}
final long ctp = note.getCtp();
if (ctp <= mRemainingConsumableCredits) {
mCtpThreshold = Math.max(mCtpThreshold, ctp);
}
}
}
}
@@ -789,13 +807,23 @@ class Agent {
* threshold.
*/
long getTimeToCrossLowerThresholdMs() {
if (mMaxDeltaPerSecToLowerThreshold == 0) {
// Will never cross upper threshold based on current events.
if (mMaxDeltaPerSecToLowerThreshold == 0 && mMaxDeltaPerSecToCtpThreshold == 0) {
// Will never cross lower threshold based on current events.
return WILL_NOT_CROSS_THRESHOLD;
}
// deltaPerSec is a negative value, so do threshold-balance to cancel out the negative.
final long minSeconds =
(mLowerThreshold - mCurBalance) / mMaxDeltaPerSecToLowerThreshold;
long minSeconds = Long.MAX_VALUE;
if (mMaxDeltaPerSecToLowerThreshold != 0) {
// deltaPerSec is a negative value, so do threshold-balance to cancel out the
// negative.
minSeconds = (mLowerThreshold - mCurBalance) / mMaxDeltaPerSecToLowerThreshold;
}
if (mMaxDeltaPerSecToCtpThreshold != 0) {
minSeconds = Math.min(minSeconds,
// deltaPerSec is a negative value, so do threshold-balance to cancel
// out the negative.
(mCtpThreshold - mRemainingConsumableCredits)
/ mMaxDeltaPerSecToCtpThreshold);
}
return minSeconds * 1000;
}
@@ -818,10 +846,15 @@ class Agent {
@Override
public void accept(OngoingEvent ongoingEvent) {
if (mCurBalance >= mLowerThreshold && ongoingEvent.deltaPerSec < 0) {
mMaxDeltaPerSecToLowerThreshold += ongoingEvent.deltaPerSec;
} else if (mCurBalance < mUpperThreshold && ongoingEvent.deltaPerSec > 0) {
mMaxDeltaPerSecToUpperThreshold += ongoingEvent.deltaPerSec;
final long deltaPerSec = ongoingEvent.getDeltaPerSec();
if (mCurBalance >= mLowerThreshold && deltaPerSec < 0) {
mMaxDeltaPerSecToLowerThreshold += deltaPerSec;
} else if (mCurBalance < mUpperThreshold && deltaPerSec > 0) {
mMaxDeltaPerSecToUpperThreshold += deltaPerSec;
}
final long ctpPerSec = ongoingEvent.getCtpPerSec();
if (mRemainingConsumableCredits >= mCtpThreshold && deltaPerSec < 0) {
mMaxDeltaPerSecToCtpThreshold -= ctpPerSec;
}
}
}
@@ -838,8 +871,9 @@ class Agent {
mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
return;
}
mTrendCalculator.reset(
getBalanceLocked(userId, pkgName), mActionAffordabilityNotes.get(userId, pkgName));
mTrendCalculator.reset(getBalanceLocked(userId, pkgName),
mScribe.getRemainingConsumableNarcsLocked(),
mActionAffordabilityNotes.get(userId, pkgName));
ongoingEvents.forEach(mTrendCalculator);
final long lowerTimeMs = mTrendCalculator.getTimeToCrossLowerThresholdMs();
final long upperTimeMs = mTrendCalculator.getTimeToCrossUpperThresholdMs();
@@ -873,18 +907,47 @@ class Agent {
public final String tag;
@Nullable
public final EconomicPolicy.Reward reward;
public final long deltaPerSec;
@Nullable
public final EconomicPolicy.Cost actionCost;
public int refCount;
OngoingEvent(int eventId, @Nullable String tag,
@Nullable EconomicPolicy.Reward reward, long startTimeElapsed, long deltaPerSec) {
OngoingEvent(int eventId, @Nullable String tag, long startTimeElapsed,
@NonNull EconomicPolicy.Reward reward) {
this.startTimeElapsed = startTimeElapsed;
this.eventId = eventId;
this.tag = tag;
this.reward = reward;
this.deltaPerSec = deltaPerSec;
this.actionCost = null;
refCount = 1;
}
OngoingEvent(int eventId, @Nullable String tag, long startTimeElapsed,
@NonNull EconomicPolicy.Cost actionCost) {
this.startTimeElapsed = startTimeElapsed;
this.eventId = eventId;
this.tag = tag;
this.reward = null;
this.actionCost = actionCost;
refCount = 1;
}
long getDeltaPerSec() {
if (actionCost != null) {
return -actionCost.price;
}
if (reward != null) {
return reward.ongoingRewardPerSecond;
}
Slog.wtfStack(TAG, "No action or reward in ongoing event?!??!");
return 0;
}
long getCtpPerSec() {
if (actionCost != null) {
return actionCost.costToProduce;
}
return 0;
}
}
private class OngoingEventUpdater implements Consumer<OngoingEvent> {
@@ -968,7 +1031,8 @@ class Agent {
protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
for (int i = 0; i < expired.size(); ++i) {
Package p = expired.valueAt(i);
mHandler.obtainMessage(MSG_CHECK_BALANCE, p.userId, 0, p.packageName)
mHandler.obtainMessage(
MSG_CHECK_INDIVIDUAL_AFFORDABILITY, p.userId, 0, p.packageName)
.sendToTarget();
}
}
@@ -998,7 +1062,7 @@ class Agent {
note.recalculateCosts(economicPolicy, userId, pkgName);
note.setNewAffordability(
isAffordableLocked(getBalanceLocked(userId, pkgName),
note.getCachedModifiedPrice()));
note.getCachedModifiedPrice(), note.getCtp()));
mIrs.postAffordabilityChanged(userId, pkgName, note);
// Update ongoing alarm
scheduleBalanceCheckLocked(userId, pkgName);
@@ -1025,6 +1089,7 @@ class Agent {
static final class ActionAffordabilityNote {
private final EconomyManagerInternal.ActionBill mActionBill;
private final EconomyManagerInternal.AffordabilityChangeListener mListener;
private long mCtp;
private long mModifiedPrice;
private boolean mIsAffordable;
@@ -1059,22 +1124,29 @@ class Agent {
return mModifiedPrice;
}
private long getCtp() {
return mCtp;
}
@VisibleForTesting
long recalculateCosts(@NonNull EconomicPolicy economicPolicy,
void recalculateCosts(@NonNull EconomicPolicy economicPolicy,
int userId, @NonNull String pkgName) {
long modifiedPrice = 0;
long ctp = 0;
final List<EconomyManagerInternal.AnticipatedAction> anticipatedActions =
mActionBill.getAnticipatedActions();
for (int i = 0; i < anticipatedActions.size(); ++i) {
final EconomyManagerInternal.AnticipatedAction aa = anticipatedActions.get(i);
final long actionCost =
final EconomicPolicy.Cost actionCost =
economicPolicy.getCostOfAction(aa.actionId, userId, pkgName);
modifiedPrice += actionCost * aa.numInstantaneousCalls
+ actionCost * (aa.ongoingDurationMs / 1000);
modifiedPrice += actionCost.price * aa.numInstantaneousCalls
+ actionCost.price * (aa.ongoingDurationMs / 1000);
ctp += actionCost.costToProduce * aa.numInstantaneousCalls
+ actionCost.costToProduce * (aa.ongoingDurationMs / 1000);
}
mModifiedPrice = modifiedPrice;
return modifiedPrice;
mCtp = ctp;
}
boolean isCurrentlyAffordable() {
@@ -1111,7 +1183,15 @@ class Agent {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CHECK_BALANCE: {
case MSG_CHECK_ALL_AFFORDABILITY: {
synchronized (mLock) {
removeMessages(MSG_CHECK_ALL_AFFORDABILITY);
onAnythingChangedLocked(false);
}
}
break;
case MSG_CHECK_INDIVIDUAL_AFFORDABILITY: {
final int userId = msg.arg1;
final String pkgName = (String) msg.obj;
synchronized (mLock) {
@@ -1125,7 +1205,7 @@ class Agent {
final ActionAffordabilityNote note =
actionAffordabilityNotes.valueAt(i);
final boolean isAffordable = isAffordableLocked(
newBalance, note.getCachedModifiedPrice());
newBalance, note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -1180,7 +1260,12 @@ class Agent {
pw.print(" runtime=");
TimeUtils.formatDuration(nowElapsed - ongoingEvent.startTimeElapsed, pw);
pw.print(" delta/sec=");
pw.print(narcToString(ongoingEvent.deltaPerSec));
pw.print(narcToString(ongoingEvent.getDeltaPerSec()));
final long ctp = ongoingEvent.getCtpPerSec();
if (ctp != 0) {
pw.print(" ctp/sec=");
pw.print(narcToString(ongoingEvent.getCtpPerSec()));
}
pw.print(" refCount=");
pw.print(ongoingEvent.refCount);
pw.println();

View File

@@ -33,7 +33,8 @@ import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NO
import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP;
import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE;
import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP;
import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_CIRCULATION;
import static android.app.tare.EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE;
import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED;
import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP;
@@ -70,7 +71,8 @@ import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAK
import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP;
import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE;
import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP;
import static android.app.tare.EconomyManager.KEY_AM_MAX_CIRCULATION;
import static android.app.tare.EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_AM_MAX_SATIATED_BALANCE;
import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED;
import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP;
@@ -143,7 +145,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
private long mMinSatiatedBalanceExempted;
private long mMinSatiatedBalanceOther;
private long mMaxSatiatedBalance;
private long mMaxSatiatedCirculation;
private long mInitialSatiatedConsumptionLimit;
private long mHardSatiatedConsumptionLimit;
private final KeyValueListParser mParser = new KeyValueListParser(',');
private final InternalResourceService mInternalResourceService;
@@ -179,8 +182,13 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
}
@Override
long getMaxSatiatedCirculation() {
return mMaxSatiatedCirculation;
long getInitialSatiatedConsumptionLimit() {
return mInitialSatiatedConsumptionLimit;
}
@Override
long getHardSatiatedConsumptionLimit() {
return mHardSatiatedConsumptionLimit;
}
@NonNull
@@ -217,8 +225,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP));
mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_AM_MAX_SATIATED_BALANCE,
DEFAULT_AM_MAX_SATIATED_BALANCE));
mMaxSatiatedCirculation = arcToNarc(mParser.getInt(KEY_AM_MAX_CIRCULATION,
DEFAULT_AM_MAX_CIRCULATION));
mInitialSatiatedConsumptionLimit = arcToNarc(mParser.getInt(
KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT));
mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit,
arcToNarc(mParser.getInt(
KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT)));
final long exactAllowWhileIdleWakeupBasePrice = arcToNarc(
mParser.getInt(KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE,
@@ -357,7 +368,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println();
pw.decreaseIndent();
pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println();
pw.print("Max satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
pw.print("Consumption limits: [");
pw.print(narcToString(mInitialSatiatedConsumptionLimit));
pw.print(", ");
pw.print(narcToString(mHardSatiatedConsumptionLimit));
pw.println("]");
pw.println();
pw.println("Actions:");

View File

@@ -34,7 +34,7 @@ public class CompleteEconomicPolicy extends EconomicPolicy {
private final SparseArray<Reward> mRewards = new SparseArray<>();
private final int[] mCostModifiers;
private long mMaxSatiatedBalance;
private long mMaxSatiatedCirculation;
private long mConsumptionLimit;
CompleteEconomicPolicy(@NonNull InternalResourceService irs) {
super(irs);
@@ -74,9 +74,9 @@ public class CompleteEconomicPolicy extends EconomicPolicy {
max = 0;
for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
max += mEnabledEconomicPolicies.valueAt(i).getMaxSatiatedCirculation();
max += mEnabledEconomicPolicies.valueAt(i).getInitialSatiatedConsumptionLimit();
}
mMaxSatiatedCirculation = max;
mConsumptionLimit = max;
}
@Override
@@ -94,8 +94,13 @@ public class CompleteEconomicPolicy extends EconomicPolicy {
}
@Override
long getMaxSatiatedCirculation() {
return mMaxSatiatedCirculation;
long getInitialSatiatedConsumptionLimit() {
return mConsumptionLimit;
}
@Override
long getHardSatiatedConsumptionLimit() {
return mConsumptionLimit;
}
@NonNull

View File

@@ -151,6 +151,16 @@ public abstract class EconomicPolicy {
}
}
static class Cost {
public final long costToProduce;
public final long price;
Cost(long costToProduce, long price) {
this.costToProduce = costToProduce;
this.price = price;
}
}
private static final Modifier[] COST_MODIFIER_BY_INDEX = new Modifier[NUM_COST_MODIFIERS];
EconomicPolicy(@NonNull InternalResourceService irs) {
@@ -193,10 +203,18 @@ public abstract class EconomicPolicy {
abstract long getMaxSatiatedBalance();
/**
* Returns the maximum number of narcs that should be in circulation at once when the device is
* at 100% battery.
* Returns the maximum number of narcs that should be consumed during a full 100% discharge
* cycle. This is the initial limit. The system may choose to increase the limit over time,
* but the increased limit should never exceed the value returned from
* {@link #getHardSatiatedConsumptionLimit()}.
*/
abstract long getMaxSatiatedCirculation();
abstract long getInitialSatiatedConsumptionLimit();
/**
* Returns the maximum number of narcs that should be consumed during a full 100% discharge
* cycle. This is the hard limit that should never be exceeded.
*/
abstract long getHardSatiatedConsumptionLimit();
/** Return the set of modifiers that should apply to this policy's costs. */
@NonNull
@@ -211,10 +229,11 @@ public abstract class EconomicPolicy {
void dump(IndentingPrintWriter pw) {
}
final long getCostOfAction(int actionId, int userId, @NonNull String pkgName) {
@NonNull
final Cost getCostOfAction(int actionId, int userId, @NonNull String pkgName) {
final Action action = getAction(actionId);
if (action == null) {
return 0;
return new Cost(0, 0);
}
long ctp = action.costToProduce;
long price = action.basePrice;
@@ -235,7 +254,7 @@ public abstract class EconomicPolicy {
(ProcessStateModifier) getModifier(COST_MODIFIER_PROCESS_STATE);
price = processStateModifier.getModifiedPrice(userId, pkgName, ctp, price);
}
return price;
return new Cost(ctp, price);
}
private static void initModifier(@Modifier.CostModifier final int modifierId,

View File

@@ -69,6 +69,7 @@ import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.tare.EconomicPolicy.Cost;
import com.android.server.tare.EconomyManagerInternal.TareStateChangeListener;
import java.io.FileDescriptor;
@@ -100,6 +101,11 @@ public class InternalResourceService extends SystemService {
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;
/**
* The battery level above which we may consider quantitative easing (increasing the consumption
* limit).
*/
private static final int QUANTITATIVE_EASING_BATTERY_THRESHOLD = 50;
private static final int PACKAGE_QUERY_FLAGS =
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_APEX;
@@ -122,44 +128,6 @@ 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<>();
@@ -266,8 +234,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_FORCE_RECLAIM = 3;
private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 4;
private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 3;
private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";
/**
@@ -362,8 +329,8 @@ public class InternalResourceService extends SystemService {
}
@GuardedBy("mLock")
long getMaxCirculationLocked() {
return mCurrentBatteryLevel * mCompleteEconomicPolicy.getMaxSatiatedCirculation() / 100;
long getConsumptionLimitLocked() {
return mCurrentBatteryLevel * mScribe.getSatiatedConsumptionLimitLocked() / 100;
}
@GuardedBy("mLock")
@@ -372,6 +339,11 @@ public class InternalResourceService extends SystemService {
/ 100;
}
@GuardedBy("mLock")
long getInitialSatiatedConsumptionLimitLocked() {
return mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
}
int getUid(final int userId, @NonNull final String pkgName) {
synchronized (mPackageToUidCache) {
Integer uid = mPackageToUidCache.get(userId, pkgName);
@@ -403,12 +375,15 @@ public class InternalResourceService extends SystemService {
void onBatteryLevelChanged() {
synchronized (mLock) {
final int newBatteryLevel = getCurrentBatteryLevel();
if (newBatteryLevel > mCurrentBatteryLevel) {
final boolean increased = newBatteryLevel > mCurrentBatteryLevel;
if (increased) {
mAgent.distributeBasicIncomeLocked(newBatteryLevel);
} else if (newBatteryLevel < mCurrentBatteryLevel) {
mHandler.obtainMessage(MSG_MAYBE_FORCE_RECLAIM).sendToTarget();
} else if (newBatteryLevel == mCurrentBatteryLevel) {
Slog.wtf(TAG, "Battery level stayed the same");
return;
}
mCurrentBatteryLevel = newBatteryLevel;
adjustCreditSupplyLocked(increased);
}
}
@@ -546,6 +521,31 @@ public class InternalResourceService extends SystemService {
}
}
/**
* Try to increase the consumption limit if apps are reaching the current limit too quickly.
*/
@GuardedBy("mLock")
void maybePerformQuantitativeEasingLocked() {
// We don't need to increase the limit if the device runs out of consumable credits
// when the battery is low.
final long remainingConsumableNarcs = mScribe.getRemainingConsumableNarcsLocked();
if (mCurrentBatteryLevel <= QUANTITATIVE_EASING_BATTERY_THRESHOLD
|| remainingConsumableNarcs > 0) {
return;
}
final long currentConsumptionLimit = mScribe.getSatiatedConsumptionLimitLocked();
final long shortfall = (mCurrentBatteryLevel - QUANTITATIVE_EASING_BATTERY_THRESHOLD)
* currentConsumptionLimit / 100;
final long newConsumptionLimit = Math.min(currentConsumptionLimit + shortfall,
mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit());
if (newConsumptionLimit != currentConsumptionLimit) {
Slog.i(TAG, "Increasing consumption limit from " + narcToString(currentConsumptionLimit)
+ " to " + narcToString(newConsumptionLimit));
mScribe.setConsumptionLimitLocked(newConsumptionLimit);
adjustCreditSupplyLocked(/* allowIncrease */ true);
}
}
void postAffordabilityChanged(final int userId, @NonNull final String pkgName,
@NonNull Agent.ActionAffordabilityNote affordabilityNote) {
if (DEBUG) {
@@ -559,6 +559,23 @@ public class InternalResourceService extends SystemService {
mHandler.obtainMessage(MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER, args).sendToTarget();
}
@GuardedBy("mLock")
private void adjustCreditSupplyLocked(boolean allowIncrease) {
final long newLimit = getConsumptionLimitLocked();
final long remainingConsumableNarcs = mScribe.getRemainingConsumableNarcsLocked();
if (remainingConsumableNarcs == newLimit) {
return;
}
if (remainingConsumableNarcs > newLimit) {
mScribe.adjustRemainingConsumableNarcsLocked(newLimit - remainingConsumableNarcs);
} else if (allowIncrease) {
final double perc = mCurrentBatteryLevel / 100d;
final long shortfall = newLimit - remainingConsumableNarcs;
mScribe.adjustRemainingConsumableNarcsLocked((long) (perc * shortfall));
}
mAgent.onCreditSupplyChanged();
}
@GuardedBy("mLock")
private void processUsageEventLocked(final int userId, @NonNull UsageEvents.Event event) {
if (!mIsEnabled) {
@@ -650,48 +667,6 @@ 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);
@@ -731,8 +706,18 @@ public class InternalResourceService extends SystemService {
final boolean isFirstSetup = !mScribe.recordExists();
if (isFirstSetup) {
mAgent.grantBirthrightsLocked();
mScribe.setConsumptionLimitLocked(
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
} else {
mScribe.loadFromDiskLocked();
if (mScribe.getSatiatedConsumptionLimitLocked()
< mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()
|| mScribe.getSatiatedConsumptionLimitLocked()
> mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) {
// Reset the consumption limit since several factors may have changed.
mScribe.setConsumptionLimitLocked(
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
}
}
scheduleUnusedWealthReclamationLocked();
}
@@ -787,14 +772,6 @@ public class InternalResourceService extends SystemService {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_MAYBE_FORCE_RECLAIM: {
removeMessages(MSG_MAYBE_FORCE_RECLAIM);
synchronized (mLock) {
maybeForceReclaimLocked();
}
}
break;
case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: {
final SomeArgs args = (SomeArgs) msg.obj;
final int userId = args.argi1;
@@ -936,12 +913,13 @@ public class InternalResourceService extends SystemService {
synchronized (mLock) {
for (int i = 0; i < projectedActions.size(); ++i) {
AnticipatedAction action = projectedActions.get(i);
final long cost = mCompleteEconomicPolicy.getCostOfAction(
final Cost cost = mCompleteEconomicPolicy.getCostOfAction(
action.actionId, userId, pkgName);
requiredBalance += cost * action.numInstantaneousCalls
+ cost * (action.ongoingDurationMs / 1000);
requiredBalance += cost.price * action.numInstantaneousCalls
+ cost.price * (action.ongoingDurationMs / 1000);
}
return mAgent.getBalanceLocked(userId, pkgName) >= requiredBalance;
return mAgent.getBalanceLocked(userId, pkgName) >= requiredBalance
&& mScribe.getRemainingConsumableNarcsLocked() >= requiredBalance;
}
}
@@ -960,14 +938,17 @@ public class InternalResourceService extends SystemService {
synchronized (mLock) {
for (int i = 0; i < projectedActions.size(); ++i) {
AnticipatedAction action = projectedActions.get(i);
final long cost = mCompleteEconomicPolicy.getCostOfAction(
final Cost cost = mCompleteEconomicPolicy.getCostOfAction(
action.actionId, userId, pkgName);
totalCostPerSecond += cost;
totalCostPerSecond += cost.price;
}
if (totalCostPerSecond == 0) {
return FOREVER_MS;
}
return mAgent.getBalanceLocked(userId, pkgName) * 1000 / totalCostPerSecond;
final long minBalance = Math.min(
mAgent.getBalanceLocked(userId, pkgName),
mScribe.getRemainingConsumableNarcsLocked());
return minBalance * 1000 / totalCostPerSecond;
}
}
@@ -1085,10 +1066,20 @@ public class InternalResourceService extends SystemService {
private void updateEconomicPolicy() {
synchronized (mLock) {
final long initialLimit =
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
final long hardLimit = mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit();
mCompleteEconomicPolicy.tearDown();
mCompleteEconomicPolicy = new CompleteEconomicPolicy(InternalResourceService.this);
if (mIsEnabled && mBootPhase >= PHASE_SYSTEM_SERVICES_READY) {
mCompleteEconomicPolicy.setup();
if (initialLimit != mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()
|| hardLimit
!= mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) {
// Reset the consumption limit since several factors may have changed.
mScribe.setConsumptionLimitLocked(
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
}
mAgent.onPricingChangedLocked();
}
}
@@ -1110,18 +1101,23 @@ public class InternalResourceService extends SystemService {
pw.print("Current battery level: ");
pw.println(mCurrentBatteryLevel);
final long maxCirculation = getMaxCirculationLocked();
pw.print("Max circulation (current/satiated): ");
pw.print(narcToString(maxCirculation));
final long consumptionLimit = getConsumptionLimitLocked();
pw.print("Consumption limit (current/initial-satiated/current-satiated): ");
pw.print(narcToString(consumptionLimit));
pw.print("/");
pw.println(narcToString(mCompleteEconomicPolicy.getMaxSatiatedCirculation()));
pw.print(narcToString(mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()));
pw.print("/");
pw.println(narcToString(mScribe.getSatiatedConsumptionLimitLocked()));
final long currentCirculation = mScribe.getNarcsInCirculationLocked();
pw.print("Current GDP: ");
pw.print(narcToString(currentCirculation));
final long remainingConsumable = mScribe.getRemainingConsumableNarcsLocked();
pw.print("Goods remaining: ");
pw.print(narcToString(remainingConsumable));
pw.print(" (");
pw.print(String.format("%.2f", 100f * currentCirculation / maxCirculation));
pw.println("% of current max)");
pw.print(String.format("%.2f", 100f * remainingConsumable / consumptionLimit));
pw.println("% of current limit)");
pw.print("Device wealth: ");
pw.println(narcToString(mScribe.getNarcsInCirculationForLoggingLocked()));
pw.println();
pw.print("Exempted apps", mExemptedApps);

View File

@@ -38,7 +38,8 @@ import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_BA
import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_CTP;
import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE;
import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP;
import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_CIRCULATION;
import static android.app.tare.EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE;
import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED;
import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP;
@@ -79,7 +80,8 @@ import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_BASE_P
import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_CTP;
import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE;
import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP;
import static android.app.tare.EconomyManager.KEY_JS_MAX_CIRCULATION;
import static android.app.tare.EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE;
import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED;
import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP;
@@ -145,7 +147,8 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
private long mMinSatiatedBalanceExempted;
private long mMinSatiatedBalanceOther;
private long mMaxSatiatedBalance;
private long mMaxSatiatedCirculation;
private long mInitialSatiatedConsumptionLimit;
private long mHardSatiatedConsumptionLimit;
private final KeyValueListParser mParser = new KeyValueListParser(',');
private final InternalResourceService mInternalResourceService;
@@ -181,8 +184,13 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
}
@Override
long getMaxSatiatedCirculation() {
return mMaxSatiatedCirculation;
long getInitialSatiatedConsumptionLimit() {
return mInitialSatiatedConsumptionLimit;
}
@Override
long getHardSatiatedConsumptionLimit() {
return mHardSatiatedConsumptionLimit;
}
@NonNull
@@ -221,8 +229,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP));
mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_JS_MAX_SATIATED_BALANCE,
DEFAULT_JS_MAX_SATIATED_BALANCE));
mMaxSatiatedCirculation = arcToNarc(mParser.getInt(KEY_JS_MAX_CIRCULATION,
DEFAULT_JS_MAX_CIRCULATION));
mInitialSatiatedConsumptionLimit = arcToNarc(mParser.getInt(
KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT));
mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit,
arcToNarc(mParser.getInt(
KEY_JS_HARD_CONSUMPTION_LIMIT, DEFAULT_JS_HARD_CONSUMPTION_LIMIT)));
mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START,
arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_MAX_START_CTP,
@@ -332,7 +343,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println();
pw.decreaseIndent();
pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println();
pw.print("Max satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
pw.print("Consumption limits: [");
pw.print(narcToString(mInitialSatiatedConsumptionLimit));
pw.print(", ");
pw.print(narcToString(mHardSatiatedConsumptionLimit));
pw.println("]");
pw.println();
pw.println("Actions:");

View File

@@ -41,14 +41,16 @@ class Ledger {
@Nullable
public final String tag;
public final long delta;
public final long ctp;
Transaction(long startTimeMs, long endTimeMs,
int eventId, @Nullable String tag, long delta) {
int eventId, @Nullable String tag, long delta, long ctp) {
this.startTimeMs = startTimeMs;
this.endTimeMs = endTimeMs;
this.eventId = eventId;
this.tag = tag;
this.delta = delta;
this.ctp = ctp;
}
}
@@ -144,7 +146,10 @@ class Ledger {
pw.print(")");
}
pw.print(" --> ");
pw.println(narcToString(transaction.delta));
pw.print(narcToString(transaction.delta));
pw.print(" (ctp=");
pw.print(narcToString(transaction.ctp));
pw.println(")");
}
}
}

View File

@@ -73,6 +73,7 @@ public class Scribe {
private static final String XML_TAG_TRANSACTION = "transaction";
private static final String XML_TAG_USER = "user";
private static final String XML_ATTR_CTP = "ctp";
private static final String XML_ATTR_DELTA = "delta";
private static final String XML_ATTR_EVENT_ID = "eventId";
private static final String XML_ATTR_TAG = "tag";
@@ -83,6 +84,8 @@ public class Scribe {
private static final String XML_ATTR_USER_ID = "userId";
private static final String XML_ATTR_VERSION = "version";
private static final String XML_ATTR_LAST_RECLAMATION_TIME = "lastReclamationTime";
private static final String XML_ATTR_REMAINING_CONSUMABLE_NARCS = "remainingConsumableNarcs";
private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit";
/** Version of the file schema. */
private static final int STATE_FILE_VERSION = 0;
@@ -95,7 +98,9 @@ public class Scribe {
@GuardedBy("mIrs.getLock()")
private long mLastReclamationTime;
@GuardedBy("mIrs.getLock()")
private long mNarcsInCirculation;
private long mSatiatedConsumptionLimit;
@GuardedBy("mIrs.getLock()")
private long mRemainingConsumableNarcs;
@GuardedBy("mIrs.getLock()")
private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>();
@@ -117,10 +122,10 @@ public class Scribe {
}
@GuardedBy("mIrs.getLock()")
void adjustNarcsInCirculationLocked(long delta) {
void adjustRemainingConsumableNarcsLocked(long delta) {
if (delta != 0) {
// No point doing any work if the change is 0.
mNarcsInCirculation += delta;
mRemainingConsumableNarcs += delta;
postWrite();
}
}
@@ -131,6 +136,11 @@ public class Scribe {
postWrite();
}
@GuardedBy("mIrs.getLock()")
long getSatiatedConsumptionLimitLocked() {
return mSatiatedConsumptionLimit;
}
@GuardedBy("mIrs.getLock()")
long getLastReclamationTimeLocked() {
return mLastReclamationTime;
@@ -153,19 +163,37 @@ public class Scribe {
return mLedgers;
}
/** Returns the total amount of narcs currently allocated to apps. */
/**
* Returns the sum of credits granted to all apps on the system. This is expensive so don't
* call it for normal operation.
*/
@GuardedBy("mIrs.getLock()")
long getNarcsInCirculationLocked() {
return mNarcsInCirculation;
long getNarcsInCirculationForLoggingLocked() {
long sum = 0;
for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) {
for (int pIdx = mLedgers.numElementsForKeyAt(uIdx) - 1; pIdx >= 0; --pIdx) {
sum += mLedgers.valueAt(uIdx, pIdx).getCurrentBalance();
}
}
return sum;
}
/** Returns the total amount of narcs that remain to be consumed. */
@GuardedBy("mIrs.getLock()")
long getRemainingConsumableNarcsLocked() {
return mRemainingConsumableNarcs;
}
@GuardedBy("mIrs.getLock()")
void loadFromDiskLocked() {
mLedgers.clear();
mNarcsInCirculation = 0;
if (!recordExists()) {
mSatiatedConsumptionLimit = mIrs.getInitialSatiatedConsumptionLimitLocked();
mRemainingConsumableNarcs = mIrs.getConsumptionLimitLocked();
return;
}
mSatiatedConsumptionLimit = 0;
mRemainingConsumableNarcs = 0;
final SparseArray<ArraySet<String>> installedPackagesPerUser = new SparseArray<>();
final List<PackageInfo> installedPackages = mIrs.getInstalledPackages();
@@ -222,6 +250,13 @@ public class Scribe {
case XML_TAG_HIGH_LEVEL_STATE:
mLastReclamationTime =
parser.getAttributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME);
mSatiatedConsumptionLimit =
parser.getAttributeLong(null, XML_ATTR_CONSUMPTION_LIMIT,
mIrs.getInitialSatiatedConsumptionLimitLocked());
final long consumptionLimit = mIrs.getConsumptionLimitLocked();
mRemainingConsumableNarcs = Math.min(consumptionLimit,
parser.getAttributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_NARCS,
consumptionLimit));
break;
case XML_TAG_USER:
earliestEndTime = Math.min(earliestEndTime,
@@ -248,6 +283,18 @@ public class Scribe {
return mStateFile.exists();
}
@GuardedBy("mIrs.getLock()")
void setConsumptionLimitLocked(long limit) {
if (mRemainingConsumableNarcs > limit) {
mRemainingConsumableNarcs = limit;
} else if (limit > mSatiatedConsumptionLimit) {
final long diff = mSatiatedConsumptionLimit - mRemainingConsumableNarcs;
mRemainingConsumableNarcs = (limit - diff);
}
mSatiatedConsumptionLimit = limit;
postWrite();
}
@GuardedBy("mIrs.getLock()")
void setLastReclamationTimeLocked(long time) {
mLastReclamationTime = time;
@@ -259,7 +306,8 @@ public class Scribe {
TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable);
TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable);
mLedgers.clear();
mNarcsInCirculation = 0;
mRemainingConsumableNarcs = 0;
mSatiatedConsumptionLimit = 0;
mLastReclamationTime = 0;
}
@@ -339,13 +387,14 @@ public class Scribe {
final long endTime = parser.getAttributeLong(null, XML_ATTR_END_TIME);
final int eventId = parser.getAttributeInt(null, XML_ATTR_EVENT_ID);
final long delta = parser.getAttributeLong(null, XML_ATTR_DELTA);
final long ctp = parser.getAttributeLong(null, XML_ATTR_CTP);
if (endTime <= endTimeCutoff) {
if (DEBUG) {
Slog.d(TAG, "Skipping event because it's too old.");
}
continue;
}
transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta));
transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta, ctp));
}
if (!isInstalled) {
@@ -395,7 +444,6 @@ public class Scribe {
final Ledger ledger = ledgerData.second;
if (ledger != null) {
mLedgers.add(curUser, ledgerData.first, ledger);
mNarcsInCirculation += Math.max(0, ledger.getCurrentBalance());
final Ledger.Transaction transaction = ledger.getEarliestTransaction();
if (transaction != null) {
earliestEndTime = Math.min(earliestEndTime, transaction.endTimeMs);
@@ -442,6 +490,9 @@ public class Scribe {
out.startTag(null, XML_TAG_HIGH_LEVEL_STATE);
out.attributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME, mLastReclamationTime);
out.attributeLong(null, XML_ATTR_CONSUMPTION_LIMIT, mSatiatedConsumptionLimit);
out.attributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_NARCS,
mRemainingConsumableNarcs);
out.endTag(null, XML_TAG_HIGH_LEVEL_STATE);
for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) {
@@ -505,6 +556,7 @@ public class Scribe {
out.attribute(null, XML_ATTR_TAG, transaction.tag);
}
out.attributeLong(null, XML_ATTR_DELTA, transaction.delta);
out.attributeLong(null, XML_ATTR_CTP, transaction.ctp);
out.endTag(null, XML_TAG_TRANSACTION);
}

View File

@@ -73,6 +73,7 @@ public class AgentTest {
.startMocking();
when(mIrs.getContext()).thenReturn(mContext);
when(mIrs.getCompleteEconomicPolicyLocked()).thenReturn(mEconomicPolicy);
when(mIrs.getLock()).thenReturn(mIrs);
when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mock(AlarmManager.class));
mScribe = new MockScribe(mIrs);
}
@@ -89,75 +90,75 @@ public class AgentTest {
Agent agent = new Agent(mIrs, mScribe);
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getMaxCirculationLocked();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance();
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5);
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 995);
transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -500);
transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(500, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L);
transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 500);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1_000_000L, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -1_000_001L);
transaction = new Ledger.Transaction(0, 0, 0, null, -1_000_001L, 1000);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(-1, ledger.getCurrentBalance());
}
@Test
public void testRecordTransaction_MaxCirculation() {
public void testRecordTransaction_MaxConsumptionLimit() {
Agent agent = new Agent(mIrs, mScribe);
Ledger ledger = new Ledger();
doReturn(1000L).when(mIrs).getMaxCirculationLocked();
doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance();
doReturn(1000L).when(mIrs).getConsumptionLimitLocked();
doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance();
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5);
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 995);
transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -500);
transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(500, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 2000);
transaction = new Ledger.Transaction(0, 0, 0, null, 2000, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
assertEquals(2500, ledger.getCurrentBalance());
// MaxCirculation can change as the battery level changes. Any already allocated ARCSs
// shouldn't be removed by recordTransaction().
doReturn(900L).when(mIrs).getMaxCirculationLocked();
// ConsumptionLimit can change as the battery level changes. Ledger balances shouldn't be
// affected.
doReturn(900L).when(mIrs).getConsumptionLimitLocked();
transaction = new Ledger.Transaction(0, 0, 0, null, 100);
transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
assertEquals(2600, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -50);
transaction = new Ledger.Transaction(0, 0, 0, null, -50, 50);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(950, ledger.getCurrentBalance());
assertEquals(2550, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -200);
transaction = new Ledger.Transaction(0, 0, 0, null, -200, 100);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(750, ledger.getCurrentBalance());
assertEquals(2350, ledger.getCurrentBalance());
doReturn(800L).when(mIrs).getMaxCirculationLocked();
doReturn(800L).when(mIrs).getConsumptionLimitLocked();
transaction = new Ledger.Transaction(0, 0, 0, null, 100);
transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(800, ledger.getCurrentBalance());
assertEquals(2450, ledger.getCurrentBalance());
}
@Test
@@ -165,33 +166,33 @@ public class AgentTest {
Agent agent = new Agent(mIrs, mScribe);
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getMaxCirculationLocked();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance();
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5);
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 995);
transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -500);
transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(500, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L);
transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 1000);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1_000, ledger.getCurrentBalance());
// Shouldn't change in normal operation, but adding test case in case it does.
doReturn(900L).when(mEconomicPolicy).getMaxSatiatedBalance();
transaction = new Ledger.Transaction(0, 0, 0, null, 500);
transaction = new Ledger.Transaction(0, 0, 0, null, 500, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1_000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -1001);
transaction = new Ledger.Transaction(0, 0, 0, null, -1001, 500);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(-1, ledger.getCurrentBalance());
}

View File

@@ -107,20 +107,32 @@ public class ScribeTest {
@Test
public void testWriteHighLevelStateToDisk() {
long lastReclamationTime = System.currentTimeMillis();
long narcsInCirculation = 2000L;
long remainingConsumableNarcs = 2000L;
long consumptionLimit = 500_000L;
when(mIrs.getConsumptionLimitLocked()).thenReturn(consumptionLimit);
Ledger ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, 2000));
ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, 2000, 0));
// Negative ledger balance shouldn't affect the total circulation value.
ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID + 1, TEST_PACKAGE);
ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, -5000));
ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, -5000, 3000));
mScribeUnderTest.setLastReclamationTimeLocked(lastReclamationTime);
mScribeUnderTest.writeImmediatelyForTesting();
mScribeUnderTest.setConsumptionLimitLocked(consumptionLimit);
mScribeUnderTest.adjustRemainingConsumableNarcsLocked(
remainingConsumableNarcs - consumptionLimit);
assertEquals(lastReclamationTime, mScribeUnderTest.getLastReclamationTimeLocked());
assertEquals(remainingConsumableNarcs,
mScribeUnderTest.getRemainingConsumableNarcsLocked());
assertEquals(consumptionLimit, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
mScribeUnderTest.writeImmediatelyForTesting();
mScribeUnderTest.loadFromDiskLocked();
assertEquals(lastReclamationTime, mScribeUnderTest.getLastReclamationTimeLocked());
assertEquals(narcsInCirculation, mScribeUnderTest.getNarcsInCirculationLocked());
assertEquals(remainingConsumableNarcs,
mScribeUnderTest.getRemainingConsumableNarcsLocked());
assertEquals(consumptionLimit, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
}
@Test
@@ -135,9 +147,9 @@ public class ScribeTest {
@Test
public void testWritingPopulatedLedgerToDisk() {
final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 0));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, -1));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 12));
mScribeUnderTest.writeImmediatelyForTesting();
mScribeUnderTest.loadFromDiskLocked();
@@ -156,11 +168,11 @@ public class ScribeTest {
addInstalledPackage(userId, pkgName);
final Ledger ledger = mScribeUnderTest.getLedgerLocked(userId, pkgName);
ledger.recordTransaction(new Ledger.Transaction(
0, 1000L * u + l, 1, null, 51L * u + l));
0, 1000L * u + l, 1, null, -51L * u + l, 50));
ledger.recordTransaction(new Ledger.Transaction(
1500L * u + l, 2000L * u + l, 2 * u + l, "green" + u + l, 52L * u + l));
1500L * u + l, 2000L * u + l, 2 * u + l, "green" + u + l, 52L * u + l, 0));
ledger.recordTransaction(new Ledger.Transaction(
2500L * u + l, 3000L * u + l, 3 * u + l, "blue" + u + l, 3L * u + l));
2500L * u + l, 3000L * u + l, 3 * u + l, "blue" + u + l, 3L * u + l, 0));
ledgers.add(userId, pkgName, ledger);
}
}
@@ -174,9 +186,9 @@ public class ScribeTest {
@Test
public void testDiscardLedgerFromDisk() {
final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 1));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 0));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 1));
mScribeUnderTest.writeImmediatelyForTesting();
mScribeUnderTest.loadFromDiskLocked();
@@ -195,9 +207,9 @@ public class ScribeTest {
public void testLoadingMissingPackageFromDisk() {
final String pkgName = TEST_PACKAGE + ".uninstalled";
final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, pkgName);
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 1));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 2));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 3));
mScribeUnderTest.writeImmediatelyForTesting();
// Package isn't installed, so make sure it's not saved to memory after loading.
@@ -209,9 +221,9 @@ public class ScribeTest {
public void testLoadingMissingUserFromDisk() {
final int userId = TEST_USER_ID + 1;
final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(userId, TEST_PACKAGE);
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 0));
ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 1));
ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 3));
mScribeUnderTest.writeImmediatelyForTesting();
// User doesn't show up with any packages, so make sure nothing is saved after loading.
@@ -219,6 +231,37 @@ public class ScribeTest {
assertLedgersEqual(new Ledger(), mScribeUnderTest.getLedgerLocked(userId, TEST_PACKAGE));
}
@Test
public void testChangingConsumable() {
assertEquals(0, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
assertEquals(0, mScribeUnderTest.getRemainingConsumableNarcsLocked());
// Limit increased, so remaining value should be adjusted as well
mScribeUnderTest.setConsumptionLimitLocked(1000);
assertEquals(1000, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
assertEquals(1000, mScribeUnderTest.getRemainingConsumableNarcsLocked());
// Limit decreased below remaining, so remaining value should be adjusted as well
mScribeUnderTest.setConsumptionLimitLocked(500);
assertEquals(500, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
assertEquals(500, mScribeUnderTest.getRemainingConsumableNarcsLocked());
mScribeUnderTest.adjustRemainingConsumableNarcsLocked(-100);
assertEquals(500, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
assertEquals(400, mScribeUnderTest.getRemainingConsumableNarcsLocked());
// Limit increased, so remaining value should be adjusted by the difference as well
mScribeUnderTest.setConsumptionLimitLocked(1000);
assertEquals(1000, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
assertEquals(900, mScribeUnderTest.getRemainingConsumableNarcsLocked());
// Limit decreased, but above remaining, so remaining value should left alone
mScribeUnderTest.setConsumptionLimitLocked(950);
assertEquals(950, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
assertEquals(900, mScribeUnderTest.getRemainingConsumableNarcsLocked());
}
private void assertLedgersEqual(Ledger expected, Ledger actual) {
if (expected == null) {
assertNull(actual);
@@ -245,6 +288,7 @@ public class ScribeTest {
assertEquals(expected.eventId, actual.eventId);
assertEquals(expected.tag, actual.tag);
assertEquals(expected.delta, actual.delta);
assertEquals(expected.ctp, actual.ctp);
}
private void addInstalledPackage(int userId, String pkgName) {

View File

@@ -65,7 +65,12 @@ public class AgentTrendCalculatorTest {
}
@Override
long getMaxSatiatedCirculation() {
long getInitialSatiatedConsumptionLimit() {
return 0;
}
@Override
long getHardSatiatedConsumptionLimit() {
return 0;
}
@@ -102,7 +107,7 @@ public class AgentTrendCalculatorTest {
TrendCalculator trendCalculator = new TrendCalculator();
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT, 20);
trendCalculator.reset(0, null);
trendCalculator.reset(0, 0, null);
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossLowerThresholdMs());
@@ -118,7 +123,7 @@ public class AgentTrendCalculatorTest {
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
}
trendCalculator.reset(1234, affordabilityNotes);
trendCalculator.reset(1234, 1234, affordabilityNotes);
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossLowerThresholdMs());
@@ -133,32 +138,28 @@ public class AgentTrendCalculatorTest {
OngoingEvent[] events = new OngoingEvent[]{
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
null, 1, 1),
1, new EconomicPolicy.Cost(1, 4)),
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
null, 2, 3),
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "3",
null, 3, -3),
2, new EconomicPolicy.Cost(3, 6)),
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "3", 3,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 0, 3, 3)),
};
trendCalculator.reset(0, null);
trendCalculator.reset(0, 100, null);
for (OngoingEvent event : events) {
trendCalculator.accept(event);
}
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(25_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
trendCalculator.reset(1234, affordabilityNotes);
trendCalculator.reset(1234, 1234, affordabilityNotes);
for (OngoingEvent event : events) {
trendCalculator.accept(event);
}
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(308_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
@@ -179,13 +180,14 @@ public class AgentTrendCalculatorTest {
// Balance is already above threshold and events are all positive delta.
// There should be no time to report.
trendCalculator.reset(1234, affordabilityNotes);
trendCalculator.reset(1234, 1234, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
null, 1, 1));
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
null, 2, 3));
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION,
3, 3, 3)));
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
@@ -196,16 +198,16 @@ public class AgentTrendCalculatorTest {
// Balance is already below threshold and events are all negative delta.
// There should be no time to report.
trendCalculator.reset(1, affordabilityNotes);
trendCalculator.reset(1, 0, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
null, 1, -1));
1, new EconomicPolicy.Cost(1, 1)));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
null, 2, -3));
2, new EconomicPolicy.Cost(3, 3)));
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
0,
trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
@@ -213,7 +215,7 @@ public class AgentTrendCalculatorTest {
}
@Test
public void testSimpleTrendToThreshold() {
public void testSimpleTrendToThreshold_Balance() {
TrendCalculator trendCalculator = new TrendCalculator();
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
@@ -227,13 +229,14 @@ public class AgentTrendCalculatorTest {
// Balance is below threshold and events are all positive delta.
// Should report the correct time to the upper threshold.
trendCalculator.reset(0, affordabilityNotes);
trendCalculator.reset(0, 1000, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1",
null, 1, 1));
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2",
null, 2, 3));
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION,
3, 3, 3)));
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
@@ -242,13 +245,13 @@ public class AgentTrendCalculatorTest {
// Balance is above the threshold and events are all negative delta.
// Should report the correct time to the lower threshold.
trendCalculator.reset(40, affordabilityNotes);
trendCalculator.reset(40, 100, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
null, 1, -1));
1, new EconomicPolicy.Cost(1, 1)));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
null, 2, -3));
2, new EconomicPolicy.Cost(3, 3)));
assertEquals(5_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
@@ -256,6 +259,123 @@ public class AgentTrendCalculatorTest {
trendCalculator.getTimeToCrossUpperThresholdMs());
}
@Test
public void testSelectCorrectThreshold_Balance() {
TrendCalculator trendCalculator = new TrendCalculator();
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 15);
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START, 10);
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 5);
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
for (ActionAffordabilityNote note : affordabilityNotes) {
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
}
// Balance is below threshold and events are all positive delta.
// Should report the correct time to the correct upper threshold.
trendCalculator.reset(0, 10_000, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(5_000, trendCalculator.getTimeToCrossUpperThresholdMs());
// Balance is above the threshold and events are all negative delta.
// Should report the correct time to the correct lower threshold.
trendCalculator.reset(30, 500, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
1, new EconomicPolicy.Cost(1, 1)));
assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
}
@Test
public void testTrendsToBothThresholds_Balance() {
TrendCalculator trendCalculator = new TrendCalculator();
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
mEconomicPolicy.mEventCosts.put(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 50);
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
for (ActionAffordabilityNote note : affordabilityNotes) {
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
}
// Balance is between both thresholds and events are mixed positive/negative delta.
// Should report the correct time to each threshold.
trendCalculator.reset(35, 10_000, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 3, 3, 3)));
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, 2,
2, 2)));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING, "3",
3, new EconomicPolicy.Cost(2, 2)));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "4",
4, new EconomicPolicy.Cost(3, 3)));
assertEquals(3_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(3_000, trendCalculator.getTimeToCrossUpperThresholdMs());
}
@Test
public void testSimpleTrendToThreshold_ConsumptionLimit() {
TrendCalculator trendCalculator = new TrendCalculator();
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
for (ActionAffordabilityNote note : affordabilityNotes) {
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
}
// Events are all negative delta. Consumable credits will run out before app's balance.
// Should report the correct time to the lower threshold.
trendCalculator.reset(10000, 40, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
1, new EconomicPolicy.Cost(1, 10)));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
2, new EconomicPolicy.Cost(3, 40)));
assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
}
@Test
public void testSelectCorrectThreshold() {
TrendCalculator trendCalculator = new TrendCalculator();
@@ -281,65 +401,42 @@ public class AgentTrendCalculatorTest {
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
}
// Balance is below threshold and events are all positive delta.
// Should report the correct time to the correct upper threshold.
trendCalculator.reset(0, affordabilityNotes);
// Balance is above threshold, consumable credits is 0, and events are all positive delta.
// There should be no time to the upper threshold since consumable credits is the limiting
// factor.
trendCalculator.reset(10_000, 0, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1",
null, 1, 1));
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
assertEquals("Expected not to cross lower threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(5_000, trendCalculator.getTimeToCrossUpperThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
// Balance is above the threshold and events are all negative delta.
// Should report the correct time to the correct lower threshold.
trendCalculator.reset(30, affordabilityNotes);
// Balance is above threshold, consumable credits is low, and events are all negative delta.
trendCalculator.reset(10_000, 4, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
null, 1, -1));
1, new EconomicPolicy.Cost(1, 10)));
assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(4000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
// Balance is above threshold, consumable credits is 0, and events are all negative delta.
// Time to the lower threshold should be 0 since consumable credits is already 0.
trendCalculator.reset(10_000, 0, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
1, new EconomicPolicy.Cost(1, 10)));
assertEquals(0, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals("Expected not to cross upper threshold",
TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
trendCalculator.getTimeToCrossUpperThresholdMs());
}
@Test
public void testTrendsToBothThresholds() {
TrendCalculator trendCalculator = new TrendCalculator();
mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
mEconomicPolicy.mEventCosts.put(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 50);
ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
new AnticipatedAction(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 1, 0))),
mock(AffordabilityChangeListener.class), mEconomicPolicy));
for (ActionAffordabilityNote note : affordabilityNotes) {
note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
}
// Balance is between both thresholds and events are mixed positive/negative delta.
// Should report the correct time to each threshold.
trendCalculator.reset(35, affordabilityNotes);
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1",
null, 1, 3));
trendCalculator.accept(
new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2",
null, 2, 2));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING, "3",
null, 3, -2));
trendCalculator.accept(
new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "4",
null, 4, -3));
assertEquals(3_000, trendCalculator.getTimeToCrossLowerThresholdMs());
assertEquals(3_000, trendCalculator.getTimeToCrossUpperThresholdMs());
}
}

View File

@@ -54,13 +54,13 @@ public class LedgerTest {
@Test
public void testMultipleTransactions() {
final Ledger ledger = new Ledger();
ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 5));
ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 5, 0));
assertEquals(5, ledger.getCurrentBalance());
assertEquals(5, ledger.get24HourSum(1, 60_000));
ledger.recordTransaction(new Ledger.Transaction(2000, 2000, 1, null, 25));
ledger.recordTransaction(new Ledger.Transaction(2000, 2000, 1, null, 25, 0));
assertEquals(30, ledger.getCurrentBalance());
assertEquals(30, ledger.get24HourSum(1, 60_000));
ledger.recordTransaction(new Ledger.Transaction(5000, 5500, 1, null, -10));
ledger.recordTransaction(new Ledger.Transaction(5000, 5500, 1, null, -10, 5));
assertEquals(20, ledger.getCurrentBalance());
assertEquals(20, ledger.get24HourSum(1, 60_000));
}
@@ -68,13 +68,13 @@ public class LedgerTest {
@Test
public void test24HourSum() {
final Ledger ledger = new Ledger();
ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 500));
ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 500, 0));
assertEquals(500, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
ledger.recordTransaction(
new Ledger.Transaction(2 * HOUR_IN_MILLIS, 3 * HOUR_IN_MILLIS, 1, null, 2500));
new Ledger.Transaction(2 * HOUR_IN_MILLIS, 3 * HOUR_IN_MILLIS, 1, null, 2500, 0));
assertEquals(3000, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
ledger.recordTransaction(
new Ledger.Transaction(4 * HOUR_IN_MILLIS, 4 * HOUR_IN_MILLIS, 1, null, 1));
new Ledger.Transaction(4 * HOUR_IN_MILLIS, 4 * HOUR_IN_MILLIS, 1, null, 1, 0));
assertEquals(3001, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
assertEquals(2501, ledger.get24HourSum(1, 25 * HOUR_IN_MILLIS));
assertEquals(2501, ledger.get24HourSum(1, 26 * HOUR_IN_MILLIS));
@@ -93,17 +93,17 @@ public class LedgerTest {
final long now = getCurrentTimeMillis();
Ledger.Transaction transaction1 = new Ledger.Transaction(
now - 48 * HOUR_IN_MILLIS, now - 40 * HOUR_IN_MILLIS, 1, null, 4800);
now - 48 * HOUR_IN_MILLIS, now - 40 * HOUR_IN_MILLIS, 1, null, 4800, 0);
Ledger.Transaction transaction2 = new Ledger.Transaction(
now - 24 * HOUR_IN_MILLIS, now - 23 * HOUR_IN_MILLIS, 1, null, 600);
now - 24 * HOUR_IN_MILLIS, now - 23 * HOUR_IN_MILLIS, 1, null, 600, 0);
Ledger.Transaction transaction3 = new Ledger.Transaction(
now - 22 * HOUR_IN_MILLIS, now - 21 * HOUR_IN_MILLIS, 1, null, 600);
now - 22 * HOUR_IN_MILLIS, now - 21 * HOUR_IN_MILLIS, 1, null, 600, 0);
// Instant event
Ledger.Transaction transaction4 = new Ledger.Transaction(
now - 20 * HOUR_IN_MILLIS, now - 20 * HOUR_IN_MILLIS, 1, null, 500);
now - 20 * HOUR_IN_MILLIS, now - 20 * HOUR_IN_MILLIS, 1, null, 500, 0);
// Recent event
Ledger.Transaction transaction5 = new Ledger.Transaction(
now - 5 * MINUTE_IN_MILLIS, now - MINUTE_IN_MILLIS, 1, null, 400);
now - 5 * MINUTE_IN_MILLIS, now - MINUTE_IN_MILLIS, 1, null, 400, 0);
ledger.recordTransaction(transaction1);
ledger.recordTransaction(transaction2);
ledger.recordTransaction(transaction3);