Loading apex/jobscheduler/service/java/com/android/server/tare/Agent.java +26 −119 Original line number Diff line number Diff line Loading @@ -76,27 +76,16 @@ class Agent { * regularly reclaiming ARCs from it. */ private static final long MIN_UNUSED_TIME_MS = 3 * 24 * HOUR_IN_MILLIS; /** * The maximum amount of time we'll keep a transaction around for. * For now, only keep transactions we actually have a use for. We can increase it if we want * to use older transactions or provide older transactions to apps. */ private static final long MAX_TRANSACTION_AGE_MS = 24 * HOUR_IN_MILLIS; /** The maximum number of transactions to dump per ledger. */ private static final int MAX_NUM_TRANSACTION_DUMP = 25; private static final String ALARM_TAG_AFFORDABILITY_CHECK = "*tare.affordability_check*"; private static final String ALARM_TAG_LEDGER_CLEANUP = "*tare.ledger_cleanup*"; private final Object mLock; private final Handler mHandler; private final InternalResourceService mIrs; private final Scribe mScribe; private final AppStandbyInternal mAppStandbyInternal; @GuardedBy("mLock") private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>(); @GuardedBy("mLock") private final SparseArrayMap<String, SparseArrayMap<String, OngoingEvent>> mCurrentOngoingEvents = new SparseArrayMap<>(); Loading @@ -113,16 +102,6 @@ class Agent { private final SparseArrayMap<String, ArraySet<ActionAffordabilityNote>> mActionAffordabilityNotes = new SparseArrayMap<>(); @GuardedBy("mLock") private long mCurrentNarcsInCirculation; /** * Listener to track and manage when we remove old transactions from ledgers. */ @GuardedBy("mLock") private final LedgerCleanupAlarmListener mLedgerCleanupAlarmListener = new LedgerCleanupAlarmListener(); /** * Listener to track and manage when apps will cross the closest affordability threshold (in * both directions). Loading Loading @@ -180,28 +159,16 @@ class Agent { }; private static final int MSG_CHECK_BALANCE = 0; private static final int MSG_CLEAN_LEDGER = 1; private static final int MSG_SET_ALARMS = 2; private static final int MSG_SET_BALANCE_ALARM = 1; Agent(@NonNull InternalResourceService irs) { Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe) { mLock = irs.getLock(); mIrs = irs; mScribe = scribe; mHandler = new AgentHandler(TareHandlerThread.get().getLooper()); mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class); } @GuardedBy("mLock") @NonNull private Ledger getLedgerLocked(final int userId, @NonNull final String pkgName) { Ledger ledger = mLedgers.get(userId, pkgName); if (ledger == null) { // TODO: load from disk ledger = new Ledger(); mLedgers.add(userId, pkgName, ledger); } return ledger; } private class TotalDeltaCalculator implements Consumer<OngoingEvent> { private Ledger mLedger; private long mNowElapsed; Loading @@ -227,7 +194,7 @@ class Agent { /** Get an app's current balance, factoring in any currently ongoing events. */ @GuardedBy("mLock") long getBalanceLocked(final int userId, @NonNull final String pkgName) { final Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); long balance = ledger.getCurrentBalance(); SparseArrayMap<String, OngoingEvent> ongoingEvents = mCurrentOngoingEvents.get(userId, pkgName); Loading @@ -241,12 +208,6 @@ class Agent { return balance; } /** Returns the total amount of narcs currently allocated to apps. */ @GuardedBy("mLock") long getCurrentCirculationLocked() { return mCurrentNarcsInCirculation; } @GuardedBy("mLock") void noteInstantaneousEventLocked(final int userId, @NonNull final String pkgName, final int eventId, @Nullable String tag) { Loading @@ -256,7 +217,7 @@ class Agent { } final long now = getCurrentTimeMillis(); final Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked(); final int eventType = getEventType(eventId); Loading Loading @@ -364,7 +325,7 @@ class Agent { for (int i = 0; i < size; ++i) { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i); final long originalBalance = getLedgerLocked(userId, pkgName).getCurrentBalance(); mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance(); wasAffordable[i] = originalBalance >= note.getCachedModifiedPrice(); } } else { Loading @@ -385,7 +346,8 @@ class Agent { for (int i = 0; i < size; ++i) { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i); note.recalculateModifiedPrice(economicPolicy, userId, pkgName); final long newBalance = getLedgerLocked(userId, pkgName).getCurrentBalance(); final long newBalance = mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance(); final boolean isAffordable = newBalance >= note.getCachedModifiedPrice(); if (wasAffordable[i] != isAffordable) { note.setNewAffordability(isAffordable); Loading Loading @@ -417,7 +379,7 @@ class Agent { for (int n = 0; n < size; ++n) { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n); final long originalBalance = getLedgerLocked(userId, pkgName).getCurrentBalance(); mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance(); wasAffordable[n] = originalBalance >= note.getCachedModifiedPrice(); } } else { Loading @@ -439,7 +401,7 @@ class Agent { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n); note.recalculateModifiedPrice(economicPolicy, userId, pkgName); final long newBalance = getLedgerLocked(userId, pkgName).getCurrentBalance(); mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance(); final boolean isAffordable = newBalance >= note.getCachedModifiedPrice(); if (wasAffordable[n] != isAffordable) { note.setNewAffordability(isAffordable); Loading Loading @@ -473,7 +435,7 @@ class Agent { return; } final Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); SparseArrayMap<String, OngoingEvent> ongoingEvents = mCurrentOngoingEvents.get(userId, pkgName); Loading Loading @@ -538,11 +500,12 @@ class Agent { } final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked(); final long maxCirculationAllowed = mIrs.getMaxCirculationLocked(); final long newArcsInCirculation = mCurrentNarcsInCirculation + transaction.delta; 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 - mCurrentNarcsInCirculation); 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) Loading @@ -569,16 +532,8 @@ class Agent { transaction.eventId, transaction.tag, newDelta); } ledger.recordTransaction(transaction); mCurrentNarcsInCirculation += transaction.delta; if (!mLedgerCleanupAlarmListener.hasAlarmScheduledLocked(userId, pkgName)) { // The earliest transaction won't change until we clean up the ledger, so no point // continuing to reschedule an existing cleanup. final long cleanupAlarmElapsed = SystemClock.elapsedRealtime() + MAX_TRANSACTION_AGE_MS - (getCurrentTimeMillis() - ledger.getEarliestTransaction().endTimeMs); mLedgerCleanupAlarmListener.addAlarmLocked(userId, pkgName, cleanupAlarmElapsed); } // TODO: save changes to disk in a background thread if (notifyOnAffordabilityChange) { mScribe.adjustNarcsInCirculationLocked(transaction.delta); if (transaction.delta != 0 && notifyOnAffordabilityChange) { final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes = mActionAffordabilityNotes.get(userId, pkgName); if (actionAffordabilityNotes != null) { Loading Loading @@ -611,7 +566,7 @@ class Agent { 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 = getLedgerLocked(userId, pkgName); 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 = Loading Loading @@ -662,7 +617,7 @@ class Agent { } final int userId = UserHandle.getUserId(pkgInfo.applicationInfo.uid); final String pkgName = pkgInfo.packageName; Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName); final double perc = batteryLevel / 100d; // TODO: maybe don't give credits to bankrupt apps until battery level >= 50% Loading Loading @@ -701,7 +656,7 @@ class Agent { continue; } final String pkgName = packageInfo.packageName; final Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); if (ledger.getCurrentBalance() > 0) { // App already got credits somehow. Move along. Slog.wtf(TAG, "App " + pkgName + " had credits before economy was set up"); Loading @@ -717,7 +672,7 @@ class Agent { @GuardedBy("mLock") void grantBirthrightLocked(final int userId, @NonNull final String pkgName) { final Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); if (ledger.getCurrentBalance() > 0) { Slog.wtf(TAG, "App " + pkgName + " had credits as soon as it was installed"); // App already got credits somehow. Move along. Loading @@ -737,7 +692,6 @@ class Agent { @GuardedBy("mLock") void onPackageRemovedLocked(final int userId, @NonNull final String pkgName) { reclaimAssetsLocked(userId, pkgName); mLedgerCleanupAlarmListener.removeAlarmLocked(userId, pkgName); mBalanceThresholdAlarmListener.removeAlarmLocked(userId, pkgName); } Loading @@ -747,19 +701,17 @@ class Agent { */ @GuardedBy("mLock") private void reclaimAssetsLocked(final int userId, @NonNull final String pkgName) { Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); if (ledger.getCurrentBalance() != 0) { mCurrentNarcsInCirculation -= ledger.getCurrentBalance(); mScribe.adjustNarcsInCirculationLocked(-ledger.getCurrentBalance()); } // TODO: delete ledger entry from disk mLedgers.delete(userId, pkgName); mScribe.discardLedgerLocked(userId, pkgName); mCurrentOngoingEvents.delete(userId, pkgName); } @GuardedBy("mLock") void onUserRemovedLocked(final int userId, @NonNull final List<String> pkgNames) { reclaimAssetsLocked(userId, pkgNames); mLedgerCleanupAlarmListener.removeAlarmsLocked(userId); mBalanceThresholdAlarmListener.removeAlarmsLocked(userId); } Loading Loading @@ -891,11 +843,8 @@ class Agent { @GuardedBy("mLock") void tearDownLocked() { mLedgers.clear(); mCurrentNarcsInCirculation = 0; mCurrentOngoingEvents.clear(); mBalanceThresholdAlarmListener.dropAllAlarmsLocked(); mLedgerCleanupAlarmListener.dropAllAlarmsLocked(); } @VisibleForTesting Loading Loading @@ -1096,7 +1045,7 @@ class Agent { mAlarmTag, this, mHandler); } } else { mHandler.sendEmptyMessageDelayed(MSG_SET_ALARMS, 30_000); mHandler.sendEmptyMessageDelayed(MSG_SET_BALANCE_ALARM, 30_000); } }); mTriggerTimeElapsed = nextTriggerTimeElapsed; Loading Loading @@ -1165,20 +1114,6 @@ class Agent { } } /** Clean up old transactions from {@link Ledger}s. */ private class LedgerCleanupAlarmListener extends AlarmQueueListener { private LedgerCleanupAlarmListener() { // We don't need to run cleanup too frequently. super(ALARM_TAG_LEDGER_CLEANUP, false, HOUR_IN_MILLIS); } @Override @GuardedBy("mLock") protected void processExpiredAlarmLocked(int userId, @NonNull String packageName) { mHandler.obtainMessage(MSG_CLEAN_LEDGER, userId, 0, packageName).sendToTarget(); } } /** Track when apps will cross the closest affordability threshold (in both directions). */ private class BalanceThresholdAlarmListener extends AlarmQueueListener { private BalanceThresholdAlarmListener() { Loading Loading @@ -1354,19 +1289,8 @@ class Agent { } break; case MSG_CLEAN_LEDGER: { final int userId = msg.arg1; final String pkgName = (String) msg.obj; synchronized (mLock) { final Ledger ledger = getLedgerLocked(userId, pkgName); ledger.removeOldTransactions(MAX_TRANSACTION_AGE_MS); } } break; case MSG_SET_ALARMS: { case MSG_SET_BALANCE_ALARM: { synchronized (mLock) { mLedgerCleanupAlarmListener.setNextAlarmLocked(); mBalanceThresholdAlarmListener.setNextAlarmLocked(); } } Loading @@ -1377,24 +1301,7 @@ class Agent { @GuardedBy("mLock") void dumpLocked(IndentingPrintWriter pw) { pw.println("Ledgers:"); pw.increaseIndent(); mLedgers.forEach((userId, pkgName, ledger) -> { pw.print(appToString(userId, pkgName)); if (mIrs.isSystem(userId, pkgName)) { pw.print(" (system)"); } pw.println(); pw.increaseIndent(); ledger.dump(pw, MAX_NUM_TRANSACTION_DUMP); pw.decreaseIndent(); }); pw.decreaseIndent(); pw.println(); mBalanceThresholdAlarmListener.dumpLocked(pw); pw.println(); mLedgerCleanupAlarmListener.dumpLocked(pw); } } apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +6 −3 Original line number Diff line number Diff line Loading @@ -109,7 +109,7 @@ public class InternalResourceService extends SystemService { @NonNull @GuardedBy("mLock") private List<PackageInfo> mPkgCache = new ArrayList<>(); private final List<PackageInfo> mPkgCache = new ArrayList<>(); /** Cached mapping of UIDs (for all users) to a list of packages in the UID. */ @GuardedBy("mLock") Loading Loading @@ -222,7 +222,7 @@ public class InternalResourceService extends SystemService { mEconomyManagerStub = new EconomyManagerStub(); mScribe = new Scribe(this); mCompleteEconomicPolicy = new CompleteEconomicPolicy(this); mAgent = new Agent(this); mAgent = new Agent(this, mScribe); mConfigObserver = new ConfigObserver(mHandler, context); Loading Loading @@ -859,7 +859,7 @@ public class InternalResourceService extends SystemService { pw.print("/"); pw.println(narcToString(mCompleteEconomicPolicy.getMaxSatiatedCirculation())); final long currentCirculation = mAgent.getCurrentCirculationLocked(); final long currentCirculation = mScribe.getNarcsInCirculationLocked(); pw.print("Current GDP: "); pw.print(narcToString(currentCirculation)); pw.print(" ("); Loading @@ -869,6 +869,9 @@ public class InternalResourceService extends SystemService { pw.println(); mCompleteEconomicPolicy.dump(pw); pw.println(); mScribe.dumpLocked(pw); pw.println(); mAgent.dumpLocked(pw); } Loading apex/jobscheduler/service/java/com/android/server/tare/Scribe.java +105 −1 Original line number Diff line number Diff line Loading @@ -16,7 +16,14 @@ package com.android.server.tare; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static com.android.server.tare.TareUtils.appToString; import android.annotation.NonNull; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.SparseArrayMap; import com.android.internal.annotations.GuardedBy; Loading @@ -28,27 +35,124 @@ public class Scribe { private static final boolean DEBUG = InternalResourceService.DEBUG || Log.isLoggable(TAG, Log.DEBUG); /** The maximum number of transactions to dump per ledger. */ private static final int MAX_NUM_TRANSACTION_DUMP = 25; /** * The maximum amount of time we'll keep a transaction around for. * For now, only keep transactions we actually have a use for. We can increase it if we want * to use older transactions or provide older transactions to apps. */ private static final long MAX_TRANSACTION_AGE_MS = 24 * HOUR_IN_MILLIS; private final InternalResourceService mIrs; @GuardedBy("mIrs.mLock") private long mLastReclamationTime; @GuardedBy("mIrs.mLock") private long mNarcsInCirculation; @GuardedBy("mIrs.mLock") private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>(); private final Runnable mCleanRunnable = this::cleanupLedgers; Scribe(InternalResourceService irs) { mIrs = irs; } @GuardedBy("mIrs.mLock") void adjustNarcsInCirculationLocked(long delta) { if (delta != 0) { // No point doing any work if the change is 0. mNarcsInCirculation += delta; } } @GuardedBy("mIrs.mLock") void discardLedgerLocked(final int userId, @NonNull final String pkgName) { mLedgers.delete(userId, pkgName); } @GuardedBy("mIrs.mLock") long getLastReclamationTimeLocked() { return mLastReclamationTime; } @GuardedBy("InternalResourceService.mLock") @GuardedBy("mIrs.mLock") @NonNull Ledger getLedgerLocked(final int userId, @NonNull final String pkgName) { Ledger ledger = mLedgers.get(userId, pkgName); if (ledger == null) { ledger = new Ledger(); mLedgers.add(userId, pkgName, ledger); } return ledger; } /** Returns the total amount of narcs currently allocated to apps. */ @GuardedBy("mIrs.mLock") long getNarcsInCirculationLocked() { return mNarcsInCirculation; } @GuardedBy("mIrs.mLock") void setLastReclamationTimeLocked(long time) { mLastReclamationTime = time; } @GuardedBy("mIrs.mLock") void tearDownLocked() { mLedgers.clear(); mNarcsInCirculation = 0; mLastReclamationTime = 0; } private void scheduleCleanup(long earliestEndTime) { if (earliestEndTime == Long.MAX_VALUE) { return; } // This is just cleanup to manage memory. We don't need to do it too often or at the exact // intended real time, so the delay that comes from using the Handler (and is limited // to uptime) should be fine. final long delayMs = Math.max(HOUR_IN_MILLIS, earliestEndTime + MAX_TRANSACTION_AGE_MS - System.currentTimeMillis()); TareHandlerThread.getHandler().postDelayed(mCleanRunnable, delayMs); } private void cleanupLedgers() { synchronized (mIrs.getLock()) { TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable); long earliestEndTime = Long.MAX_VALUE; for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) { final int userId = mLedgers.keyAt(uIdx); for (int pIdx = mLedgers.numElementsForKey(userId) - 1; pIdx >= 0; --pIdx) { final String pkgName = mLedgers.keyAt(uIdx, pIdx); final Ledger ledger = mLedgers.get(userId, pkgName); ledger.removeOldTransactions(MAX_TRANSACTION_AGE_MS); Ledger.Transaction transaction = ledger.getEarliestTransaction(); if (transaction != null) { earliestEndTime = Math.min(earliestEndTime, transaction.endTimeMs); } } } scheduleCleanup(earliestEndTime); } } @GuardedBy("mIrs.mLock") void dumpLocked(IndentingPrintWriter pw) { pw.println("Ledgers:"); pw.increaseIndent(); mLedgers.forEach((userId, pkgName, ledger) -> { pw.print(appToString(userId, pkgName)); if (mIrs.isSystem(userId, pkgName)) { pw.print(" (system)"); } pw.println(); pw.increaseIndent(); ledger.dump(pw, MAX_NUM_TRANSACTION_DUMP); pw.decreaseIndent(); }); pw.decreaseIndent(); } } services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java +12 −3 Original line number Diff line number Diff line Loading @@ -51,6 +51,14 @@ public class AgentTest { @Mock private InternalResourceService mIrs; private Scribe mScribe; private static class MockScribe extends Scribe { MockScribe(InternalResourceService irs) { super(irs); } } @Before public void setUp() { mMockingSession = mockitoSession() Loading @@ -61,6 +69,7 @@ public class AgentTest { when(mIrs.getContext()).thenReturn(mContext); when(mIrs.getCompleteEconomicPolicyLocked()).thenReturn(mEconomicPolicy); when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mock(AlarmManager.class)); mScribe = new MockScribe(mIrs); } @After Loading @@ -72,7 +81,7 @@ public class AgentTest { @Test public void testRecordTransaction_UnderMax() { Agent agent = new Agent(mIrs); Agent agent = new Agent(mIrs, mScribe); Ledger ledger = new Ledger(); doReturn(1_000_000L).when(mIrs).getMaxCirculationLocked(); Loading Loading @@ -101,7 +110,7 @@ public class AgentTest { @Test public void testRecordTransaction_MaxCirculation() { Agent agent = new Agent(mIrs); Agent agent = new Agent(mIrs, mScribe); Ledger ledger = new Ledger(); doReturn(1000L).when(mIrs).getMaxCirculationLocked(); Loading Loading @@ -148,7 +157,7 @@ public class AgentTest { @Test public void testRecordTransaction_MaxSatiatedBalance() { Agent agent = new Agent(mIrs); Agent agent = new Agent(mIrs, mScribe); Ledger ledger = new Ledger(); doReturn(1_000_000L).when(mIrs).getMaxCirculationLocked(); Loading Loading
apex/jobscheduler/service/java/com/android/server/tare/Agent.java +26 −119 Original line number Diff line number Diff line Loading @@ -76,27 +76,16 @@ class Agent { * regularly reclaiming ARCs from it. */ private static final long MIN_UNUSED_TIME_MS = 3 * 24 * HOUR_IN_MILLIS; /** * The maximum amount of time we'll keep a transaction around for. * For now, only keep transactions we actually have a use for. We can increase it if we want * to use older transactions or provide older transactions to apps. */ private static final long MAX_TRANSACTION_AGE_MS = 24 * HOUR_IN_MILLIS; /** The maximum number of transactions to dump per ledger. */ private static final int MAX_NUM_TRANSACTION_DUMP = 25; private static final String ALARM_TAG_AFFORDABILITY_CHECK = "*tare.affordability_check*"; private static final String ALARM_TAG_LEDGER_CLEANUP = "*tare.ledger_cleanup*"; private final Object mLock; private final Handler mHandler; private final InternalResourceService mIrs; private final Scribe mScribe; private final AppStandbyInternal mAppStandbyInternal; @GuardedBy("mLock") private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>(); @GuardedBy("mLock") private final SparseArrayMap<String, SparseArrayMap<String, OngoingEvent>> mCurrentOngoingEvents = new SparseArrayMap<>(); Loading @@ -113,16 +102,6 @@ class Agent { private final SparseArrayMap<String, ArraySet<ActionAffordabilityNote>> mActionAffordabilityNotes = new SparseArrayMap<>(); @GuardedBy("mLock") private long mCurrentNarcsInCirculation; /** * Listener to track and manage when we remove old transactions from ledgers. */ @GuardedBy("mLock") private final LedgerCleanupAlarmListener mLedgerCleanupAlarmListener = new LedgerCleanupAlarmListener(); /** * Listener to track and manage when apps will cross the closest affordability threshold (in * both directions). Loading Loading @@ -180,28 +159,16 @@ class Agent { }; private static final int MSG_CHECK_BALANCE = 0; private static final int MSG_CLEAN_LEDGER = 1; private static final int MSG_SET_ALARMS = 2; private static final int MSG_SET_BALANCE_ALARM = 1; Agent(@NonNull InternalResourceService irs) { Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe) { mLock = irs.getLock(); mIrs = irs; mScribe = scribe; mHandler = new AgentHandler(TareHandlerThread.get().getLooper()); mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class); } @GuardedBy("mLock") @NonNull private Ledger getLedgerLocked(final int userId, @NonNull final String pkgName) { Ledger ledger = mLedgers.get(userId, pkgName); if (ledger == null) { // TODO: load from disk ledger = new Ledger(); mLedgers.add(userId, pkgName, ledger); } return ledger; } private class TotalDeltaCalculator implements Consumer<OngoingEvent> { private Ledger mLedger; private long mNowElapsed; Loading @@ -227,7 +194,7 @@ class Agent { /** Get an app's current balance, factoring in any currently ongoing events. */ @GuardedBy("mLock") long getBalanceLocked(final int userId, @NonNull final String pkgName) { final Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); long balance = ledger.getCurrentBalance(); SparseArrayMap<String, OngoingEvent> ongoingEvents = mCurrentOngoingEvents.get(userId, pkgName); Loading @@ -241,12 +208,6 @@ class Agent { return balance; } /** Returns the total amount of narcs currently allocated to apps. */ @GuardedBy("mLock") long getCurrentCirculationLocked() { return mCurrentNarcsInCirculation; } @GuardedBy("mLock") void noteInstantaneousEventLocked(final int userId, @NonNull final String pkgName, final int eventId, @Nullable String tag) { Loading @@ -256,7 +217,7 @@ class Agent { } final long now = getCurrentTimeMillis(); final Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked(); final int eventType = getEventType(eventId); Loading Loading @@ -364,7 +325,7 @@ class Agent { for (int i = 0; i < size; ++i) { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i); final long originalBalance = getLedgerLocked(userId, pkgName).getCurrentBalance(); mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance(); wasAffordable[i] = originalBalance >= note.getCachedModifiedPrice(); } } else { Loading @@ -385,7 +346,8 @@ class Agent { for (int i = 0; i < size; ++i) { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i); note.recalculateModifiedPrice(economicPolicy, userId, pkgName); final long newBalance = getLedgerLocked(userId, pkgName).getCurrentBalance(); final long newBalance = mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance(); final boolean isAffordable = newBalance >= note.getCachedModifiedPrice(); if (wasAffordable[i] != isAffordable) { note.setNewAffordability(isAffordable); Loading Loading @@ -417,7 +379,7 @@ class Agent { for (int n = 0; n < size; ++n) { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n); final long originalBalance = getLedgerLocked(userId, pkgName).getCurrentBalance(); mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance(); wasAffordable[n] = originalBalance >= note.getCachedModifiedPrice(); } } else { Loading @@ -439,7 +401,7 @@ class Agent { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n); note.recalculateModifiedPrice(economicPolicy, userId, pkgName); final long newBalance = getLedgerLocked(userId, pkgName).getCurrentBalance(); mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance(); final boolean isAffordable = newBalance >= note.getCachedModifiedPrice(); if (wasAffordable[n] != isAffordable) { note.setNewAffordability(isAffordable); Loading Loading @@ -473,7 +435,7 @@ class Agent { return; } final Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); SparseArrayMap<String, OngoingEvent> ongoingEvents = mCurrentOngoingEvents.get(userId, pkgName); Loading Loading @@ -538,11 +500,12 @@ class Agent { } final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked(); final long maxCirculationAllowed = mIrs.getMaxCirculationLocked(); final long newArcsInCirculation = mCurrentNarcsInCirculation + transaction.delta; 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 - mCurrentNarcsInCirculation); 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) Loading @@ -569,16 +532,8 @@ class Agent { transaction.eventId, transaction.tag, newDelta); } ledger.recordTransaction(transaction); mCurrentNarcsInCirculation += transaction.delta; if (!mLedgerCleanupAlarmListener.hasAlarmScheduledLocked(userId, pkgName)) { // The earliest transaction won't change until we clean up the ledger, so no point // continuing to reschedule an existing cleanup. final long cleanupAlarmElapsed = SystemClock.elapsedRealtime() + MAX_TRANSACTION_AGE_MS - (getCurrentTimeMillis() - ledger.getEarliestTransaction().endTimeMs); mLedgerCleanupAlarmListener.addAlarmLocked(userId, pkgName, cleanupAlarmElapsed); } // TODO: save changes to disk in a background thread if (notifyOnAffordabilityChange) { mScribe.adjustNarcsInCirculationLocked(transaction.delta); if (transaction.delta != 0 && notifyOnAffordabilityChange) { final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes = mActionAffordabilityNotes.get(userId, pkgName); if (actionAffordabilityNotes != null) { Loading Loading @@ -611,7 +566,7 @@ class Agent { 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 = getLedgerLocked(userId, pkgName); 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 = Loading Loading @@ -662,7 +617,7 @@ class Agent { } final int userId = UserHandle.getUserId(pkgInfo.applicationInfo.uid); final String pkgName = pkgInfo.packageName; Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName); final double perc = batteryLevel / 100d; // TODO: maybe don't give credits to bankrupt apps until battery level >= 50% Loading Loading @@ -701,7 +656,7 @@ class Agent { continue; } final String pkgName = packageInfo.packageName; final Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); if (ledger.getCurrentBalance() > 0) { // App already got credits somehow. Move along. Slog.wtf(TAG, "App " + pkgName + " had credits before economy was set up"); Loading @@ -717,7 +672,7 @@ class Agent { @GuardedBy("mLock") void grantBirthrightLocked(final int userId, @NonNull final String pkgName) { final Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); if (ledger.getCurrentBalance() > 0) { Slog.wtf(TAG, "App " + pkgName + " had credits as soon as it was installed"); // App already got credits somehow. Move along. Loading @@ -737,7 +692,6 @@ class Agent { @GuardedBy("mLock") void onPackageRemovedLocked(final int userId, @NonNull final String pkgName) { reclaimAssetsLocked(userId, pkgName); mLedgerCleanupAlarmListener.removeAlarmLocked(userId, pkgName); mBalanceThresholdAlarmListener.removeAlarmLocked(userId, pkgName); } Loading @@ -747,19 +701,17 @@ class Agent { */ @GuardedBy("mLock") private void reclaimAssetsLocked(final int userId, @NonNull final String pkgName) { Ledger ledger = getLedgerLocked(userId, pkgName); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); if (ledger.getCurrentBalance() != 0) { mCurrentNarcsInCirculation -= ledger.getCurrentBalance(); mScribe.adjustNarcsInCirculationLocked(-ledger.getCurrentBalance()); } // TODO: delete ledger entry from disk mLedgers.delete(userId, pkgName); mScribe.discardLedgerLocked(userId, pkgName); mCurrentOngoingEvents.delete(userId, pkgName); } @GuardedBy("mLock") void onUserRemovedLocked(final int userId, @NonNull final List<String> pkgNames) { reclaimAssetsLocked(userId, pkgNames); mLedgerCleanupAlarmListener.removeAlarmsLocked(userId); mBalanceThresholdAlarmListener.removeAlarmsLocked(userId); } Loading Loading @@ -891,11 +843,8 @@ class Agent { @GuardedBy("mLock") void tearDownLocked() { mLedgers.clear(); mCurrentNarcsInCirculation = 0; mCurrentOngoingEvents.clear(); mBalanceThresholdAlarmListener.dropAllAlarmsLocked(); mLedgerCleanupAlarmListener.dropAllAlarmsLocked(); } @VisibleForTesting Loading Loading @@ -1096,7 +1045,7 @@ class Agent { mAlarmTag, this, mHandler); } } else { mHandler.sendEmptyMessageDelayed(MSG_SET_ALARMS, 30_000); mHandler.sendEmptyMessageDelayed(MSG_SET_BALANCE_ALARM, 30_000); } }); mTriggerTimeElapsed = nextTriggerTimeElapsed; Loading Loading @@ -1165,20 +1114,6 @@ class Agent { } } /** Clean up old transactions from {@link Ledger}s. */ private class LedgerCleanupAlarmListener extends AlarmQueueListener { private LedgerCleanupAlarmListener() { // We don't need to run cleanup too frequently. super(ALARM_TAG_LEDGER_CLEANUP, false, HOUR_IN_MILLIS); } @Override @GuardedBy("mLock") protected void processExpiredAlarmLocked(int userId, @NonNull String packageName) { mHandler.obtainMessage(MSG_CLEAN_LEDGER, userId, 0, packageName).sendToTarget(); } } /** Track when apps will cross the closest affordability threshold (in both directions). */ private class BalanceThresholdAlarmListener extends AlarmQueueListener { private BalanceThresholdAlarmListener() { Loading Loading @@ -1354,19 +1289,8 @@ class Agent { } break; case MSG_CLEAN_LEDGER: { final int userId = msg.arg1; final String pkgName = (String) msg.obj; synchronized (mLock) { final Ledger ledger = getLedgerLocked(userId, pkgName); ledger.removeOldTransactions(MAX_TRANSACTION_AGE_MS); } } break; case MSG_SET_ALARMS: { case MSG_SET_BALANCE_ALARM: { synchronized (mLock) { mLedgerCleanupAlarmListener.setNextAlarmLocked(); mBalanceThresholdAlarmListener.setNextAlarmLocked(); } } Loading @@ -1377,24 +1301,7 @@ class Agent { @GuardedBy("mLock") void dumpLocked(IndentingPrintWriter pw) { pw.println("Ledgers:"); pw.increaseIndent(); mLedgers.forEach((userId, pkgName, ledger) -> { pw.print(appToString(userId, pkgName)); if (mIrs.isSystem(userId, pkgName)) { pw.print(" (system)"); } pw.println(); pw.increaseIndent(); ledger.dump(pw, MAX_NUM_TRANSACTION_DUMP); pw.decreaseIndent(); }); pw.decreaseIndent(); pw.println(); mBalanceThresholdAlarmListener.dumpLocked(pw); pw.println(); mLedgerCleanupAlarmListener.dumpLocked(pw); } }
apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +6 −3 Original line number Diff line number Diff line Loading @@ -109,7 +109,7 @@ public class InternalResourceService extends SystemService { @NonNull @GuardedBy("mLock") private List<PackageInfo> mPkgCache = new ArrayList<>(); private final List<PackageInfo> mPkgCache = new ArrayList<>(); /** Cached mapping of UIDs (for all users) to a list of packages in the UID. */ @GuardedBy("mLock") Loading Loading @@ -222,7 +222,7 @@ public class InternalResourceService extends SystemService { mEconomyManagerStub = new EconomyManagerStub(); mScribe = new Scribe(this); mCompleteEconomicPolicy = new CompleteEconomicPolicy(this); mAgent = new Agent(this); mAgent = new Agent(this, mScribe); mConfigObserver = new ConfigObserver(mHandler, context); Loading Loading @@ -859,7 +859,7 @@ public class InternalResourceService extends SystemService { pw.print("/"); pw.println(narcToString(mCompleteEconomicPolicy.getMaxSatiatedCirculation())); final long currentCirculation = mAgent.getCurrentCirculationLocked(); final long currentCirculation = mScribe.getNarcsInCirculationLocked(); pw.print("Current GDP: "); pw.print(narcToString(currentCirculation)); pw.print(" ("); Loading @@ -869,6 +869,9 @@ public class InternalResourceService extends SystemService { pw.println(); mCompleteEconomicPolicy.dump(pw); pw.println(); mScribe.dumpLocked(pw); pw.println(); mAgent.dumpLocked(pw); } Loading
apex/jobscheduler/service/java/com/android/server/tare/Scribe.java +105 −1 Original line number Diff line number Diff line Loading @@ -16,7 +16,14 @@ package com.android.server.tare; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static com.android.server.tare.TareUtils.appToString; import android.annotation.NonNull; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.SparseArrayMap; import com.android.internal.annotations.GuardedBy; Loading @@ -28,27 +35,124 @@ public class Scribe { private static final boolean DEBUG = InternalResourceService.DEBUG || Log.isLoggable(TAG, Log.DEBUG); /** The maximum number of transactions to dump per ledger. */ private static final int MAX_NUM_TRANSACTION_DUMP = 25; /** * The maximum amount of time we'll keep a transaction around for. * For now, only keep transactions we actually have a use for. We can increase it if we want * to use older transactions or provide older transactions to apps. */ private static final long MAX_TRANSACTION_AGE_MS = 24 * HOUR_IN_MILLIS; private final InternalResourceService mIrs; @GuardedBy("mIrs.mLock") private long mLastReclamationTime; @GuardedBy("mIrs.mLock") private long mNarcsInCirculation; @GuardedBy("mIrs.mLock") private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>(); private final Runnable mCleanRunnable = this::cleanupLedgers; Scribe(InternalResourceService irs) { mIrs = irs; } @GuardedBy("mIrs.mLock") void adjustNarcsInCirculationLocked(long delta) { if (delta != 0) { // No point doing any work if the change is 0. mNarcsInCirculation += delta; } } @GuardedBy("mIrs.mLock") void discardLedgerLocked(final int userId, @NonNull final String pkgName) { mLedgers.delete(userId, pkgName); } @GuardedBy("mIrs.mLock") long getLastReclamationTimeLocked() { return mLastReclamationTime; } @GuardedBy("InternalResourceService.mLock") @GuardedBy("mIrs.mLock") @NonNull Ledger getLedgerLocked(final int userId, @NonNull final String pkgName) { Ledger ledger = mLedgers.get(userId, pkgName); if (ledger == null) { ledger = new Ledger(); mLedgers.add(userId, pkgName, ledger); } return ledger; } /** Returns the total amount of narcs currently allocated to apps. */ @GuardedBy("mIrs.mLock") long getNarcsInCirculationLocked() { return mNarcsInCirculation; } @GuardedBy("mIrs.mLock") void setLastReclamationTimeLocked(long time) { mLastReclamationTime = time; } @GuardedBy("mIrs.mLock") void tearDownLocked() { mLedgers.clear(); mNarcsInCirculation = 0; mLastReclamationTime = 0; } private void scheduleCleanup(long earliestEndTime) { if (earliestEndTime == Long.MAX_VALUE) { return; } // This is just cleanup to manage memory. We don't need to do it too often or at the exact // intended real time, so the delay that comes from using the Handler (and is limited // to uptime) should be fine. final long delayMs = Math.max(HOUR_IN_MILLIS, earliestEndTime + MAX_TRANSACTION_AGE_MS - System.currentTimeMillis()); TareHandlerThread.getHandler().postDelayed(mCleanRunnable, delayMs); } private void cleanupLedgers() { synchronized (mIrs.getLock()) { TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable); long earliestEndTime = Long.MAX_VALUE; for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) { final int userId = mLedgers.keyAt(uIdx); for (int pIdx = mLedgers.numElementsForKey(userId) - 1; pIdx >= 0; --pIdx) { final String pkgName = mLedgers.keyAt(uIdx, pIdx); final Ledger ledger = mLedgers.get(userId, pkgName); ledger.removeOldTransactions(MAX_TRANSACTION_AGE_MS); Ledger.Transaction transaction = ledger.getEarliestTransaction(); if (transaction != null) { earliestEndTime = Math.min(earliestEndTime, transaction.endTimeMs); } } } scheduleCleanup(earliestEndTime); } } @GuardedBy("mIrs.mLock") void dumpLocked(IndentingPrintWriter pw) { pw.println("Ledgers:"); pw.increaseIndent(); mLedgers.forEach((userId, pkgName, ledger) -> { pw.print(appToString(userId, pkgName)); if (mIrs.isSystem(userId, pkgName)) { pw.print(" (system)"); } pw.println(); pw.increaseIndent(); ledger.dump(pw, MAX_NUM_TRANSACTION_DUMP); pw.decreaseIndent(); }); pw.decreaseIndent(); } }
services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java +12 −3 Original line number Diff line number Diff line Loading @@ -51,6 +51,14 @@ public class AgentTest { @Mock private InternalResourceService mIrs; private Scribe mScribe; private static class MockScribe extends Scribe { MockScribe(InternalResourceService irs) { super(irs); } } @Before public void setUp() { mMockingSession = mockitoSession() Loading @@ -61,6 +69,7 @@ public class AgentTest { when(mIrs.getContext()).thenReturn(mContext); when(mIrs.getCompleteEconomicPolicyLocked()).thenReturn(mEconomicPolicy); when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mock(AlarmManager.class)); mScribe = new MockScribe(mIrs); } @After Loading @@ -72,7 +81,7 @@ public class AgentTest { @Test public void testRecordTransaction_UnderMax() { Agent agent = new Agent(mIrs); Agent agent = new Agent(mIrs, mScribe); Ledger ledger = new Ledger(); doReturn(1_000_000L).when(mIrs).getMaxCirculationLocked(); Loading Loading @@ -101,7 +110,7 @@ public class AgentTest { @Test public void testRecordTransaction_MaxCirculation() { Agent agent = new Agent(mIrs); Agent agent = new Agent(mIrs, mScribe); Ledger ledger = new Ledger(); doReturn(1000L).when(mIrs).getMaxCirculationLocked(); Loading Loading @@ -148,7 +157,7 @@ public class AgentTest { @Test public void testRecordTransaction_MaxSatiatedBalance() { Agent agent = new Agent(mIrs); Agent agent = new Agent(mIrs, mScribe); Ledger ledger = new Ledger(); doReturn(1_000_000L).when(mIrs).getMaxCirculationLocked(); Loading