Merge "CarService detects recurring overuse" into sc-v2-dev
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index ae487da..7e49fb1 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -167,6 +167,14 @@
to signal the potential for flash wear. -->
<integer name="maxExcessiveIoSamplesInWindow">11</integer>
+ <!-- The number of days past until the current day considered by car watchdog to
+ attribute recurring overuse to a package. -->
+ <integer name="recurringResourceOverusePeriodInDays">14</integer>
+
+ <!-- The number of overuses a package has to exceed for car watchdog to attribute
+ recurring overuse. -->
+ <integer name="recurringResourceOveruseTimes">2</integer>
+
<!-- The name of an intent to be notified by CarService whenever it detects too many
samples with excessive I/O activity. Value must either be an empty string, which
means that no notification will take place, or be in the format of a flattened
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index f59644a..ae9897d 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -466,11 +466,6 @@
}
@VisibleForTesting
- void setRecurringOveruseThreshold(int threshold) {
- mWatchdogPerfHandler.setRecurringOveruseThreshold(threshold);
- }
-
- @VisibleForTesting
void setUidIoUsageSummaryTopCount(int uidIoUsageSummaryTopCount) {
mWatchdogPerfHandler.setUidIoUsageSummaryTopCount(uidIoUsageSummaryTopCount);
}
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index 31b790c..d39ec88 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -115,6 +115,7 @@
import com.android.car.CarServiceUtils;
import com.android.car.CarStatsLog;
import com.android.car.CarUxRestrictionsManagerService;
+import com.android.car.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ConcurrentUtils;
@@ -177,11 +178,6 @@
.setTimeoutMillis(10_000)
.build();
- // TODO(b/195425666): Define this constant as a resource overlay config with two values:
- // 1. Recurring overuse period - Period to calculate the recurring overuse.
- // 2. Recurring overuse threshold - Total overuses for recurring behavior.
- private static final int RECURRING_OVERUSE_THRESHOLD = 2;
-
/**
* Don't distract the user by sending user notifications/dialogs, killing foreground
* applications, repeatedly killing persistent background services, or disabling any
@@ -212,6 +208,8 @@
private final WatchdogStorage mWatchdogStorage;
private final OveruseConfigurationCache mOveruseConfigurationCache;
private final UserNotificationHelper mUserNotificationHelper;
+ private final int mRecurringOverusePeriodInDays;
+ private final int mRecurringOveruseTimes;
private final Object mLock = new Object();
@GuardedBy("mLock")
private final ArrayMap<String, PackageResourceUsage> mUsageByUserPackage = new ArrayMap<>();
@@ -298,8 +296,11 @@
mOveruseHandlingDelayMills = OVERUSE_HANDLING_DELAY_MILLS;
mCurrentUxState = UX_STATE_NO_DISTRACTION;
mCurrentGarageMode = GarageMode.GARAGE_MODE_OFF;
- mRecurringOveruseThreshold = RECURRING_OVERUSE_THRESHOLD;
mUidIoUsageSummaryTopCount = UID_IO_USAGE_SUMMARY_TOP_COUNT;
+ mRecurringOverusePeriodInDays = mContext.getResources().getInteger(
+ R.integer.recurringResourceOverusePeriodInDays);
+ mRecurringOveruseTimes = mContext.getResources().getInteger(
+ R.integer.recurringResourceOveruseTimes);
}
/** Initializes the handler. */
@@ -307,10 +308,6 @@
/* First database read is expensive, so post it on a separate handler thread. */
mServiceHandler.post(() -> {
readFromDatabase();
- synchronized (mLock) {
- checkAndHandleDateChangeLocked();
- mIsWrittenToDatabase = false;
- }
// Set atom pull callbacks only after the internal datastructures are updated. When the
// pull happens, the service is already initialized and ready to populate the pulled
// atoms.
@@ -773,8 +770,8 @@
}
SparseArray<String> genericPackageNamesByUid = mPackageInfoHandler.getNamesForUids(uids);
ArraySet<String> overusingUserPackageKeys = new ArraySet<>();
+ checkAndHandleDateChange();
synchronized (mLock) {
- checkAndHandleDateChangeLocked();
for (int i = 0; i < packageIoOveruseStats.size(); ++i) {
PackageIoOveruseStats stats = packageIoOveruseStats.get(i);
String genericPackageName = genericPackageNamesByUid.get(stats.uid);
@@ -802,10 +799,11 @@
if (killableState == KILLABLE_STATE_NEVER) {
continue;
}
- if (isRecurringOveruseLocked(usage)) {
+ if (usage.ioUsage.getNotForgivenOveruses() > mRecurringOveruseTimes) {
String id = usage.getUniqueId();
mActionableUserPackages.add(id);
mUserNotifiablePackages.add(id);
+ usage.ioUsage.forgiveOveruses();
}
}
if ((mCurrentUxState != UX_STATE_NO_DISTRACTION && !mUserNotifiablePackages.isEmpty())
@@ -819,6 +817,7 @@
performOveruseHandlingLocked();
}}, mOveruseHandlingDelayMills);
}
+ mIsWrittenToDatabase = false;
}
if (!overusingUserPackageKeys.isEmpty()) {
pushIoOveruseMetrics(overusingUserPackageKeys);
@@ -985,13 +984,6 @@
}
}
- /** Sets the threshold for recurring overuse behavior. */
- public void setRecurringOveruseThreshold(int threshold) {
- synchronized (mLock) {
- mRecurringOveruseThreshold = threshold;
- }
- }
-
/** Sets top N UID I/O usage summaries to report on stats pull. */
public void setUidIoUsageSummaryTopCount(int uidIoUsageSummaryTopCount) {
synchronized (mLock) {
@@ -1105,12 +1097,28 @@
usage.ioUsage.overwrite(entry.ioUsage);
mUsageByUserPackage.put(key, usage);
}
- if (!ioStatsEntries.isEmpty()) {
- /* When mLatestStatsReportDate is null, the latest stats push from daemon hasn't
- * happened yet. Thus the cached stats contains only the stats read from database.
- */
- mIsWrittenToDatabase = mLatestStatsReportDate == null;
- mLatestStatsReportDate = curReportDate;
+ mLatestStatsReportDate = curReportDate;
+ }
+ syncHistoricalNotForgivenOveruses();
+ }
+
+ /** Fetches all historical not forgiven overuses and syncs them with package I/O usages. */
+ private void syncHistoricalNotForgivenOveruses() {
+ List<WatchdogStorage.NotForgivenOverusesEntry> notForgivenOverusesEntries =
+ mWatchdogStorage.getNotForgivenHistoricalIoOveruses(mRecurringOverusePeriodInDays);
+ Slogf.i(TAG, "Read %d not forgiven overuse stats from database",
+ notForgivenOverusesEntries.size());
+ synchronized (mLock) {
+ for (int i = 0; i < notForgivenOverusesEntries.size(); i++) {
+ WatchdogStorage.NotForgivenOverusesEntry entry = notForgivenOverusesEntries.get(i);
+ String key = getUserPackageUniqueId(entry.userId, entry.packageName);
+ PackageResourceUsage usage = mUsageByUserPackage.get(key);
+ if (usage == null) {
+ usage = new PackageResourceUsage(entry.userId, entry.packageName,
+ getDefaultKillableStateLocked(entry.packageName));
+ }
+ usage.ioUsage.setHistoricalNotForgivenOveruses(entry.notForgivenOveruses);
+ mUsageByUserPackage.put(key, usage);
}
}
}
@@ -1151,20 +1159,39 @@
@GuardedBy("mLock")
private void writeStatsLocked() {
- List<WatchdogStorage.IoUsageStatsEntry> entries =
+ List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries =
new ArrayList<>(mUsageByUserPackage.size());
+ SparseArray<List<String>> forgivePackagesByUserId = new SparseArray<>();
for (int i = 0; i < mUsageByUserPackage.size(); ++i) {
PackageResourceUsage usage = mUsageByUserPackage.valueAt(i);
if (!usage.ioUsage.hasUsage()) {
continue;
}
- entries.add(new WatchdogStorage.IoUsageStatsEntry(
- usage.userId, usage.genericPackageName, usage.ioUsage));
+ if (usage.ioUsage.shouldForgiveHistoricalOveruses()) {
+ List<String> packagesToForgive = forgivePackagesByUserId.get(usage.userId);
+ if (packagesToForgive == null) {
+ packagesToForgive = new ArrayList<>();
+ }
+ packagesToForgive.add(usage.genericPackageName);
+ forgivePackagesByUserId.put(usage.userId, packagesToForgive);
+ }
+ ioUsageStatsEntries.add(new WatchdogStorage.IoUsageStatsEntry(usage.userId,
+ usage.genericPackageName, usage.ioUsage));
}
- if (!mWatchdogStorage.saveIoUsageStats(entries)) {
- Slogf.e(TAG, "Failed to write %d I/O overuse stats to database", entries.size());
+ // Forgive historical overuses before writing the latest stats to disk to avoid forgiving
+ // the latest stats when the write is triggered after date change.
+ if (forgivePackagesByUserId.size() != 0) {
+ mWatchdogStorage.forgiveHistoricalOveruses(forgivePackagesByUserId,
+ mRecurringOverusePeriodInDays);
+ Slogf.e(TAG, "Attempted to forgive historical overuses for %d users.",
+ forgivePackagesByUserId.size());
+ }
+ if (!mWatchdogStorage.saveIoUsageStats(ioUsageStatsEntries)) {
+ Slogf.e(TAG, "Failed to write %d I/O overuse stats to database",
+ ioUsageStatsEntries.size());
} else {
- Slogf.i(TAG, "Successfully saved %d I/O overuse stats to database", entries.size());
+ Slogf.i(TAG, "Successfully saved %d I/O overuse stats to database",
+ ioUsageStatsEntries.size());
}
}
@@ -1221,23 +1248,24 @@
}
}
- @GuardedBy("mLock")
- private void checkAndHandleDateChangeLocked() {
- ZonedDateTime currentDate = mTimeSource.getCurrentDate();
- if (currentDate.equals(mLatestStatsReportDate)) {
- return;
+ private void checkAndHandleDateChange() {
+ synchronized (mLock) {
+ ZonedDateTime currentDate = mTimeSource.getCurrentDate();
+ if (currentDate.equals(mLatestStatsReportDate)) {
+ return;
+ }
+ // After the first database read or on the first stats sync from the daemon, whichever
+ // happens first, the cached stats would either be empty or initialized from the
+ // database. In either case, don't write to database.
+ if (mLatestStatsReportDate != null && !mIsWrittenToDatabase) {
+ writeStatsLocked();
+ }
+ for (int i = 0; i < mUsageByUserPackage.size(); ++i) {
+ mUsageByUserPackage.valueAt(i).resetStats();
+ }
+ mLatestStatsReportDate = currentDate;
}
- /* After the first database read or on the first stats sync from the daemon, whichever
- * happens first, the cached stats would either be empty or initialized from the database.
- * In either case, don't write to database.
- */
- if (mLatestStatsReportDate != null && !mIsWrittenToDatabase) {
- writeStatsLocked();
- }
- for (int i = 0; i < mUsageByUserPackage.size(); ++i) {
- mUsageByUserPackage.valueAt(i).resetStats();
- }
- mLatestStatsReportDate = currentDate;
+ syncHistoricalNotForgivenOveruses();
if (DEBUG) {
Slogf.d(TAG, "Handled date change successfully");
}
@@ -1259,14 +1287,6 @@
return usage;
}
- @GuardedBy("mLock")
- private boolean isRecurringOveruseLocked(PackageResourceUsage usage) {
- // TODO(b/195425666): Look up I/O overuse history and determine whether or not the package
- // has recurring I/O overuse behavior.
- return usage.ioUsage.getInternalIoOveruseStats().totalOveruses
- > mRecurringOveruseThreshold;
- }
-
private IoOveruseStats getIoOveruseStatsForPeriod(int userId, String genericPackageName,
@CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
synchronized (mLock) {
@@ -2437,35 +2457,84 @@
public static final class PackageIoUsage {
private static final android.automotive.watchdog.PerStateBytes DEFAULT_PER_STATE_BYTES =
new android.automotive.watchdog.PerStateBytes();
+ private static final int MISSING_VALUE = -1;
+
private android.automotive.watchdog.IoOveruseStats mIoOveruseStats;
private android.automotive.watchdog.PerStateBytes mForgivenWriteBytes;
+ private int mForgivenOveruses;
+ private int mHistoricalNotForgivenOveruses;
private int mTotalTimesKilled;
private PackageIoUsage() {
mForgivenWriteBytes = DEFAULT_PER_STATE_BYTES;
+ mForgivenOveruses = 0;
+ mHistoricalNotForgivenOveruses = MISSING_VALUE;
mTotalTimesKilled = 0;
}
public PackageIoUsage(android.automotive.watchdog.IoOveruseStats ioOveruseStats,
- android.automotive.watchdog.PerStateBytes forgivenWriteBytes,
+ android.automotive.watchdog.PerStateBytes forgivenWriteBytes, int forgivenOveruses,
int totalTimesKilled) {
mIoOveruseStats = ioOveruseStats;
mForgivenWriteBytes = forgivenWriteBytes;
+ mForgivenOveruses = forgivenOveruses;
mTotalTimesKilled = totalTimesKilled;
+ mHistoricalNotForgivenOveruses = MISSING_VALUE;
}
+ /** Returns the I/O overuse stats related to the package. */
public android.automotive.watchdog.IoOveruseStats getInternalIoOveruseStats() {
return mIoOveruseStats;
}
+ /** Returns the forgiven write bytes. */
public android.automotive.watchdog.PerStateBytes getForgivenWriteBytes() {
return mForgivenWriteBytes;
}
+ /** Returns the number of forgiven overuses today. */
+ public int getForgivenOveruses() {
+ return mForgivenOveruses;
+ }
+
+ /**
+ * Returns the number of not forgiven overuses. These are overuses that have not been
+ * attributed previously to a package's recurring overuse.
+ */
+ public int getNotForgivenOveruses() {
+ if (!hasUsage()) {
+ return 0;
+ }
+ int historicalNotForgivenOveruses =
+ mHistoricalNotForgivenOveruses != MISSING_VALUE
+ ? mHistoricalNotForgivenOveruses : 0;
+ return (mIoOveruseStats.totalOveruses - mForgivenOveruses)
+ + historicalNotForgivenOveruses;
+ }
+
+ /** Sets historical not forgiven overuses. */
+ public void setHistoricalNotForgivenOveruses(int historicalNotForgivenOveruses) {
+ mHistoricalNotForgivenOveruses = historicalNotForgivenOveruses;
+ }
+
+ /** Forgives all the I/O overuse stats' overuses. */
+ public void forgiveOveruses() {
+ if (!hasUsage()) {
+ return;
+ }
+ mForgivenOveruses = mIoOveruseStats.totalOveruses;
+ mHistoricalNotForgivenOveruses = 0;
+ }
+
+ /** Returns the total number of times the package was killed. */
public int getTotalTimesKilled() {
return mTotalTimesKilled;
}
+ boolean shouldForgiveHistoricalOveruses() {
+ return mHistoricalNotForgivenOveruses != MISSING_VALUE;
+ }
+
boolean hasUsage() {
return mIoOveruseStats != null;
}
@@ -2474,6 +2543,7 @@
mIoOveruseStats = ioUsage.mIoOveruseStats;
mForgivenWriteBytes = ioUsage.mForgivenWriteBytes;
mTotalTimesKilled = ioUsage.mTotalTimesKilled;
+ mHistoricalNotForgivenOveruses = ioUsage.mHistoricalNotForgivenOveruses;
}
void update(android.automotive.watchdog.IoOveruseStats internalStats,
@@ -2503,6 +2573,8 @@
void resetStats() {
mIoOveruseStats = null;
mForgivenWriteBytes = DEFAULT_PER_STATE_BYTES;
+ mForgivenOveruses = 0;
+ mHistoricalNotForgivenOveruses = MISSING_VALUE;
mTotalTimesKilled = 0;
}
}
diff --git a/service/src/com/android/car/watchdog/WatchdogStorage.java b/service/src/com/android/car/watchdog/WatchdogStorage.java
index 27ead48..57baf06 100644
--- a/service/src/com/android/car/watchdog/WatchdogStorage.java
+++ b/service/src/com/android/car/watchdog/WatchdogStorage.java
@@ -34,6 +34,7 @@
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.car.CarLog;
import com.android.internal.annotations.GuardedBy;
@@ -262,6 +263,71 @@
}
/**
+ * Returns the aggregated historical overuses minus the forgiven overuses for all saved
+ * packages. Forgiven overuses are overuses that have been attributed previously to a package's
+ * recurring overuse.
+ */
+ public List<NotForgivenOverusesEntry> getNotForgivenHistoricalIoOveruses(int numDaysAgo) {
+ ZonedDateTime currentDate =
+ mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
+ long includingStartEpochSeconds = currentDate.minusDays(numDaysAgo).toEpochSecond();
+ long excludingEndEpochSeconds = currentDate.toEpochSecond();
+ ArrayMap<String, Integer> notForgivenOverusesById;
+ try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) {
+ notForgivenOverusesById = IoUsageStatsTable.queryNotForgivenHistoricalOveruses(db,
+ includingStartEpochSeconds, excludingEndEpochSeconds);
+ }
+ List<NotForgivenOverusesEntry> notForgivenOverusesEntries = new ArrayList<>();
+ for (int i = 0; i < notForgivenOverusesById.size(); i++) {
+ String id = notForgivenOverusesById.keyAt(i);
+ UserPackage userPackage = mUserPackagesById.get(id);
+ if (userPackage == null) {
+ Slogf.w(TAG,
+ "Failed to find user id and package name for unique database id: '%s'",
+ id);
+ continue;
+ }
+ notForgivenOverusesEntries.add(new NotForgivenOverusesEntry(userPackage.getUserId(),
+ userPackage.getPackageName(), notForgivenOverusesById.valueAt(i)));
+ }
+ return notForgivenOverusesEntries;
+ }
+
+ /**
+ * Forgives all historical overuses between yesterday and {@code numDaysAgo}
+ * for a list of specific {@code userIds} and {@code packageNames}.
+ */
+ public void forgiveHistoricalOveruses(SparseArray<List<String>> packagesByUserId,
+ int numDaysAgo) {
+ if (packagesByUserId.size() == 0) {
+ Slogf.w(TAG, "No I/O usage stats provided to forgive historical overuses.");
+ return;
+ }
+ ZonedDateTime currentDate =
+ mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
+ long includingStartEpochSeconds = currentDate.minusDays(numDaysAgo).toEpochSecond();
+ long excludingEndEpochSeconds = currentDate.toEpochSecond();
+ List<String> uniqueIds = new ArrayList<>();
+ for (int i = 0; i < packagesByUserId.size(); i++) {
+ int userId = packagesByUserId.keyAt(i);
+ List<String> packages = packagesByUserId.valueAt(i);
+ for (int pkgIdx = 0; pkgIdx < packages.size(); pkgIdx++) {
+ UserPackage userPackage =
+ mUserPackagesByKey.get(UserPackage.getKey(userId, packages.get(pkgIdx)));
+ if (userPackage == null) {
+ // Packages without historical stats don't have userPackage entry.
+ continue;
+ }
+ uniqueIds.add(userPackage.getUniqueId());
+ }
+ }
+ try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
+ IoUsageStatsTable.forgiveHistoricalOverusesForPackage(db, uniqueIds,
+ includingStartEpochSeconds, excludingEndEpochSeconds);
+ }
+ }
+
+ /**
* Deletes all user package settings and resource stats for all non-alive users.
*
* @param aliveUserIds Array of alive user ids.
@@ -572,6 +638,46 @@
}
}
+ /** Defines the not forgiven overuses stored in the IoUsageStatsTable. */
+ static final class NotForgivenOverusesEntry {
+ public final @UserIdInt int userId;
+ public final String packageName;
+ public final int notForgivenOveruses;
+
+ NotForgivenOverusesEntry(@UserIdInt int userId,
+ String packageName, int notForgivenOveruses) {
+ this.userId = userId;
+ this.packageName = packageName;
+ this.notForgivenOveruses = notForgivenOveruses;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof NotForgivenOverusesEntry)) {
+ return false;
+ }
+ NotForgivenOverusesEntry other = (NotForgivenOverusesEntry) obj;
+ return userId == other.userId
+ && packageName.equals(other.packageName)
+ && notForgivenOveruses == other.notForgivenOveruses;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(userId, packageName, notForgivenOveruses);
+ }
+
+ @Override
+ public String toString() {
+ return "NotForgivenOverusesEntry {UserId: " + userId
+ + ", Package name: " + packageName
+ + ", Not forgiven overuses: " + notForgivenOveruses + "}";
+ }
+ }
+
/**
* Defines the contents and queries for the I/O usage stats table.
*/
@@ -634,8 +740,7 @@
values.put(COLUMN_USER_PACKAGE_ID, userPackageId);
values.put(COLUMN_DATE_EPOCH, statsDateEpochSeconds);
values.put(COLUMN_NUM_OVERUSES, ioOveruseStats.totalOveruses);
- /* TODO(b/195425666): Put total forgiven overuses for the day. */
- values.put(COLUMN_NUM_FORGIVEN_OVERUSES, 0);
+ values.put(COLUMN_NUM_FORGIVEN_OVERUSES, entry.ioUsage.getForgivenOveruses());
values.put(COLUMN_NUM_TIMES_KILLED, entry.ioUsage.getTotalTimesKilled());
values.put(
COLUMN_WRITTEN_FOREGROUND_BYTES, ioOveruseStats.writtenBytes.foregroundBytes);
@@ -664,6 +769,7 @@
.append(COLUMN_USER_PACKAGE_ID).append(", ")
.append("MIN(").append(COLUMN_DATE_EPOCH).append("), ")
.append("SUM(").append(COLUMN_NUM_OVERUSES).append("), ")
+ .append("SUM(").append(COLUMN_NUM_FORGIVEN_OVERUSES).append("), ")
.append("SUM(").append(COLUMN_NUM_TIMES_KILLED).append("), ")
.append("SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append("), ")
.append("SUM(").append(COLUMN_WRITTEN_BACKGROUND_BYTES).append("), ")
@@ -691,20 +797,22 @@
excludingEndEpochSeconds - includingStartEpochSeconds;
ioOveruseStats.totalOveruses = cursor.getInt(2);
ioOveruseStats.writtenBytes = new PerStateBytes();
- ioOveruseStats.writtenBytes.foregroundBytes = cursor.getLong(4);
- ioOveruseStats.writtenBytes.backgroundBytes = cursor.getLong(5);
- ioOveruseStats.writtenBytes.garageModeBytes = cursor.getLong(6);
+ ioOveruseStats.writtenBytes.foregroundBytes = cursor.getLong(5);
+ ioOveruseStats.writtenBytes.backgroundBytes = cursor.getLong(6);
+ ioOveruseStats.writtenBytes.garageModeBytes = cursor.getLong(7);
ioOveruseStats.remainingWriteBytes = new PerStateBytes();
- ioOveruseStats.remainingWriteBytes.foregroundBytes = cursor.getLong(7);
- ioOveruseStats.remainingWriteBytes.backgroundBytes = cursor.getLong(8);
- ioOveruseStats.remainingWriteBytes.garageModeBytes = cursor.getLong(9);
+ ioOveruseStats.remainingWriteBytes.foregroundBytes = cursor.getLong(8);
+ ioOveruseStats.remainingWriteBytes.backgroundBytes = cursor.getLong(9);
+ ioOveruseStats.remainingWriteBytes.garageModeBytes = cursor.getLong(10);
PerStateBytes forgivenWriteBytes = new PerStateBytes();
- forgivenWriteBytes.foregroundBytes = cursor.getLong(10);
- forgivenWriteBytes.backgroundBytes = cursor.getLong(11);
- forgivenWriteBytes.garageModeBytes = cursor.getLong(12);
+ forgivenWriteBytes.foregroundBytes = cursor.getLong(11);
+ forgivenWriteBytes.backgroundBytes = cursor.getLong(12);
+ forgivenWriteBytes.garageModeBytes = cursor.getLong(13);
ioUsageById.put(cursor.getString(0), new WatchdogPerfHandler.PackageIoUsage(
- ioOveruseStats, forgivenWriteBytes, cursor.getInt(3)));
+ ioOveruseStats, forgivenWriteBytes,
+ /* forgivenOveruses= */ cursor.getInt(3),
+ /* totalTimesKilled= */ cursor.getInt(4)));
}
}
return ioUsageById;
@@ -753,6 +861,63 @@
return statsBuilder.build();
}
+ public static ArrayMap<String, Integer> queryNotForgivenHistoricalOveruses(
+ SQLiteDatabase db, long includingStartEpochSeconds, long excludingEndEpochSeconds) {
+ StringBuilder queryBuilder = new StringBuilder("SELECT ")
+ .append(COLUMN_USER_PACKAGE_ID).append(", ")
+ .append("SUM(").append(COLUMN_NUM_OVERUSES).append("), ")
+ .append("SUM(").append(COLUMN_NUM_FORGIVEN_OVERUSES).append(") ")
+ .append("FROM ").append(TABLE_NAME).append(" WHERE ")
+ .append(COLUMN_DATE_EPOCH).append(" >= ? and ")
+ .append(COLUMN_DATE_EPOCH).append("< ? GROUP BY ")
+ .append(COLUMN_USER_PACKAGE_ID);
+ String[] selectionArgs = new String[]{String.valueOf(includingStartEpochSeconds),
+ String.valueOf(excludingEndEpochSeconds)};
+ ArrayMap<String, Integer> notForgivenOverusesById = new ArrayMap<>();
+ try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) {
+ while (cursor.moveToNext()) {
+ if (cursor.getInt(1) <= cursor.getInt(2)) {
+ continue;
+ }
+ notForgivenOverusesById.put(cursor.getString(0),
+ cursor.getInt(1) - cursor.getInt(2));
+ }
+ }
+ return notForgivenOverusesById;
+ }
+
+ public static void forgiveHistoricalOverusesForPackage(SQLiteDatabase db,
+ List<String> uniqueIds, long includingStartEpochSeconds,
+ long excludingEndEpochSeconds) {
+ if (uniqueIds.isEmpty()) {
+ Slogf.e(TAG, "No unique ids provided to forgive historical overuses.");
+ return;
+ }
+ StringBuilder updateQueryBuilder = new StringBuilder("UPDATE ").append(TABLE_NAME)
+ .append(" SET ")
+ .append(COLUMN_NUM_FORGIVEN_OVERUSES).append("=").append(COLUMN_NUM_OVERUSES)
+ .append(" WHERE ")
+ .append(COLUMN_DATE_EPOCH).append(">= ").append(includingStartEpochSeconds)
+ .append(" and ")
+ .append(COLUMN_DATE_EPOCH).append("< ").append(excludingEndEpochSeconds);
+ for (int i = 0; i < uniqueIds.size(); i++) {
+ if (i == 0) {
+ updateQueryBuilder.append(" and ").append(COLUMN_USER_PACKAGE_ID)
+ .append(" IN (");
+ } else {
+ updateQueryBuilder.append(", ");
+ }
+ updateQueryBuilder.append(uniqueIds.get(i));
+ if (i == uniqueIds.size() - 1) {
+ updateQueryBuilder.append(")");
+ }
+ }
+
+ db.execSQL(updateQueryBuilder.toString());
+ Slogf.i(TAG, "Attempted to forgive overuses for I/O usage stats entries on pid %d",
+ Process.myPid());
+ }
+
public static @Nullable List<AtomsProto.CarWatchdogDailyIoUsageSummary>
queryDailySystemIoUsageSummaries(SQLiteDatabase db, long includingStartEpochSeconds,
long excludingEndEpochSeconds) {
diff --git a/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java b/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
index ca2faf4..30b8fa1 100644
--- a/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
@@ -40,6 +40,7 @@
import android.car.watchdog.CarWatchdogManager;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -80,6 +81,8 @@
private static final String CAR_WATCHDOG_DAEMON_INTERFACE = "carwatchdogd_system";
private static final int MAX_WAIT_TIME_MS = 3000;
private static final int INVALID_SESSION_ID = -1;
+ private static final int RECURRING_OVERUSE_TIMES = 2;
+ private static final int RECURRING_OVERUSE_PERIOD_IN_DAYS = 2;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
private final Executor mExecutor =
@@ -92,6 +95,7 @@
@Mock private Context mMockContext;
@Mock private Car mMockCar;
+ @Mock private Resources mMockResources;
@Mock private UserManager mMockUserManager;
@Mock private StatsManager mMockStatsManager;
@Mock private SystemInterface mMockSystemInterface;
@@ -108,14 +112,17 @@
@Before
public void setUp() throws Exception {
- mCarWatchdogService = new CarWatchdogService(mMockContext, mMockWatchdogStorage,
- mMockUserNotificationHelper, mTimeSource);
-
mockQueryService(CAR_WATCHDOG_DAEMON_INTERFACE, mMockDaemonBinder, mMockCarWatchdogDaemon);
when(mMockCar.getEventHandler()).thenReturn(mMainHandler);
- when(mMockServiceBinder.queryLocalInterface(anyString())).thenReturn(mCarWatchdogService);
when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
when(mMockContext.getSystemService(StatsManager.class)).thenReturn(mMockStatsManager);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getInteger(
+ com.android.car.R.integer.recurringResourceOverusePeriodInDays))
+ .thenReturn(RECURRING_OVERUSE_PERIOD_IN_DAYS);
+ when(mMockResources.getInteger(
+ com.android.car.R.integer.recurringResourceOveruseTimes))
+ .thenReturn(RECURRING_OVERUSE_TIMES);
doReturn(mMockSystemInterface)
.when(() -> CarLocalServices.getService(SystemInterface.class));
@@ -128,6 +135,11 @@
mockUmIsUserRunning(mMockUserManager, 100, true);
mockUmIsUserRunning(mMockUserManager, 101, false);
+ mCarWatchdogService = new CarWatchdogService(mMockContext, mMockWatchdogStorage,
+ mMockUserNotificationHelper, mTimeSource);
+
+ when(mMockServiceBinder.queryLocalInterface(anyString())).thenReturn(mCarWatchdogService);
+
mCarWatchdogService.init();
mWatchdogServiceForSystemImpl = registerCarWatchdogService();
}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
index 6215a6a..5a745e9 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
@@ -117,6 +117,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.os.Binder;
import android.os.FileUtils;
import android.os.Handler;
@@ -179,7 +180,8 @@
private static final int MAX_WAIT_TIME_MS = 3000;
private static final int INVALID_SESSION_ID = -1;
private static final int OVERUSE_HANDLING_DELAY_MILLS = 1000;
- private static final int RECURRING_OVERUSE_THRESHOLD = 2;
+ private static final int RECURRING_OVERUSE_TIMES = 2;
+ private static final int RECURRING_OVERUSE_PERIOD_IN_DAYS = 2;
private static final int UID_IO_USAGE_SUMMARY_TOP_COUNT = 3;
private static final long STATS_DURATION_SECONDS = 3 * 60 * 60;
private static final long SYSTEM_DAILY_IO_USAGE_SUMMARY_MULTIPLIER = 10_000;
@@ -192,6 +194,7 @@
@Mock private SystemInterface mMockSystemInterface;
@Mock private CarPowerManagementService mMockCarPowerManagementService;
@Mock private CarUxRestrictionsManagerService mMockCarUxRestrictionsManagerService;
+ @Mock private Resources mMockResources;
@Mock private IBinder mMockBinder;
@Mock private ICarWatchdog mMockCarWatchdogDaemon;
@Mock private WatchdogStorage mMockWatchdogStorage;
@@ -208,6 +211,7 @@
@Captor private ArgumentCaptor<List<
android.automotive.watchdog.internal.ResourceOveruseConfiguration>>
mResourceOveruseConfigurationsCaptor;
+ @Captor private ArgumentCaptor<SparseArray<List<String>>> mPackagesByUserIdCaptor;
@Captor private ArgumentCaptor<StatsPullAtomCallback> mStatsPullAtomCallbackCaptor;
@Captor private ArgumentCaptor<List<UserNotificationHelper.PackageNotificationInfo>>
mPackageNotificationInfosCaptor;
@@ -270,6 +274,13 @@
when(mMockContext.getSystemService(StatsManager.class)).thenReturn(mMockStatsManager);
when(mMockContext.getPackageName()).thenReturn(
CarWatchdogServiceUnitTest.class.getCanonicalName());
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getInteger(
+ eq(com.android.car.R.integer.recurringResourceOverusePeriodInDays)))
+ .thenReturn(RECURRING_OVERUSE_PERIOD_IN_DAYS);
+ when(mMockResources.getInteger(
+ eq(com.android.car.R.integer.recurringResourceOveruseTimes)))
+ .thenReturn(RECURRING_OVERUSE_TIMES);
doReturn(mMockSystemInterface)
.when(() -> CarLocalServices.getService(SystemInterface.class));
doReturn(mMockCarPowerManagementService)
@@ -2182,13 +2193,15 @@
/* remainingWriteBytes= */ constructPerStateBytes(200, 300, 400),
/* writtenBytes= */ constructPerStateBytes(1000, 2000, 3000),
/* forgivenWriteBytes= */ constructPerStateBytes(100, 100, 100),
- /* totalOveruses= */ 2, /* totalTimesKilled= */ 1),
+ /* totalOveruses= */ 2, /* forgivenOveruses= */ 0,
+ /* totalTimesKilled= */ 1),
WatchdogStorageUnitTest.constructIoUsageStatsEntry(
/* userId= */ 11, "vendor_package", /* startTime */ 0, /* duration= */ 1234,
/* remainingWriteBytes= */ constructPerStateBytes(500, 600, 700),
/* writtenBytes= */ constructPerStateBytes(1100, 2300, 4300),
/* forgivenWriteBytes= */ constructPerStateBytes(100, 100, 100),
- /* totalOveruses= */ 4, /* totalTimesKilled= */ 10));
+ /* totalOveruses= */ 4, /* forgivenOveruses= */ 1,
+ /* totalTimesKilled= */ 10));
when(mMockWatchdogStorage.getTodayIoUsageStats()).thenReturn(ioUsageStatsEntries);
List<UserPackageIoUsageStats> actualStats =
@@ -2292,11 +2305,11 @@
new WatchdogStorage.IoUsageStatsEntry(/* userId= */ 10, "system_package",
new WatchdogPerfHandler.PackageIoUsage(prevDayStats.get(0).ioOveruseStats,
/* forgivenWriteBytes= */ constructPerStateBytes(600, 700, 800),
- /* totalTimesKilled= */ 1)),
+ /* forgivenOveruses= */ 3, /* totalTimesKilled= */ 1)),
new WatchdogStorage.IoUsageStatsEntry(/* userId= */ 10, "third_party_package",
new WatchdogPerfHandler.PackageIoUsage(prevDayStats.get(1).ioOveruseStats,
/* forgivenWriteBytes= */ constructPerStateBytes(1050, 1100, 1200),
- /* totalTimesKilled= */ 0)));
+ /* forgivenOveruses= */ 0, /* totalTimesKilled= */ 0)));
setDisplayStateEnabled(true);
mTimeSource.updateNow(/* numDaysAgo= */ 0);
@@ -2523,19 +2536,35 @@
/* notificationIds= */ Arrays.asList(150, 151))));
}
- /* TODO(b/195425666): Test resource overuse notifications taking into considerations
- * historical overuses.
- *
- * @Test
- * public void testUserNotificationOnHistoricalRecurrentOveruse() throws Exception {
- * 1. Setup historical stats with RECURRING_OVERUSE_THRESHOLD overuses by mocking
- * WatchdogStorage calls.
- * 2. Set display to enabled and UX restriction to requires distraction optimization.
- * 3. Push some I/O stats with non-recurrent overuse and wait.
- * 4. Set UX restriction to not require distraction optimization.
- * 5. Verify no user notification.
- * }
- */
+ @Test
+ public void testUserNotificationOnHistoricalRecurrentOveruse() throws Exception {
+ when(mMockWatchdogStorage
+ .getNotForgivenHistoricalIoOveruses(RECURRING_OVERUSE_PERIOD_IN_DAYS))
+ .thenReturn(Arrays.asList(new WatchdogStorage.NotForgivenOverusesEntry(100,
+ "system_package.non_critical", 2)));
+
+ // Force CarWatchdogService to fetch historical not forgiven overuses.
+ restartService(/* totalRestarts= */ 1);
+ mockAmGetCurrentUser(100);
+ setDisplayStateEnabled(true);
+ setRequiresDistractionOptimization(false);
+
+ setUpSampleUserAndPackages();
+
+ pushLatestIoOveruseStatsAndWait(Collections.singletonList(
+ constructPackageIoOveruseStats(/* uid= */ 10010002, /* shouldNotify= */ true,
+ /* forgivenWriteBytes= */ constructPerStateBytes(100, 200, 300),
+ constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+ /* remainingWriteBytes= */ constructPerStateBytes(0, 0, 0),
+ /* writtenBytes= */ constructPerStateBytes(300, 600, 900),
+ /* totalOveruses= */ 1))));
+
+ captureAndVerifyUserNotifications(Collections.singletonList(
+ new UserNotificationCall(UserHandle.of(100),
+ Collections.singletonList("system_package.non_critical"),
+ /* hasHeadsUpNotification= */ true,
+ /* notificationIds= */ Collections.singletonList(150))));
+ }
@Test
public void testUserNotificationWithDisabledDisplay() throws Exception {
@@ -2830,10 +2859,106 @@
"100:third_party_package.B", "101:third_party_package.B");
}
- /* TODO(b/195425666): Test recurrently overusing app with overuse history stored in the DB.
- * @Test
- * public void testDisableHistoricalRecurrentlyOverusingApp() throws Exception {}
- */
+ @Test
+ public void testDisableHistoricalRecurrentlyOverusingApp() throws Exception {
+ when(mMockWatchdogStorage
+ .getNotForgivenHistoricalIoOveruses(RECURRING_OVERUSE_PERIOD_IN_DAYS))
+ .thenReturn(Arrays.asList(new WatchdogStorage.NotForgivenOverusesEntry(100,
+ "third_party_package", 2)));
+
+ // Force CarWatchdogService to fetch historical not forgiven overuses.
+ restartService(/* totalRestarts= */ 1);
+ setRequiresDistractionOptimization(true);
+ setDisplayStateEnabled(false);
+ int thirdPartyPkgUid = UserHandle.getUid(100, 10005);
+
+ injectPackageInfos(Collections.singletonList(constructPackageManagerPackageInfo(
+ "third_party_package", thirdPartyPkgUid, null)));
+
+ pushLatestIoOveruseStatsAndWait(
+ sampleIoOveruseStats(/* requireRecurrentOveruseStats= */ false));
+
+ // Third party package is disabled given the two historical overuses and one current
+ // overuse.
+ assertWithMessage("Disabled packages after recurring overuse with history")
+ .that(mDisabledUserPackages)
+ .containsExactlyElementsIn(Collections.singleton("100:third_party_package"));
+
+ // Package was enabled again.
+ mDisabledUserPackages.clear();
+
+ PackageIoOveruseStats packageIoOveruseStats =
+ constructPackageIoOveruseStats(thirdPartyPkgUid, /* shouldNotify= */ true,
+ /* forgivenWriteBytes= */ constructPerStateBytes(200, 400, 600),
+ constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+ /* remainingWriteBytes= */ constructPerStateBytes(0, 0, 0),
+ /* writtenBytes= */ constructPerStateBytes(200, 400, 600),
+ /* totalOveruses= */ 3));
+
+ pushLatestIoOveruseStatsAndWait(Collections.singletonList(packageIoOveruseStats));
+
+ // From the 3 total overuses, one overuse was forgiven previously.
+ assertWithMessage("Disabled packages after non-recurring overuse")
+ .that(mDisabledUserPackages).isEmpty();
+
+ // Add one overuse.
+ packageIoOveruseStats.ioOveruseStats.totalOveruses = 4;
+
+ pushLatestIoOveruseStatsAndWait(Collections.singletonList(packageIoOveruseStats));
+
+ // Third party package is disabled again given the three current overuses. From the 4 total
+ // overuses, one overuse was forgiven previously.
+ assertWithMessage("Disabled packages after recurring overuse from the same day")
+ .that(mDisabledUserPackages)
+ .containsExactlyElementsIn(Collections.singleton("100:third_party_package"));
+
+ // Force write to database
+ restartService(/* totalRestarts= */ 2);
+
+ verify(mMockWatchdogStorage).forgiveHistoricalOveruses(mPackagesByUserIdCaptor.capture(),
+ eq(RECURRING_OVERUSE_PERIOD_IN_DAYS));
+
+ assertWithMessage("Forgiven packages")
+ .that(mPackagesByUserIdCaptor.getValue().get(100))
+ .containsExactlyElementsIn(Arrays.asList("third_party_package"));
+ }
+
+ @Test
+ public void testDisableHistoricalRecurrentlyOverusingAppAfterDateChange() throws Exception {
+ when(mMockWatchdogStorage.getNotForgivenHistoricalIoOveruses(
+ eq(RECURRING_OVERUSE_PERIOD_IN_DAYS)))
+ .thenReturn(Arrays.asList(new WatchdogStorage.NotForgivenOverusesEntry(100,
+ "third_party_package", 2)));
+
+ mTimeSource.updateNow(/* numDaysAgo= */ 1);
+ setRequiresDistractionOptimization(true);
+ setDisplayStateEnabled(false);
+ int thirdPartyPkgUid = UserHandle.getUid(100, 10005);
+
+ injectPackageInfos(Collections.singletonList(constructPackageManagerPackageInfo(
+ "third_party_package", thirdPartyPkgUid, null)));
+
+ List<PackageIoOveruseStats> ioOveruseStats =
+ sampleIoOveruseStats(/* requireRecurrentOveruseStats= */ false);
+ pushLatestIoOveruseStatsAndWait(ioOveruseStats);
+
+ // Third party package is disabled given the two historical overuses and one current
+ // overuse.
+ assertThat(mDisabledUserPackages)
+ .containsExactlyElementsIn(Collections.singleton("100:third_party_package"));
+
+ // Force write to database by pushing non-overusing I/O overuse stats.
+ mTimeSource.updateNow(/* numDaysAgo= */ 0);
+ pushLatestIoOveruseStatsAndWait(Collections.singletonList(ioOveruseStats.get(0)));
+
+ verify(mMockWatchdogStorage).forgiveHistoricalOveruses(mPackagesByUserIdCaptor.capture(),
+ eq(RECURRING_OVERUSE_PERIOD_IN_DAYS));
+
+ assertWithMessage("Forgiven packages")
+ .that(mPackagesByUserIdCaptor.getValue().get(100))
+ .containsExactlyElementsIn(Arrays.asList("third_party_package"));
+ }
+
@Test
public void testResetResourceOveruseStatsResetsStats() throws Exception {
@@ -3455,6 +3580,7 @@
new WatchdogPerfHandler.PackageIoUsage(
entry.ioUsage.getInternalIoOveruseStats(),
entry.ioUsage.getForgivenWriteBytes(),
+ entry.ioUsage.getForgivenOveruses(),
entry.ioUsage.getTotalTimesKilled())));
}
return true;
@@ -3489,7 +3615,6 @@
private void initService(int wantedInvocations) throws Exception {
mTimeSource.updateNow(/* numDaysAgo= */ 0);
mCarWatchdogService.setOveruseHandlingDelay(OVERUSE_HANDLING_DELAY_MILLS);
- mCarWatchdogService.setRecurringOveruseThreshold(RECURRING_OVERUSE_THRESHOLD);
mCarWatchdogService.setUidIoUsageSummaryTopCount(
UID_IO_USAGE_SUMMARY_TOP_COUNT);
mCarWatchdogService.init();
@@ -3584,6 +3709,8 @@
verify(mMockWatchdogStorage, times(wantedInvocations)).syncUsers(any());
verify(mMockWatchdogStorage, times(wantedInvocations)).getUserPackageSettings();
verify(mMockWatchdogStorage, times(wantedInvocations)).getTodayIoUsageStats();
+ verify(mMockWatchdogStorage, times(wantedInvocations)).getNotForgivenHistoricalIoOveruses(
+ RECURRING_OVERUSE_PERIOD_IN_DAYS);
}
private void captureStatsPullAtomCallback(int wantedInvocations) {
@@ -3842,7 +3969,7 @@
private List<PackageIoOveruseStats> sampleIoOveruseStats(boolean requireRecurrentOveruseStats)
throws Exception {
int[] users = new int[]{100, 101};
- int totalOveruses = requireRecurrentOveruseStats ? RECURRING_OVERUSE_THRESHOLD + 1 : 0;
+ int totalOveruses = requireRecurrentOveruseStats ? RECURRING_OVERUSE_TIMES + 1 : 1;
List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>();
android.automotive.watchdog.PerStateBytes zeroRemainingBytes =
constructPerStateBytes(0, 0, 0);
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java
index 0479c93..4e08b0f 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java
@@ -79,6 +79,7 @@
}
return actual.userId == expected.userId && actual.packageName.equals(expected.packageName)
&& actual.ioUsage.getTotalTimesKilled() == expected.ioUsage.getTotalTimesKilled()
+ && actual.ioUsage.getForgivenOveruses() == expected.ioUsage.getForgivenOveruses()
&& InternalPerStateBytesSubject.isEquals(actual.ioUsage.getForgivenWriteBytes(),
expected.ioUsage.getForgivenWriteBytes())
&& isEqualsIoOveruseStats(actual.ioUsage.getInternalIoOveruseStats(),
@@ -127,6 +128,7 @@
toStringBuilder(builder, ioUsage.getInternalIoOveruseStats());
builder.append(", ForgivenWriteBytes: ");
InternalPerStateBytesSubject.toStringBuilder(builder, ioUsage.getForgivenWriteBytes());
+ builder.append(", Forgiven overuses: ").append(ioUsage.getForgivenOveruses());
return builder.append(", Total times killed: ").append(ioUsage.getTotalTimesKilled())
.append('}');
}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
index 85a54ec..84b8672 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
@@ -31,6 +31,7 @@
import android.car.watchdog.IoOveruseStats;
import android.content.Context;
import android.util.Slog;
+import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -167,7 +168,8 @@
CarWatchdogServiceUnitTest.constructPerStateBytes(1000, 2000, 3000),
/* forgivenWriteBytes= */
CarWatchdogServiceUnitTest.constructPerStateBytes(100, 100, 100),
- /* totalOveruses= */ 2, /* totalTimesKilled= */ 1));
+ /* totalOveruses= */ 2, /* forgivenOveruses= */ 0,
+ /* totalTimesKilled= */ 1));
assertWithMessage("Saved I/O usage stats successfully")
.that(mService.saveIoUsageStats(statsBeforeOverwrite)).isTrue();
@@ -185,7 +187,8 @@
CarWatchdogServiceUnitTest.constructPerStateBytes(2000, 3000, 4000),
/* forgivenWriteBytes= */
CarWatchdogServiceUnitTest.constructPerStateBytes(1200, 2300, 3400),
- /* totalOveruses= */ 4, /* totalTimesKilled= */ 2));
+ /* totalOveruses= */ 4, /* forgivenOveruses= */ 2,
+ /* totalTimesKilled= */ 2));
assertWithMessage("Saved I/O usage stats successfully")
.that(mService.saveIoUsageStats(statsAfterOverwrite)).isTrue();
@@ -604,6 +607,42 @@
expected.toString(), actual.toString()).that(actual).isEqualTo(expected);
}
+ @Test
+ public void testForgiveHistoricalOveruses() throws Exception {
+ injectSampleUserPackageSettings();
+
+ assertThat(mService.saveIoUsageStats(sampleStatsBetweenDates(/* includingStartDaysAgo= */ 1,
+ /* excludingEndDaysAgo= */ 3))).isTrue();
+
+ List<WatchdogStorage.NotForgivenOverusesEntry> expectedOveruses = Arrays.asList(
+ new WatchdogStorage.NotForgivenOverusesEntry(100, "system_package.non_critical.A",
+ 2),
+ new WatchdogStorage.NotForgivenOverusesEntry(101, "system_package.non_critical.A",
+ 2),
+ new WatchdogStorage.NotForgivenOverusesEntry(100, "vendor_package.critical.C", 2),
+ new WatchdogStorage.NotForgivenOverusesEntry(101, "vendor_package.critical.C", 2));
+
+ assertWithMessage("Not forgiven historical overuses before forgiving")
+ .that(mService.getNotForgivenHistoricalIoOveruses(/* numDaysAgo= */ 7))
+ .containsExactlyElementsIn(expectedOveruses);
+
+ SparseArray<List<String>> packagesToForgiveByUserId = new SparseArray<>();
+ packagesToForgiveByUserId.put(100,
+ Collections.singletonList("system_package.non_critical.A"));
+ packagesToForgiveByUserId.put(101, Collections.singletonList("vendor_package.critical.C"));
+
+ mService.forgiveHistoricalOveruses(packagesToForgiveByUserId, /* numDaysAgo= */ 7);
+
+ expectedOveruses = Arrays.asList(
+ new WatchdogStorage.NotForgivenOverusesEntry(101, "system_package.non_critical.A",
+ 2),
+ new WatchdogStorage.NotForgivenOverusesEntry(100, "vendor_package.critical.C", 2));
+
+ assertWithMessage("Not forgiven historical overuses after forgiving")
+ .that(mService.getNotForgivenHistoricalIoOveruses(/* numDaysAgo= */ 7))
+ .containsExactlyElementsIn(expectedOveruses);
+ }
+
private void injectSampleUserPackageSettings() throws Exception {
List<WatchdogStorage.UserPackageSettingsEntry> expected = sampleSettings();
@@ -670,7 +709,7 @@
(3000L + i) * writtenBytesMultiplier),
/* forgivenWriteBytes= */
CarWatchdogServiceUnitTest.constructPerStateBytes(100L, 100L, 100L),
- /* totalOveruses= */ 2, /* totalTimesKilled= */ 1));
+ /* totalOveruses= */ 2, /* forgivenOveruses= */ 1, /* totalTimesKilled= */ 1));
entries.add(constructIoUsageStatsEntry(
/* userId= */ i, "vendor_package.critical.C", statsDateEpoch, duration,
/* remainingWriteBytes= */
@@ -682,7 +721,7 @@
(6000L + i) * writtenBytesMultiplier),
/* forgivenWriteBytes= */
CarWatchdogServiceUnitTest.constructPerStateBytes(200L, 200L, 200L),
- /* totalOveruses= */ 1, /* totalTimesKilled= */ 0));
+ /* totalOveruses= */ 1, /* forgivenOveruses= */ 0, /* totalTimesKilled= */ 0));
}
return entries;
}
@@ -690,10 +729,12 @@
static WatchdogStorage.IoUsageStatsEntry constructIoUsageStatsEntry(
int userId, String packageName, long startTime, long duration,
PerStateBytes remainingWriteBytes, PerStateBytes writtenBytes,
- PerStateBytes forgivenWriteBytes, int totalOveruses, int totalTimesKilled) {
+ PerStateBytes forgivenWriteBytes, int totalOveruses, int forgivenOveruses,
+ int totalTimesKilled) {
WatchdogPerfHandler.PackageIoUsage ioUsage = new WatchdogPerfHandler.PackageIoUsage(
constructInternalIoOveruseStats(startTime, duration, remainingWriteBytes,
- writtenBytes, totalOveruses), forgivenWriteBytes, totalTimesKilled);
+ writtenBytes, totalOveruses), forgivenWriteBytes, forgivenOveruses,
+ totalTimesKilled);
return new WatchdogStorage.IoUsageStatsEntry(userId, packageName, ioUsage);
}