• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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