1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.tare; 18 19 import static android.text.format.DateUtils.HOUR_IN_MILLIS; 20 21 import static com.android.server.tare.TareUtils.cakeToString; 22 import static com.android.server.tare.TareUtils.dumpTime; 23 import static com.android.server.tare.TareUtils.getCurrentTimeMillis; 24 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.util.IndentingPrintWriter; 28 import android.util.SparseLongArray; 29 30 import java.util.ArrayList; 31 import java.util.List; 32 33 /** 34 * Ledger to track the last recorded balance and recent activities of an app. 35 */ 36 class Ledger { 37 static class Transaction { 38 public final long startTimeMs; 39 public final long endTimeMs; 40 public final int eventId; 41 @Nullable 42 public final String tag; 43 public final long delta; 44 public final long ctp; 45 Transaction(long startTimeMs, long endTimeMs, int eventId, @Nullable String tag, long delta, long ctp)46 Transaction(long startTimeMs, long endTimeMs, 47 int eventId, @Nullable String tag, long delta, long ctp) { 48 this.startTimeMs = startTimeMs; 49 this.endTimeMs = endTimeMs; 50 this.eventId = eventId; 51 this.tag = tag; 52 this.delta = delta; 53 this.ctp = ctp; 54 } 55 } 56 57 /** Last saved balance. This doesn't take currently ongoing events into account. */ 58 private long mCurrentBalance = 0; 59 private final List<Transaction> mTransactions = new ArrayList<>(); 60 private final SparseLongArray mCumulativeDeltaPerReason = new SparseLongArray(); 61 private long mEarliestSumTime; 62 Ledger()63 Ledger() { 64 } 65 Ledger(long currentBalance, @NonNull List<Transaction> transactions)66 Ledger(long currentBalance, @NonNull List<Transaction> transactions) { 67 mCurrentBalance = currentBalance; 68 mTransactions.addAll(transactions); 69 } 70 getCurrentBalance()71 long getCurrentBalance() { 72 return mCurrentBalance; 73 } 74 75 @Nullable getEarliestTransaction()76 Transaction getEarliestTransaction() { 77 if (mTransactions.size() > 0) { 78 return mTransactions.get(0); 79 } 80 return null; 81 } 82 83 @NonNull getTransactions()84 List<Transaction> getTransactions() { 85 return mTransactions; 86 } 87 recordTransaction(@onNull Transaction transaction)88 void recordTransaction(@NonNull Transaction transaction) { 89 mTransactions.add(transaction); 90 mCurrentBalance += transaction.delta; 91 92 final long sum = mCumulativeDeltaPerReason.get(transaction.eventId); 93 mCumulativeDeltaPerReason.put(transaction.eventId, sum + transaction.delta); 94 mEarliestSumTime = Math.min(mEarliestSumTime, transaction.startTimeMs); 95 } 96 get24HourSum(int eventId, final long now)97 long get24HourSum(int eventId, final long now) { 98 final long windowStartTime = now - 24 * HOUR_IN_MILLIS; 99 if (mEarliestSumTime < windowStartTime) { 100 // Need to redo sums 101 mCumulativeDeltaPerReason.clear(); 102 for (int i = mTransactions.size() - 1; i >= 0; --i) { 103 final Transaction transaction = mTransactions.get(i); 104 if (transaction.endTimeMs <= windowStartTime) { 105 break; 106 } 107 long sum = mCumulativeDeltaPerReason.get(transaction.eventId); 108 if (transaction.startTimeMs >= windowStartTime) { 109 sum += transaction.delta; 110 } else { 111 // Pro-rate durationed deltas. Intentionally floor the result. 112 sum += (long) (1.0 * (transaction.endTimeMs - windowStartTime) 113 * transaction.delta) 114 / (transaction.endTimeMs - transaction.startTimeMs); 115 } 116 mCumulativeDeltaPerReason.put(transaction.eventId, sum); 117 } 118 mEarliestSumTime = windowStartTime; 119 } 120 return mCumulativeDeltaPerReason.get(eventId); 121 } 122 123 /** Deletes transactions that are older than {@code minAgeMs}. */ removeOldTransactions(long minAgeMs)124 void removeOldTransactions(long minAgeMs) { 125 final long cutoff = getCurrentTimeMillis() - minAgeMs; 126 while (mTransactions.size() > 0 && mTransactions.get(0).endTimeMs <= cutoff) { 127 mTransactions.remove(0); 128 } 129 } 130 dump(IndentingPrintWriter pw, int numRecentTransactions)131 void dump(IndentingPrintWriter pw, int numRecentTransactions) { 132 pw.print("Current balance", cakeToString(getCurrentBalance())).println(); 133 134 final int size = mTransactions.size(); 135 for (int i = Math.max(0, size - numRecentTransactions); i < size; ++i) { 136 final Transaction transaction = mTransactions.get(i); 137 138 dumpTime(pw, transaction.startTimeMs); 139 pw.print("--"); 140 dumpTime(pw, transaction.endTimeMs); 141 pw.print(": "); 142 pw.print(EconomicPolicy.eventToString(transaction.eventId)); 143 if (transaction.tag != null) { 144 pw.print("("); 145 pw.print(transaction.tag); 146 pw.print(")"); 147 } 148 pw.print(" --> "); 149 pw.print(cakeToString(transaction.delta)); 150 pw.print(" (ctp="); 151 pw.print(cakeToString(transaction.ctp)); 152 pw.println(")"); 153 } 154 } 155 } 156