Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 891bc0e9 authored by Kweku Adams's avatar Kweku Adams Committed by Android (Google) Code Review
Browse files

Merge "Move Ledger tracking to the Scribe."

parents daed2b80 a1b93340
Loading
Loading
Loading
Loading
+26 −119
Original line number Diff line number Diff line
@@ -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<>();
@@ -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).
@@ -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;
@@ -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);
@@ -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) {
@@ -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);
@@ -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 {
@@ -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);
@@ -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 {
@@ -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);
@@ -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);
@@ -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)
@@ -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) {
@@ -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 =
@@ -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%
@@ -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");
@@ -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.
@@ -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);
    }

@@ -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);
    }

@@ -891,11 +843,8 @@ class Agent {

    @GuardedBy("mLock")
    void tearDownLocked() {
        mLedgers.clear();
        mCurrentNarcsInCirculation = 0;
        mCurrentOngoingEvents.clear();
        mBalanceThresholdAlarmListener.dropAllAlarmsLocked();
        mLedgerCleanupAlarmListener.dropAllAlarmsLocked();
    }

    @VisibleForTesting
@@ -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;
@@ -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() {
@@ -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();
                    }
                }
@@ -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);
    }
}
+6 −3
Original line number Diff line number Diff line
@@ -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")
@@ -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);

@@ -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(" (");
@@ -869,6 +869,9 @@ public class InternalResourceService extends SystemService {
            pw.println();
            mCompleteEconomicPolicy.dump(pw);

            pw.println();
            mScribe.dumpLocked(pw);

            pw.println();
            mAgent.dumpLocked(pw);
        }
+105 −1
Original line number Diff line number Diff line
@@ -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;

@@ -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();
    }
}
+12 −3
Original line number Diff line number Diff line
@@ -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()
@@ -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
@@ -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();
@@ -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();
@@ -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();