Merge "Track basic TARE statistics." into tm-dev
This commit is contained in:
@@ -73,6 +73,7 @@ class Agent {
|
|||||||
|
|
||||||
private final Object mLock;
|
private final Object mLock;
|
||||||
private final Handler mHandler;
|
private final Handler mHandler;
|
||||||
|
private final Analyst mAnalyst;
|
||||||
private final InternalResourceService mIrs;
|
private final InternalResourceService mIrs;
|
||||||
private final Scribe mScribe;
|
private final Scribe mScribe;
|
||||||
|
|
||||||
@@ -110,10 +111,11 @@ class Agent {
|
|||||||
*/
|
*/
|
||||||
private static final int MSG_CHECK_INDIVIDUAL_AFFORDABILITY = 1;
|
private static final int MSG_CHECK_INDIVIDUAL_AFFORDABILITY = 1;
|
||||||
|
|
||||||
Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe) {
|
Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe, @NonNull Analyst analyst) {
|
||||||
mLock = irs.getLock();
|
mLock = irs.getLock();
|
||||||
mIrs = irs;
|
mIrs = irs;
|
||||||
mScribe = scribe;
|
mScribe = scribe;
|
||||||
|
mAnalyst = analyst;
|
||||||
mHandler = new AgentHandler(TareHandlerThread.get().getLooper());
|
mHandler = new AgentHandler(TareHandlerThread.get().getLooper());
|
||||||
mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
|
mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
|
||||||
mBalanceThresholdAlarmQueue = new BalanceThresholdAlarmQueue(
|
mBalanceThresholdAlarmQueue = new BalanceThresholdAlarmQueue(
|
||||||
@@ -443,7 +445,7 @@ class Agent {
|
|||||||
void recordTransactionLocked(final int userId, @NonNull final String pkgName,
|
void recordTransactionLocked(final int userId, @NonNull final String pkgName,
|
||||||
@NonNull Ledger ledger, @NonNull Ledger.Transaction transaction,
|
@NonNull Ledger ledger, @NonNull Ledger.Transaction transaction,
|
||||||
final boolean notifyOnAffordabilityChange) {
|
final boolean notifyOnAffordabilityChange) {
|
||||||
if (transaction.delta == 0) {
|
if (!DEBUG && transaction.delta == 0) {
|
||||||
// Skip recording transactions with a delta of 0 to save on space.
|
// Skip recording transactions with a delta of 0 to save on space.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -471,6 +473,7 @@ class Agent {
|
|||||||
}
|
}
|
||||||
ledger.recordTransaction(transaction);
|
ledger.recordTransaction(transaction);
|
||||||
mScribe.adjustRemainingConsumableCakesLocked(-transaction.ctp);
|
mScribe.adjustRemainingConsumableCakesLocked(-transaction.ctp);
|
||||||
|
mAnalyst.noteTransaction(transaction);
|
||||||
if (transaction.delta != 0 && notifyOnAffordabilityChange) {
|
if (transaction.delta != 0 && notifyOnAffordabilityChange) {
|
||||||
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
|
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
|
||||||
mActionAffordabilityNotes.get(userId, pkgName);
|
mActionAffordabilityNotes.get(userId, pkgName);
|
||||||
|
|||||||
@@ -0,0 +1,290 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.server.tare;
|
||||||
|
|
||||||
|
import static com.android.server.tare.EconomicPolicy.TYPE_ACTION;
|
||||||
|
import static com.android.server.tare.EconomicPolicy.TYPE_REGULATION;
|
||||||
|
import static com.android.server.tare.EconomicPolicy.TYPE_REWARD;
|
||||||
|
import static com.android.server.tare.EconomicPolicy.getEventType;
|
||||||
|
import static com.android.server.tare.TareUtils.cakeToString;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.util.IndentingPrintWriter;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for maintaining statistics and analysis of TARE's performance.
|
||||||
|
*/
|
||||||
|
public class Analyst {
|
||||||
|
private static final String TAG = "TARE-" + Analyst.class.getSimpleName();
|
||||||
|
private static final boolean DEBUG = InternalResourceService.DEBUG
|
||||||
|
|| Log.isLoggable(TAG, Log.DEBUG);
|
||||||
|
|
||||||
|
private static final int NUM_PERIODS_TO_RETAIN = 8;
|
||||||
|
|
||||||
|
static final class Report {
|
||||||
|
/** How much the battery was discharged over the tracked period. */
|
||||||
|
public int cumulativeBatteryDischarge = 0;
|
||||||
|
public int currentBatteryLevel = 0;
|
||||||
|
/**
|
||||||
|
* Profit from performing actions. This excludes special circumstances where we charge the
|
||||||
|
* app
|
||||||
|
* less than the action's CTP.
|
||||||
|
*/
|
||||||
|
public long cumulativeProfit = 0;
|
||||||
|
public int numProfitableActions = 0;
|
||||||
|
/**
|
||||||
|
* Losses from performing actions for special circumstances (eg. for a TOP app) where we
|
||||||
|
* charge
|
||||||
|
* the app less than the action's CTP.
|
||||||
|
*/
|
||||||
|
public long cumulativeLoss = 0;
|
||||||
|
public int numUnprofitableActions = 0;
|
||||||
|
/**
|
||||||
|
* The total number of rewards given to apps over this period.
|
||||||
|
*/
|
||||||
|
public long cumulativeRewards = 0;
|
||||||
|
public int numRewards = 0;
|
||||||
|
/**
|
||||||
|
* Regulations that increased an app's balance.
|
||||||
|
*/
|
||||||
|
public long cumulativePositiveRegulations = 0;
|
||||||
|
public int numPositiveRegulations = 0;
|
||||||
|
/**
|
||||||
|
* Regulations that decreased an app's balance.
|
||||||
|
*/
|
||||||
|
public long cumulativeNegativeRegulations = 0;
|
||||||
|
public int numNegativeRegulations = 0;
|
||||||
|
|
||||||
|
private void clear() {
|
||||||
|
cumulativeBatteryDischarge = 0;
|
||||||
|
currentBatteryLevel = 0;
|
||||||
|
cumulativeProfit = 0;
|
||||||
|
numProfitableActions = 0;
|
||||||
|
cumulativeLoss = 0;
|
||||||
|
numUnprofitableActions = 0;
|
||||||
|
cumulativeRewards = 0;
|
||||||
|
numRewards = 0;
|
||||||
|
cumulativePositiveRegulations = 0;
|
||||||
|
numPositiveRegulations = 0;
|
||||||
|
cumulativeNegativeRegulations = 0;
|
||||||
|
numNegativeRegulations = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int mPeriodIndex = 0;
|
||||||
|
/** How much the battery was discharged over the tracked period. */
|
||||||
|
private final Report[] mReports = new Report[NUM_PERIODS_TO_RETAIN];
|
||||||
|
|
||||||
|
/** Returns the list of most recent reports, with the oldest report first. */
|
||||||
|
@NonNull
|
||||||
|
List<Report> getReports() {
|
||||||
|
final List<Report> list = new ArrayList<>(NUM_PERIODS_TO_RETAIN);
|
||||||
|
for (int i = 1; i <= NUM_PERIODS_TO_RETAIN; ++i) {
|
||||||
|
final int idx = (mPeriodIndex + i) % NUM_PERIODS_TO_RETAIN;
|
||||||
|
final Report report = mReports[idx];
|
||||||
|
if (report != null) {
|
||||||
|
list.add(report);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks the given reports instead of whatever is currently saved. Reports should be ordered
|
||||||
|
* oldest to most recent.
|
||||||
|
*/
|
||||||
|
void loadReports(@NonNull List<Report> reports) {
|
||||||
|
final int numReports = reports.size();
|
||||||
|
mPeriodIndex = Math.max(0, numReports - 1);
|
||||||
|
for (int i = 0; i < NUM_PERIODS_TO_RETAIN; ++i) {
|
||||||
|
if (i < numReports) {
|
||||||
|
mReports[i] = reports.get(i);
|
||||||
|
} else {
|
||||||
|
mReports[i] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void noteBatteryLevelChange(int newBatteryLevel) {
|
||||||
|
if (newBatteryLevel == 100 && mReports[mPeriodIndex] != null
|
||||||
|
&& mReports[mPeriodIndex].currentBatteryLevel < newBatteryLevel) {
|
||||||
|
mPeriodIndex = (mPeriodIndex + 1) % NUM_PERIODS_TO_RETAIN;
|
||||||
|
if (mReports[mPeriodIndex] != null) {
|
||||||
|
final Report report = mReports[mPeriodIndex];
|
||||||
|
report.clear();
|
||||||
|
report.currentBatteryLevel = newBatteryLevel;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mReports[mPeriodIndex] == null) {
|
||||||
|
Report report = new Report();
|
||||||
|
mReports[mPeriodIndex] = report;
|
||||||
|
report.currentBatteryLevel = newBatteryLevel;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Report report = mReports[mPeriodIndex];
|
||||||
|
if (newBatteryLevel < report.currentBatteryLevel) {
|
||||||
|
report.cumulativeBatteryDischarge += (report.currentBatteryLevel - newBatteryLevel);
|
||||||
|
}
|
||||||
|
report.currentBatteryLevel = newBatteryLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void noteTransaction(@NonNull Ledger.Transaction transaction) {
|
||||||
|
if (mReports[mPeriodIndex] == null) {
|
||||||
|
mReports[mPeriodIndex] = new Report();
|
||||||
|
}
|
||||||
|
final Report report = mReports[mPeriodIndex];
|
||||||
|
switch (getEventType(transaction.eventId)) {
|
||||||
|
case TYPE_ACTION:
|
||||||
|
// For now, assume all instances where price < CTP is a special instance.
|
||||||
|
// TODO: add an explicit signal for special circumstances
|
||||||
|
if (-transaction.delta > transaction.ctp) {
|
||||||
|
report.cumulativeProfit += (-transaction.delta - transaction.ctp);
|
||||||
|
report.numProfitableActions++;
|
||||||
|
} else if (-transaction.delta < transaction.ctp) {
|
||||||
|
report.cumulativeLoss += (transaction.ctp + transaction.delta);
|
||||||
|
report.numUnprofitableActions++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_REGULATION:
|
||||||
|
if (transaction.delta > 0) {
|
||||||
|
report.cumulativePositiveRegulations += transaction.delta;
|
||||||
|
report.numPositiveRegulations++;
|
||||||
|
} else if (transaction.delta < 0) {
|
||||||
|
report.cumulativeNegativeRegulations -= transaction.delta;
|
||||||
|
report.numNegativeRegulations++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_REWARD:
|
||||||
|
if (transaction.delta != 0) {
|
||||||
|
report.cumulativeRewards += transaction.delta;
|
||||||
|
report.numRewards++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tearDown() {
|
||||||
|
for (int i = 0; i < mReports.length; ++i) {
|
||||||
|
mReports[i] = null;
|
||||||
|
}
|
||||||
|
mPeriodIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private String padStringWithSpaces(@NonNull String text, int targetLength) {
|
||||||
|
// Make sure to have at least one space on either side.
|
||||||
|
final int padding = Math.max(2, targetLength - text.length()) >>> 1;
|
||||||
|
return " ".repeat(padding) + text + " ".repeat(padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump(IndentingPrintWriter pw) {
|
||||||
|
pw.println("Reports:");
|
||||||
|
pw.increaseIndent();
|
||||||
|
pw.print(" Total Discharge");
|
||||||
|
final int statColsLength = 47;
|
||||||
|
pw.print(padStringWithSpaces("Profit (avg/action : avg/discharge)", statColsLength));
|
||||||
|
pw.print(padStringWithSpaces("Loss (avg/action : avg/discharge)", statColsLength));
|
||||||
|
pw.print(padStringWithSpaces("Rewards (avg/reward : avg/discharge)", statColsLength));
|
||||||
|
pw.print(padStringWithSpaces("+Regs (avg/reg : avg/discharge)", statColsLength));
|
||||||
|
pw.print(padStringWithSpaces("-Regs (avg/reg : avg/discharge)", statColsLength));
|
||||||
|
pw.println();
|
||||||
|
for (int r = 0; r < NUM_PERIODS_TO_RETAIN; ++r) {
|
||||||
|
final int idx = (mPeriodIndex - r + NUM_PERIODS_TO_RETAIN) % NUM_PERIODS_TO_RETAIN;
|
||||||
|
final Report report = mReports[idx];
|
||||||
|
if (report == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pw.print("t-");
|
||||||
|
pw.print(r);
|
||||||
|
pw.print(": ");
|
||||||
|
pw.print(padStringWithSpaces(Integer.toString(report.cumulativeBatteryDischarge), 15));
|
||||||
|
if (report.numProfitableActions > 0) {
|
||||||
|
final String perDischarge = report.cumulativeBatteryDischarge > 0
|
||||||
|
? cakeToString(report.cumulativeProfit / report.cumulativeBatteryDischarge)
|
||||||
|
: "N/A";
|
||||||
|
pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
|
||||||
|
cakeToString(report.cumulativeProfit),
|
||||||
|
cakeToString(report.cumulativeProfit / report.numProfitableActions),
|
||||||
|
perDischarge),
|
||||||
|
statColsLength));
|
||||||
|
} else {
|
||||||
|
pw.print(padStringWithSpaces("N/A", statColsLength));
|
||||||
|
}
|
||||||
|
if (report.numUnprofitableActions > 0) {
|
||||||
|
final String perDischarge = report.cumulativeBatteryDischarge > 0
|
||||||
|
? cakeToString(report.cumulativeLoss / report.cumulativeBatteryDischarge)
|
||||||
|
: "N/A";
|
||||||
|
pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
|
||||||
|
cakeToString(report.cumulativeLoss),
|
||||||
|
cakeToString(report.cumulativeLoss / report.numUnprofitableActions),
|
||||||
|
perDischarge),
|
||||||
|
statColsLength));
|
||||||
|
} else {
|
||||||
|
pw.print(padStringWithSpaces("N/A", statColsLength));
|
||||||
|
}
|
||||||
|
if (report.numRewards > 0) {
|
||||||
|
final String perDischarge = report.cumulativeBatteryDischarge > 0
|
||||||
|
? cakeToString(report.cumulativeRewards / report.cumulativeBatteryDischarge)
|
||||||
|
: "N/A";
|
||||||
|
pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
|
||||||
|
cakeToString(report.cumulativeRewards),
|
||||||
|
cakeToString(report.cumulativeRewards / report.numRewards),
|
||||||
|
perDischarge),
|
||||||
|
statColsLength));
|
||||||
|
} else {
|
||||||
|
pw.print(padStringWithSpaces("N/A", statColsLength));
|
||||||
|
}
|
||||||
|
if (report.numPositiveRegulations > 0) {
|
||||||
|
final String perDischarge = report.cumulativeBatteryDischarge > 0
|
||||||
|
? cakeToString(
|
||||||
|
report.cumulativePositiveRegulations / report.cumulativeBatteryDischarge)
|
||||||
|
: "N/A";
|
||||||
|
pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
|
||||||
|
cakeToString(report.cumulativePositiveRegulations),
|
||||||
|
cakeToString(report.cumulativePositiveRegulations
|
||||||
|
/ report.numPositiveRegulations),
|
||||||
|
perDischarge),
|
||||||
|
statColsLength));
|
||||||
|
} else {
|
||||||
|
pw.print(padStringWithSpaces("N/A", statColsLength));
|
||||||
|
}
|
||||||
|
if (report.numNegativeRegulations > 0) {
|
||||||
|
final String perDischarge = report.cumulativeBatteryDischarge > 0
|
||||||
|
? cakeToString(
|
||||||
|
report.cumulativeNegativeRegulations / report.cumulativeBatteryDischarge)
|
||||||
|
: "N/A";
|
||||||
|
pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
|
||||||
|
cakeToString(report.cumulativeNegativeRegulations),
|
||||||
|
cakeToString(report.cumulativeNegativeRegulations
|
||||||
|
/ report.numNegativeRegulations),
|
||||||
|
perDischarge),
|
||||||
|
statColsLength));
|
||||||
|
} else {
|
||||||
|
pw.print(padStringWithSpaces("N/A", statColsLength));
|
||||||
|
}
|
||||||
|
pw.println();
|
||||||
|
}
|
||||||
|
pw.decreaseIndent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -121,6 +121,7 @@ public class InternalResourceService extends SystemService {
|
|||||||
private IDeviceIdleController mDeviceIdleController;
|
private IDeviceIdleController mDeviceIdleController;
|
||||||
|
|
||||||
private final Agent mAgent;
|
private final Agent mAgent;
|
||||||
|
private final Analyst mAnalyst;
|
||||||
private final ConfigObserver mConfigObserver;
|
private final ConfigObserver mConfigObserver;
|
||||||
private final EconomyManagerStub mEconomyManagerStub;
|
private final EconomyManagerStub mEconomyManagerStub;
|
||||||
private final Scribe mScribe;
|
private final Scribe mScribe;
|
||||||
@@ -254,9 +255,10 @@ public class InternalResourceService extends SystemService {
|
|||||||
mPackageManager = context.getPackageManager();
|
mPackageManager = context.getPackageManager();
|
||||||
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
|
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
|
||||||
mEconomyManagerStub = new EconomyManagerStub();
|
mEconomyManagerStub = new EconomyManagerStub();
|
||||||
mScribe = new Scribe(this);
|
mAnalyst = new Analyst();
|
||||||
|
mScribe = new Scribe(this, mAnalyst);
|
||||||
mCompleteEconomicPolicy = new CompleteEconomicPolicy(this);
|
mCompleteEconomicPolicy = new CompleteEconomicPolicy(this);
|
||||||
mAgent = new Agent(this, mScribe);
|
mAgent = new Agent(this, mScribe, mAnalyst);
|
||||||
|
|
||||||
mConfigObserver = new ConfigObserver(mHandler, context);
|
mConfigObserver = new ConfigObserver(mHandler, context);
|
||||||
|
|
||||||
@@ -375,6 +377,7 @@ public class InternalResourceService extends SystemService {
|
|||||||
void onBatteryLevelChanged() {
|
void onBatteryLevelChanged() {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
final int newBatteryLevel = getCurrentBatteryLevel();
|
final int newBatteryLevel = getCurrentBatteryLevel();
|
||||||
|
mAnalyst.noteBatteryLevelChange(newBatteryLevel);
|
||||||
final boolean increased = newBatteryLevel > mCurrentBatteryLevel;
|
final boolean increased = newBatteryLevel > mCurrentBatteryLevel;
|
||||||
if (increased) {
|
if (increased) {
|
||||||
mAgent.distributeBasicIncomeLocked(newBatteryLevel);
|
mAgent.distributeBasicIncomeLocked(newBatteryLevel);
|
||||||
@@ -741,6 +744,7 @@ public class InternalResourceService extends SystemService {
|
|||||||
}
|
}
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
mAgent.tearDownLocked();
|
mAgent.tearDownLocked();
|
||||||
|
mAnalyst.tearDown();
|
||||||
mCompleteEconomicPolicy.tearDown();
|
mCompleteEconomicPolicy.tearDown();
|
||||||
mExemptedApps.clear();
|
mExemptedApps.clear();
|
||||||
mExemptListLoaded = false;
|
mExemptListLoaded = false;
|
||||||
@@ -1145,6 +1149,9 @@ public class InternalResourceService extends SystemService {
|
|||||||
|
|
||||||
pw.println();
|
pw.println();
|
||||||
mAgent.dumpLocked(pw);
|
mAgent.dumpLocked(pw);
|
||||||
|
|
||||||
|
pw.println();
|
||||||
|
mAnalyst.dump(pw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ public class Scribe {
|
|||||||
private static final String XML_TAG_TARE = "tare";
|
private static final String XML_TAG_TARE = "tare";
|
||||||
private static final String XML_TAG_TRANSACTION = "transaction";
|
private static final String XML_TAG_TRANSACTION = "transaction";
|
||||||
private static final String XML_TAG_USER = "user";
|
private static final String XML_TAG_USER = "user";
|
||||||
|
private static final String XML_TAG_PERIOD_REPORT = "report";
|
||||||
|
|
||||||
private static final String XML_ATTR_CTP = "ctp";
|
private static final String XML_ATTR_CTP = "ctp";
|
||||||
private static final String XML_ATTR_DELTA = "delta";
|
private static final String XML_ATTR_DELTA = "delta";
|
||||||
@@ -86,6 +87,18 @@ public class Scribe {
|
|||||||
private static final String XML_ATTR_LAST_RECLAMATION_TIME = "lastReclamationTime";
|
private static final String XML_ATTR_LAST_RECLAMATION_TIME = "lastReclamationTime";
|
||||||
private static final String XML_ATTR_REMAINING_CONSUMABLE_CAKES = "remainingConsumableCakes";
|
private static final String XML_ATTR_REMAINING_CONSUMABLE_CAKES = "remainingConsumableCakes";
|
||||||
private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit";
|
private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit";
|
||||||
|
private static final String XML_ATTR_PR_DISCHARGE = "discharge";
|
||||||
|
private static final String XML_ATTR_PR_BATTERY_LEVEL = "batteryLevel";
|
||||||
|
private static final String XML_ATTR_PR_PROFIT = "profit";
|
||||||
|
private static final String XML_ATTR_PR_NUM_PROFIT = "numProfits";
|
||||||
|
private static final String XML_ATTR_PR_LOSS = "loss";
|
||||||
|
private static final String XML_ATTR_PR_NUM_LOSS = "numLoss";
|
||||||
|
private static final String XML_ATTR_PR_REWARDS = "rewards";
|
||||||
|
private static final String XML_ATTR_PR_NUM_REWARDS = "numRewards";
|
||||||
|
private static final String XML_ATTR_PR_POS_REGULATIONS = "posRegulations";
|
||||||
|
private static final String XML_ATTR_PR_NUM_POS_REGULATIONS = "numPosRegulations";
|
||||||
|
private static final String XML_ATTR_PR_NEG_REGULATIONS = "negRegulations";
|
||||||
|
private static final String XML_ATTR_PR_NUM_NEG_REGULATIONS = "numNegRegulations";
|
||||||
|
|
||||||
/** Version of the file schema. */
|
/** Version of the file schema. */
|
||||||
private static final int STATE_FILE_VERSION = 0;
|
private static final int STATE_FILE_VERSION = 0;
|
||||||
@@ -94,6 +107,7 @@ public class Scribe {
|
|||||||
|
|
||||||
private final AtomicFile mStateFile;
|
private final AtomicFile mStateFile;
|
||||||
private final InternalResourceService mIrs;
|
private final InternalResourceService mIrs;
|
||||||
|
private final Analyst mAnalyst;
|
||||||
|
|
||||||
@GuardedBy("mIrs.getLock()")
|
@GuardedBy("mIrs.getLock()")
|
||||||
private long mLastReclamationTime;
|
private long mLastReclamationTime;
|
||||||
@@ -107,13 +121,14 @@ public class Scribe {
|
|||||||
private final Runnable mCleanRunnable = this::cleanupLedgers;
|
private final Runnable mCleanRunnable = this::cleanupLedgers;
|
||||||
private final Runnable mWriteRunnable = this::writeState;
|
private final Runnable mWriteRunnable = this::writeState;
|
||||||
|
|
||||||
Scribe(InternalResourceService irs) {
|
Scribe(InternalResourceService irs, Analyst analyst) {
|
||||||
this(irs, Environment.getDataSystemDirectory());
|
this(irs, analyst, Environment.getDataSystemDirectory());
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
Scribe(InternalResourceService irs, File dataDir) {
|
Scribe(InternalResourceService irs, Analyst analyst, File dataDir) {
|
||||||
mIrs = irs;
|
mIrs = irs;
|
||||||
|
mAnalyst = analyst;
|
||||||
|
|
||||||
final File tareDir = new File(dataDir, "tare");
|
final File tareDir = new File(dataDir, "tare");
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
@@ -210,6 +225,7 @@ public class Scribe {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<Analyst.Report> reports = new ArrayList<>();
|
||||||
try (FileInputStream fis = mStateFile.openRead()) {
|
try (FileInputStream fis = mStateFile.openRead()) {
|
||||||
TypedXmlPullParser parser = Xml.resolvePullParser(fis);
|
TypedXmlPullParser parser = Xml.resolvePullParser(fis);
|
||||||
|
|
||||||
@@ -263,11 +279,15 @@ public class Scribe {
|
|||||||
readUserFromXmlLocked(
|
readUserFromXmlLocked(
|
||||||
parser, installedPackagesPerUser, endTimeCutoff));
|
parser, installedPackagesPerUser, endTimeCutoff));
|
||||||
break;
|
break;
|
||||||
|
case XML_TAG_PERIOD_REPORT:
|
||||||
|
reports.add(readReportFromXml(parser));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Slog.e(TAG, "Unexpected tag: " + tagName);
|
Slog.e(TAG, "Unexpected tag: " + tagName);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mAnalyst.loadReports(reports);
|
||||||
scheduleCleanup(earliestEndTime);
|
scheduleCleanup(earliestEndTime);
|
||||||
} catch (IOException | XmlPullParserException e) {
|
} catch (IOException | XmlPullParserException e) {
|
||||||
Slog.wtf(TAG, "Error reading state from disk", e);
|
Slog.wtf(TAG, "Error reading state from disk", e);
|
||||||
@@ -457,6 +477,37 @@ public class Scribe {
|
|||||||
return earliestEndTime;
|
return earliestEndTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param parser Xml parser at the beginning of a {@link #XML_TAG_PERIOD_REPORT} tag. The next
|
||||||
|
* "parser.next()" call will take the parser into the body of the report tag.
|
||||||
|
* @return Newly instantiated Report holding all the information we just read out of the xml tag
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private static Analyst.Report readReportFromXml(TypedXmlPullParser parser)
|
||||||
|
throws XmlPullParserException, IOException {
|
||||||
|
final Analyst.Report report = new Analyst.Report();
|
||||||
|
|
||||||
|
report.cumulativeBatteryDischarge = parser.getAttributeInt(null, XML_ATTR_PR_DISCHARGE);
|
||||||
|
report.currentBatteryLevel = parser.getAttributeInt(null, XML_ATTR_PR_BATTERY_LEVEL);
|
||||||
|
report.cumulativeProfit = parser.getAttributeLong(null, XML_ATTR_PR_PROFIT);
|
||||||
|
report.numProfitableActions = parser.getAttributeInt(null, XML_ATTR_PR_NUM_PROFIT);
|
||||||
|
report.cumulativeLoss = parser.getAttributeLong(null, XML_ATTR_PR_LOSS);
|
||||||
|
report.numUnprofitableActions = parser.getAttributeInt(null, XML_ATTR_PR_NUM_LOSS);
|
||||||
|
report.cumulativeRewards = parser.getAttributeLong(null, XML_ATTR_PR_REWARDS);
|
||||||
|
report.numRewards = parser.getAttributeInt(null, XML_ATTR_PR_NUM_REWARDS);
|
||||||
|
report.cumulativePositiveRegulations =
|
||||||
|
parser.getAttributeLong(null, XML_ATTR_PR_POS_REGULATIONS);
|
||||||
|
report.numPositiveRegulations =
|
||||||
|
parser.getAttributeInt(null, XML_ATTR_PR_NUM_POS_REGULATIONS);
|
||||||
|
report.cumulativeNegativeRegulations =
|
||||||
|
parser.getAttributeLong(null, XML_ATTR_PR_NEG_REGULATIONS);
|
||||||
|
report.numNegativeRegulations =
|
||||||
|
parser.getAttributeInt(null, XML_ATTR_PR_NUM_NEG_REGULATIONS);
|
||||||
|
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
private void scheduleCleanup(long earliestEndTime) {
|
private void scheduleCleanup(long earliestEndTime) {
|
||||||
if (earliestEndTime == Long.MAX_VALUE) {
|
if (earliestEndTime == Long.MAX_VALUE) {
|
||||||
return;
|
return;
|
||||||
@@ -501,6 +552,11 @@ public class Scribe {
|
|||||||
writeUserLocked(out, userId));
|
writeUserLocked(out, userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Analyst.Report> reports = mAnalyst.getReports();
|
||||||
|
for (int i = 0, size = reports.size(); i < size; ++i) {
|
||||||
|
writeReport(out, reports.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
out.endTag(null, XML_TAG_TARE);
|
out.endTag(null, XML_TAG_TARE);
|
||||||
|
|
||||||
out.endDocument();
|
out.endDocument();
|
||||||
@@ -560,6 +616,24 @@ public class Scribe {
|
|||||||
out.endTag(null, XML_TAG_TRANSACTION);
|
out.endTag(null, XML_TAG_TRANSACTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void writeReport(@NonNull TypedXmlSerializer out,
|
||||||
|
@NonNull Analyst.Report report) throws IOException {
|
||||||
|
out.startTag(null, XML_TAG_PERIOD_REPORT);
|
||||||
|
out.attributeInt(null, XML_ATTR_PR_DISCHARGE, report.cumulativeBatteryDischarge);
|
||||||
|
out.attributeInt(null, XML_ATTR_PR_BATTERY_LEVEL, report.currentBatteryLevel);
|
||||||
|
out.attributeLong(null, XML_ATTR_PR_PROFIT, report.cumulativeProfit);
|
||||||
|
out.attributeInt(null, XML_ATTR_PR_NUM_PROFIT, report.numProfitableActions);
|
||||||
|
out.attributeLong(null, XML_ATTR_PR_LOSS, report.cumulativeLoss);
|
||||||
|
out.attributeInt(null, XML_ATTR_PR_NUM_LOSS, report.numUnprofitableActions);
|
||||||
|
out.attributeLong(null, XML_ATTR_PR_REWARDS, report.cumulativeRewards);
|
||||||
|
out.attributeInt(null, XML_ATTR_PR_NUM_REWARDS, report.numRewards);
|
||||||
|
out.attributeLong(null, XML_ATTR_PR_POS_REGULATIONS, report.cumulativePositiveRegulations);
|
||||||
|
out.attributeInt(null, XML_ATTR_PR_NUM_POS_REGULATIONS, report.numPositiveRegulations);
|
||||||
|
out.attributeLong(null, XML_ATTR_PR_NEG_REGULATIONS, report.cumulativeNegativeRegulations);
|
||||||
|
out.attributeInt(null, XML_ATTR_PR_NUM_NEG_REGULATIONS, report.numNegativeRegulations);
|
||||||
|
out.endTag(null, XML_TAG_PERIOD_REPORT);
|
||||||
|
}
|
||||||
|
|
||||||
@GuardedBy("mIrs.getLock()")
|
@GuardedBy("mIrs.getLock()")
|
||||||
void dumpLocked(IndentingPrintWriter pw, boolean dumpAll) {
|
void dumpLocked(IndentingPrintWriter pw, boolean dumpAll) {
|
||||||
pw.println("Ledgers:");
|
pw.println("Ledgers:");
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ public class AgentTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private CompleteEconomicPolicy mEconomicPolicy;
|
private CompleteEconomicPolicy mEconomicPolicy;
|
||||||
@Mock
|
@Mock
|
||||||
|
private Analyst mAnalyst;
|
||||||
|
@Mock
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
@Mock
|
@Mock
|
||||||
private InternalResourceService mIrs;
|
private InternalResourceService mIrs;
|
||||||
@@ -53,8 +55,8 @@ public class AgentTest {
|
|||||||
private Scribe mScribe;
|
private Scribe mScribe;
|
||||||
|
|
||||||
private static class MockScribe extends Scribe {
|
private static class MockScribe extends Scribe {
|
||||||
MockScribe(InternalResourceService irs) {
|
MockScribe(InternalResourceService irs, Analyst analyst) {
|
||||||
super(irs);
|
super(irs, analyst);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -74,7 +76,7 @@ public class AgentTest {
|
|||||||
doReturn(mEconomicPolicy).when(mIrs).getCompleteEconomicPolicyLocked();
|
doReturn(mEconomicPolicy).when(mIrs).getCompleteEconomicPolicyLocked();
|
||||||
doReturn(mIrs).when(mIrs).getLock();
|
doReturn(mIrs).when(mIrs).getLock();
|
||||||
doReturn(mock(AlarmManager.class)).when(mContext).getSystemService(Context.ALARM_SERVICE);
|
doReturn(mock(AlarmManager.class)).when(mContext).getSystemService(Context.ALARM_SERVICE);
|
||||||
mScribe = new MockScribe(mIrs);
|
mScribe = new MockScribe(mIrs, mAnalyst);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -86,7 +88,7 @@ public class AgentTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRecordTransaction_UnderMax() {
|
public void testRecordTransaction_UnderMax() {
|
||||||
Agent agent = new Agent(mIrs, mScribe);
|
Agent agent = new Agent(mIrs, mScribe, mAnalyst);
|
||||||
Ledger ledger = new Ledger();
|
Ledger ledger = new Ledger();
|
||||||
|
|
||||||
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
|
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
|
||||||
@@ -115,7 +117,7 @@ public class AgentTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRecordTransaction_MaxConsumptionLimit() {
|
public void testRecordTransaction_MaxConsumptionLimit() {
|
||||||
Agent agent = new Agent(mIrs, mScribe);
|
Agent agent = new Agent(mIrs, mScribe, mAnalyst);
|
||||||
Ledger ledger = new Ledger();
|
Ledger ledger = new Ledger();
|
||||||
|
|
||||||
doReturn(1000L).when(mIrs).getConsumptionLimitLocked();
|
doReturn(1000L).when(mIrs).getConsumptionLimitLocked();
|
||||||
@@ -162,7 +164,7 @@ public class AgentTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRecordTransaction_MaxSatiatedBalance() {
|
public void testRecordTransaction_MaxSatiatedBalance() {
|
||||||
Agent agent = new Agent(mIrs, mScribe);
|
Agent agent = new Agent(mIrs, mScribe, mAnalyst);
|
||||||
Ledger ledger = new Ledger();
|
Ledger ledger = new Ledger();
|
||||||
|
|
||||||
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
|
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
package com.android.server.tare;
|
package com.android.server.tare;
|
||||||
|
|
||||||
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
|
||||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -41,6 +41,8 @@ import org.junit.After;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InOrder;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoSession;
|
import org.mockito.MockitoSession;
|
||||||
import org.mockito.quality.Strictness;
|
import org.mockito.quality.Strictness;
|
||||||
@@ -66,7 +68,10 @@ public class ScribeTest {
|
|||||||
private Scribe mScribeUnderTest;
|
private Scribe mScribeUnderTest;
|
||||||
private File mTestFileDir;
|
private File mTestFileDir;
|
||||||
private final List<PackageInfo> mInstalledPackages = new ArrayList<>();
|
private final List<PackageInfo> mInstalledPackages = new ArrayList<>();
|
||||||
|
private final List<Analyst.Report> mReports = new ArrayList<>();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Analyst mAnalyst;
|
||||||
@Mock
|
@Mock
|
||||||
private InternalResourceService mIrs;
|
private InternalResourceService mIrs;
|
||||||
|
|
||||||
@@ -84,11 +89,12 @@ public class ScribeTest {
|
|||||||
when(mIrs.getLock()).thenReturn(new Object());
|
when(mIrs.getLock()).thenReturn(new Object());
|
||||||
when(mIrs.isEnabled()).thenReturn(true);
|
when(mIrs.isEnabled()).thenReturn(true);
|
||||||
when(mIrs.getInstalledPackages()).thenReturn(mInstalledPackages);
|
when(mIrs.getInstalledPackages()).thenReturn(mInstalledPackages);
|
||||||
|
when(mAnalyst.getReports()).thenReturn(mReports);
|
||||||
mTestFileDir = new File(getContext().getFilesDir(), "scribe_test");
|
mTestFileDir = new File(getContext().getFilesDir(), "scribe_test");
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
mTestFileDir.mkdirs();
|
mTestFileDir.mkdirs();
|
||||||
Log.d(TAG, "Saving data to '" + mTestFileDir + "'");
|
Log.d(TAG, "Saving data to '" + mTestFileDir + "'");
|
||||||
mScribeUnderTest = new Scribe(mIrs, mTestFileDir);
|
mScribeUnderTest = new Scribe(mIrs, mAnalyst, mTestFileDir);
|
||||||
|
|
||||||
addInstalledPackage(TEST_USER_ID, TEST_PACKAGE);
|
addInstalledPackage(TEST_USER_ID, TEST_PACKAGE);
|
||||||
}
|
}
|
||||||
@@ -104,6 +110,62 @@ public class ScribeTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWritingAnalystReportsToDisk() {
|
||||||
|
ArgumentCaptor<List<Analyst.Report>> reportCaptor =
|
||||||
|
ArgumentCaptor.forClass(List.class);
|
||||||
|
|
||||||
|
InOrder inOrder = inOrder(mAnalyst);
|
||||||
|
|
||||||
|
// Empty set
|
||||||
|
mReports.clear();
|
||||||
|
mScribeUnderTest.writeImmediatelyForTesting();
|
||||||
|
mScribeUnderTest.loadFromDiskLocked();
|
||||||
|
inOrder.verify(mAnalyst).loadReports(reportCaptor.capture());
|
||||||
|
List<Analyst.Report> result = reportCaptor.getValue();
|
||||||
|
assertReportListsEqual(mReports, result);
|
||||||
|
|
||||||
|
Analyst.Report report1 = new Analyst.Report();
|
||||||
|
report1.cumulativeBatteryDischarge = 1;
|
||||||
|
report1.currentBatteryLevel = 2;
|
||||||
|
report1.cumulativeProfit = 3;
|
||||||
|
report1.numProfitableActions = 4;
|
||||||
|
report1.cumulativeLoss = 5;
|
||||||
|
report1.numUnprofitableActions = 6;
|
||||||
|
report1.cumulativeRewards = 7;
|
||||||
|
report1.numRewards = 8;
|
||||||
|
report1.cumulativePositiveRegulations = 9;
|
||||||
|
report1.numPositiveRegulations = 10;
|
||||||
|
report1.cumulativeNegativeRegulations = 11;
|
||||||
|
report1.numNegativeRegulations = 12;
|
||||||
|
mReports.add(report1);
|
||||||
|
mScribeUnderTest.writeImmediatelyForTesting();
|
||||||
|
mScribeUnderTest.loadFromDiskLocked();
|
||||||
|
inOrder.verify(mAnalyst).loadReports(reportCaptor.capture());
|
||||||
|
result = reportCaptor.getValue();
|
||||||
|
assertReportListsEqual(mReports, result);
|
||||||
|
|
||||||
|
Analyst.Report report2 = new Analyst.Report();
|
||||||
|
report2.cumulativeBatteryDischarge = 10;
|
||||||
|
report2.currentBatteryLevel = 20;
|
||||||
|
report2.cumulativeProfit = 30;
|
||||||
|
report2.numProfitableActions = 40;
|
||||||
|
report2.cumulativeLoss = 50;
|
||||||
|
report2.numUnprofitableActions = 60;
|
||||||
|
report2.cumulativeRewards = 70;
|
||||||
|
report2.numRewards = 80;
|
||||||
|
report2.cumulativePositiveRegulations = 90;
|
||||||
|
report2.numPositiveRegulations = 100;
|
||||||
|
report2.cumulativeNegativeRegulations = 110;
|
||||||
|
report2.numNegativeRegulations = 120;
|
||||||
|
mReports.add(report2);
|
||||||
|
mScribeUnderTest.writeImmediatelyForTesting();
|
||||||
|
mScribeUnderTest.loadFromDiskLocked();
|
||||||
|
inOrder.verify(mAnalyst).loadReports(reportCaptor.capture());
|
||||||
|
result = reportCaptor.getValue();
|
||||||
|
assertReportListsEqual(mReports, result);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteHighLevelStateToDisk() {
|
public void testWriteHighLevelStateToDisk() {
|
||||||
long lastReclamationTime = System.currentTimeMillis();
|
long lastReclamationTime = System.currentTimeMillis();
|
||||||
@@ -277,6 +339,49 @@ public class ScribeTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertReportListsEqual(List<Analyst.Report> expected,
|
||||||
|
List<Analyst.Report> actual) {
|
||||||
|
if (expected == null) {
|
||||||
|
assertNull(actual);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assertNotNull(actual);
|
||||||
|
assertEquals(expected.size(), actual.size());
|
||||||
|
for (int i = 0; i < expected.size(); ++i) {
|
||||||
|
Analyst.Report eReport = expected.get(i);
|
||||||
|
Analyst.Report aReport = actual.get(i);
|
||||||
|
if (eReport == null) {
|
||||||
|
assertNull(aReport);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
assertNotNull(aReport);
|
||||||
|
assertEquals("Reports #" + i + " cumulativeBatteryDischarge are not equal",
|
||||||
|
eReport.cumulativeBatteryDischarge, aReport.cumulativeBatteryDischarge);
|
||||||
|
assertEquals("Reports #" + i + " currentBatteryLevel are not equal",
|
||||||
|
eReport.currentBatteryLevel, aReport.currentBatteryLevel);
|
||||||
|
assertEquals("Reports #" + i + " cumulativeProfit are not equal",
|
||||||
|
eReport.cumulativeProfit, aReport.cumulativeProfit);
|
||||||
|
assertEquals("Reports #" + i + " numProfitableActions are not equal",
|
||||||
|
eReport.numProfitableActions, aReport.numProfitableActions);
|
||||||
|
assertEquals("Reports #" + i + " cumulativeLoss are not equal",
|
||||||
|
eReport.cumulativeLoss, aReport.cumulativeLoss);
|
||||||
|
assertEquals("Reports #" + i + " numUnprofitableActions are not equal",
|
||||||
|
eReport.numUnprofitableActions, aReport.numUnprofitableActions);
|
||||||
|
assertEquals("Reports #" + i + " cumulativeRewards are not equal",
|
||||||
|
eReport.cumulativeRewards, aReport.cumulativeRewards);
|
||||||
|
assertEquals("Reports #" + i + " numRewards are not equal",
|
||||||
|
eReport.numRewards, aReport.numRewards);
|
||||||
|
assertEquals("Reports #" + i + " cumulativePositiveRegulations are not equal",
|
||||||
|
eReport.cumulativePositiveRegulations, aReport.cumulativePositiveRegulations);
|
||||||
|
assertEquals("Reports #" + i + " numPositiveRegulations are not equal",
|
||||||
|
eReport.numPositiveRegulations, aReport.numPositiveRegulations);
|
||||||
|
assertEquals("Reports #" + i + " cumulativeNegativeRegulations are not equal",
|
||||||
|
eReport.cumulativeNegativeRegulations, aReport.cumulativeNegativeRegulations);
|
||||||
|
assertEquals("Reports #" + i + " numNegativeRegulations are not equal",
|
||||||
|
eReport.numNegativeRegulations, aReport.numNegativeRegulations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void assertTransactionsEqual(Ledger.Transaction expected, Ledger.Transaction actual) {
|
private void assertTransactionsEqual(Ledger.Transaction expected, Ledger.Transaction actual) {
|
||||||
if (expected == null) {
|
if (expected == null) {
|
||||||
assertNull(actual);
|
assertNull(actual);
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.server.tare;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Test that the Analyst processes transactions correctly. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@SmallTest
|
||||||
|
public class AnalystTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitialState() {
|
||||||
|
final Analyst analyst = new Analyst();
|
||||||
|
assertEquals(0, analyst.getReports().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatteryLevelChange() {
|
||||||
|
final Analyst analyst = new Analyst();
|
||||||
|
|
||||||
|
Analyst.Report expected = new Analyst.Report();
|
||||||
|
expected.currentBatteryLevel = 55;
|
||||||
|
analyst.noteBatteryLevelChange(55);
|
||||||
|
assertEquals(1, analyst.getReports().size());
|
||||||
|
assertReportsEqual(expected, analyst.getReports().get(0));
|
||||||
|
|
||||||
|
// Discharging
|
||||||
|
analyst.noteBatteryLevelChange(54);
|
||||||
|
expected.currentBatteryLevel = 54;
|
||||||
|
expected.cumulativeBatteryDischarge = 1;
|
||||||
|
assertEquals(1, analyst.getReports().size());
|
||||||
|
assertReportsEqual(expected, analyst.getReports().get(0));
|
||||||
|
analyst.noteBatteryLevelChange(50);
|
||||||
|
expected.currentBatteryLevel = 50;
|
||||||
|
expected.cumulativeBatteryDischarge = 5;
|
||||||
|
assertEquals(1, analyst.getReports().size());
|
||||||
|
assertReportsEqual(expected, analyst.getReports().get(0));
|
||||||
|
|
||||||
|
// Charging
|
||||||
|
analyst.noteBatteryLevelChange(51);
|
||||||
|
expected.currentBatteryLevel = 51;
|
||||||
|
assertEquals(1, analyst.getReports().size());
|
||||||
|
assertReportsEqual(expected, analyst.getReports().get(0));
|
||||||
|
analyst.noteBatteryLevelChange(55);
|
||||||
|
expected.currentBatteryLevel = 55;
|
||||||
|
assertEquals(1, analyst.getReports().size());
|
||||||
|
assertReportsEqual(expected, analyst.getReports().get(0));
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
analyst.noteBatteryLevelChange(100);
|
||||||
|
assertEquals(2, analyst.getReports().size());
|
||||||
|
assertReportsEqual(expected, analyst.getReports().get(0));
|
||||||
|
expected.currentBatteryLevel = 100;
|
||||||
|
expected.cumulativeBatteryDischarge = 0;
|
||||||
|
assertReportsEqual(expected, analyst.getReports().get(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransaction() {
|
||||||
|
runTestTransactions(new Analyst(), new Analyst.Report(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransaction_PeriodChange() {
|
||||||
|
final Analyst analyst = new Analyst();
|
||||||
|
|
||||||
|
Analyst.Report expected = new Analyst.Report();
|
||||||
|
expected.currentBatteryLevel = 55;
|
||||||
|
analyst.noteBatteryLevelChange(55);
|
||||||
|
|
||||||
|
runTestTransactions(analyst, expected, 1);
|
||||||
|
|
||||||
|
expected.currentBatteryLevel = 49;
|
||||||
|
expected.cumulativeBatteryDischarge = 6;
|
||||||
|
analyst.noteBatteryLevelChange(49);
|
||||||
|
|
||||||
|
runTestTransactions(analyst, expected, 1);
|
||||||
|
|
||||||
|
expected = new Analyst.Report();
|
||||||
|
expected.currentBatteryLevel = 100;
|
||||||
|
analyst.noteBatteryLevelChange(100);
|
||||||
|
expected.cumulativeBatteryDischarge = 0;
|
||||||
|
|
||||||
|
runTestTransactions(analyst, expected, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runTestTransactions(Analyst analyst, Analyst.Report lastExpectedReport,
|
||||||
|
int numExpectedReports) {
|
||||||
|
Analyst.Report expected = lastExpectedReport;
|
||||||
|
|
||||||
|
// Profit
|
||||||
|
analyst.noteTransaction(
|
||||||
|
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_ACTION, null, -51, 1));
|
||||||
|
expected.cumulativeProfit += 50;
|
||||||
|
expected.numProfitableActions += 1;
|
||||||
|
assertEquals(numExpectedReports, analyst.getReports().size());
|
||||||
|
assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
|
||||||
|
|
||||||
|
// Loss
|
||||||
|
analyst.noteTransaction(
|
||||||
|
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_ACTION, null, -51, 100));
|
||||||
|
expected.cumulativeLoss += 49;
|
||||||
|
expected.numUnprofitableActions += 1;
|
||||||
|
assertEquals(numExpectedReports, analyst.getReports().size());
|
||||||
|
assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
|
||||||
|
|
||||||
|
// Reward
|
||||||
|
analyst.noteTransaction(
|
||||||
|
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD, null, 51, 0));
|
||||||
|
expected.cumulativeRewards += 51;
|
||||||
|
expected.numRewards += 1;
|
||||||
|
assertEquals(numExpectedReports, analyst.getReports().size());
|
||||||
|
assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
|
||||||
|
|
||||||
|
// Regulations
|
||||||
|
analyst.noteTransaction(
|
||||||
|
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REGULATION, null, 25, 0));
|
||||||
|
expected.cumulativePositiveRegulations += 25;
|
||||||
|
expected.numPositiveRegulations += 1;
|
||||||
|
assertEquals(numExpectedReports, analyst.getReports().size());
|
||||||
|
assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
|
||||||
|
analyst.noteTransaction(
|
||||||
|
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REGULATION, null, -25, 0));
|
||||||
|
expected.cumulativeNegativeRegulations += 25;
|
||||||
|
expected.numNegativeRegulations += 1;
|
||||||
|
assertEquals(numExpectedReports, analyst.getReports().size());
|
||||||
|
assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
|
||||||
|
|
||||||
|
// No-ops
|
||||||
|
analyst.noteTransaction(
|
||||||
|
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_ACTION, null, -100, 100));
|
||||||
|
analyst.noteTransaction(
|
||||||
|
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REGULATION, null, 0, 0));
|
||||||
|
analyst.noteTransaction(
|
||||||
|
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD, null, 0, 0));
|
||||||
|
assertEquals(numExpectedReports, analyst.getReports().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadReports() {
|
||||||
|
final Analyst analyst = new Analyst();
|
||||||
|
|
||||||
|
List<Analyst.Report> expected = new ArrayList<>();
|
||||||
|
analyst.loadReports(expected);
|
||||||
|
assertReportListsEqual(expected, analyst.getReports());
|
||||||
|
|
||||||
|
Analyst.Report report1 = new Analyst.Report();
|
||||||
|
report1.cumulativeBatteryDischarge = 1;
|
||||||
|
report1.currentBatteryLevel = 2;
|
||||||
|
report1.cumulativeProfit = 3;
|
||||||
|
report1.numProfitableActions = 4;
|
||||||
|
report1.cumulativeLoss = 5;
|
||||||
|
report1.numUnprofitableActions = 6;
|
||||||
|
report1.cumulativeRewards = 7;
|
||||||
|
report1.numRewards = 8;
|
||||||
|
report1.cumulativePositiveRegulations = 9;
|
||||||
|
report1.numPositiveRegulations = 10;
|
||||||
|
report1.cumulativeNegativeRegulations = 11;
|
||||||
|
report1.numNegativeRegulations = 12;
|
||||||
|
expected.add(report1);
|
||||||
|
analyst.loadReports(expected);
|
||||||
|
assertReportListsEqual(expected, analyst.getReports());
|
||||||
|
|
||||||
|
Analyst.Report report2 = new Analyst.Report();
|
||||||
|
report2.cumulativeBatteryDischarge = 10;
|
||||||
|
report2.currentBatteryLevel = 20;
|
||||||
|
report2.cumulativeProfit = 30;
|
||||||
|
report2.numProfitableActions = 40;
|
||||||
|
report2.cumulativeLoss = 50;
|
||||||
|
report2.numUnprofitableActions = 60;
|
||||||
|
report2.cumulativeRewards = 70;
|
||||||
|
report2.numRewards = 80;
|
||||||
|
report2.cumulativePositiveRegulations = 90;
|
||||||
|
report2.numPositiveRegulations = 100;
|
||||||
|
report2.cumulativeNegativeRegulations = 110;
|
||||||
|
report2.numNegativeRegulations = 120;
|
||||||
|
expected.add(report2);
|
||||||
|
analyst.loadReports(expected);
|
||||||
|
assertReportListsEqual(expected, analyst.getReports());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertReportsEqual(Analyst.Report expected, Analyst.Report actual) {
|
||||||
|
if (expected == null) {
|
||||||
|
assertNull(actual);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assertNotNull(actual);
|
||||||
|
assertEquals(expected.cumulativeBatteryDischarge, actual.cumulativeBatteryDischarge);
|
||||||
|
assertEquals(expected.currentBatteryLevel, actual.currentBatteryLevel);
|
||||||
|
assertEquals(expected.cumulativeProfit, actual.cumulativeProfit);
|
||||||
|
assertEquals(expected.numProfitableActions, actual.numProfitableActions);
|
||||||
|
assertEquals(expected.cumulativeLoss, actual.cumulativeLoss);
|
||||||
|
assertEquals(expected.numUnprofitableActions, actual.numUnprofitableActions);
|
||||||
|
assertEquals(expected.cumulativeRewards, actual.cumulativeRewards);
|
||||||
|
assertEquals(expected.numRewards, actual.numRewards);
|
||||||
|
assertEquals(expected.cumulativePositiveRegulations, actual.cumulativePositiveRegulations);
|
||||||
|
assertEquals(expected.numPositiveRegulations, actual.numPositiveRegulations);
|
||||||
|
assertEquals(expected.cumulativeNegativeRegulations, actual.cumulativeNegativeRegulations);
|
||||||
|
assertEquals(expected.numNegativeRegulations, actual.numNegativeRegulations);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertReportListsEqual(List<Analyst.Report> expected,
|
||||||
|
List<Analyst.Report> actual) {
|
||||||
|
if (expected == null) {
|
||||||
|
assertNull(actual);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assertNotNull(actual);
|
||||||
|
assertEquals(expected.size(), actual.size());
|
||||||
|
for (int i = 0; i < expected.size(); ++i) {
|
||||||
|
assertReportsEqual(expected.get(i), actual.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user