Merge "Avoid holding lock in CarWatchdogService when writing to database" into sc-v2-dev
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index 850c517..d963bff 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -215,6 +215,10 @@
private final int mRecurringOveruseTimes;
private final TimeSource mTimeSource;
private final Object mLock = new Object();
+ /**
+ * Tracks user packages' resource usage. When cache is updated, call
+ * {@link WatchdogStorage#markDirty()} to notify database is out of sync.
+ */
@GuardedBy("mLock")
private final ArrayMap<String, PackageResourceUsage> mUsageByUserPackage = new ArrayMap<>();
@GuardedBy("mLock")
@@ -223,7 +227,11 @@
@GuardedBy("mLock")
private final SparseArray<ArrayList<ResourceOveruseListenerInfo>>
mOveruseSystemListenerInfosByUid = new SparseArray<>();
- /** Default killable state for packages. Updated only for {@link UserHandle.ALL} user handle. */
+ /**
+ * Default killable state for packages. Updated only for {@link UserHandle#ALL} user handle.
+ * When cache is updated, call {@link WatchdogStorage#markDirty()} to notify database is out of
+ * sync.
+ */
@GuardedBy("mLock")
private final ArraySet<String> mDefaultNotKillableGenericPackages = new ArraySet<>();
/** Keys in {@link mUsageByUserPackage} for user notification on resource overuse. */
@@ -249,8 +257,6 @@
@GuardedBy("mLock")
private boolean mIsConnectedToDaemon;
@GuardedBy("mLock")
- private boolean mIsWrittenToDatabase = true;
- @GuardedBy("mLock")
private @UxStateType int mCurrentUxState = UX_STATE_NO_DISTRACTION;
@GuardedBy("mLock")
private CarUxRestrictions mCurrentUxRestrictions;
@@ -556,16 +562,14 @@
}
String key = getUserPackageUniqueId(userId, genericPackageName);
synchronized (mLock) {
- /*
- * When the queried package is not cached in {@link mUsageByUserPackage}, the set API
- * will update the killable state even when the package should never be killed.
- * But the get API will return the correct killable state. This behavior is tolerable
- * because in production the set API should be called only after the get API.
- * For instance, when this case happens by mistake and the package overuses resource
- * between the set and the get API calls, the daemon will provide correct killable
- * state when pushing the latest stats. Ergo, the invalid killable state doesn't have
- * any effect.
- */
+ // When the queried package is not cached in {@link mUsageByUserPackage}, the set API
+ // will update the killable state even when the package should never be killed.
+ // But the get API will return the correct killable state. This behavior is tolerable
+ // because in production the set API should be called only after the get API.
+ // For instance, when this case happens by mistake and the package overuses resource
+ // between the set and the get API calls, the daemon will provide correct killable
+ // state when pushing the latest stats. Ergo, the invalid killable state doesn't have
+ // any effect.
PackageResourceUsage usage = mUsageByUserPackage.get(key);
if (usage == null) {
usage = new PackageResourceUsage(userId, genericPackageName,
@@ -578,6 +582,7 @@
}
mUsageByUserPackage.put(key, usage);
}
+ mWatchdogStorage.markDirty();
if (DEBUG) {
Slogf.d(TAG, "Successfully set killable package state for user %d", userId);
}
@@ -611,6 +616,7 @@
} else {
mDefaultNotKillableGenericPackages.remove(genericPackageName);
}
+ mWatchdogStorage.markDirty();
}
}
if (DEBUG) {
@@ -768,8 +774,10 @@
SparseArray<String> genericPackageNamesByUid = mPackageInfoHandler.getNamesForUids(uids);
ArraySet<String> overusingUserPackageKeys = new ArraySet<>();
checkAndHandleDateChange();
+ if (genericPackageNamesByUid.size() > 0) {
+ mWatchdogStorage.markDirty();
+ }
synchronized (mLock) {
- mIsWrittenToDatabase &= genericPackageNamesByUid.size() == 0;
for (int i = 0; i < packageIoOveruseStats.size(); ++i) {
PackageIoOveruseStats stats = packageIoOveruseStats.get(i);
String genericPackageName = genericPackageNamesByUid.get(stats.uid);
@@ -793,8 +801,7 @@
continue;
}
overusingUserPackageKeys.add(usage.getUniqueId());
- int killableState = usage.getKillableState();
- if (killableState == KILLABLE_STATE_NEVER) {
+ if (usage.getKillableState() == KILLABLE_STATE_NEVER) {
continue;
}
if (usage.ioUsage.getNotForgivenOveruses() > mRecurringOveruseTimes) {
@@ -1119,31 +1126,58 @@
}
}
- /** Writes user package settings and stats to database. */
+ /**
+ * Writes user package settings and stats to database. If database is marked as clean,
+ * no writing is executed.
+ */
public void writeToDatabase() {
- synchronized (mLock) {
- if (mIsWrittenToDatabase) {
- return;
+ if (!mWatchdogStorage.startWrite()) {
+ return;
+ }
+ try {
+ List<WatchdogStorage.UserPackageSettingsEntry> userPackageSettingsEntries =
+ new ArrayList<>();
+ List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = new ArrayList<>();
+ SparseArray<List<String>> forgivePackagesByUserId = new SparseArray<>();
+ synchronized (mLock) {
+ for (int i = 0; i < mUsageByUserPackage.size(); i++) {
+ PackageResourceUsage usage = mUsageByUserPackage.valueAt(i);
+ userPackageSettingsEntries.add(new WatchdogStorage.UserPackageSettingsEntry(
+ usage.userId, usage.genericPackageName, usage.getKillableState()));
+ if (!usage.ioUsage.hasUsage()) {
+ continue;
+ }
+ 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));
+ }
+ for (int i = 0; i < mDefaultNotKillableGenericPackages.size(); i++) {
+ String packageName = mDefaultNotKillableGenericPackages.valueAt(i);
+ userPackageSettingsEntries.add(new WatchdogStorage.UserPackageSettingsEntry(
+ UserHandle.ALL.getIdentifier(), packageName, KILLABLE_STATE_NO));
+ }
}
- List<WatchdogStorage.UserPackageSettingsEntry> entries =
- new ArrayList<>(mUsageByUserPackage.size());
- for (int i = 0; i < mUsageByUserPackage.size(); ++i) {
- PackageResourceUsage usage = mUsageByUserPackage.valueAt(i);
- entries.add(new WatchdogStorage.UserPackageSettingsEntry(
- usage.userId, usage.genericPackageName, usage.getKillableState()));
- }
- for (String packageName : mDefaultNotKillableGenericPackages) {
- entries.add(new WatchdogStorage.UserPackageSettingsEntry(
- UserHandle.USER_ALL, packageName, KILLABLE_STATE_NO));
- }
- if (!mWatchdogStorage.saveUserPackageSettings(entries)) {
+ boolean userPackageSettingResult =
+ mWatchdogStorage.saveUserPackageSettings(userPackageSettingsEntries);
+ if (!userPackageSettingResult) {
Slogf.e(TAG, "Failed to write user package settings to database");
} else {
Slogf.i(TAG, "Successfully saved %d user package settings to database",
- entries.size());
+ userPackageSettingsEntries.size());
}
- writeStatsLocked();
- mIsWrittenToDatabase = true;
+ if (writeStats(ioUsageStatsEntries, forgivePackagesByUserId)
+ && userPackageSettingResult) {
+ mWatchdogStorage.markWriteSuccessful();
+ }
+ } finally {
+ mWatchdogStorage.endWrite();
}
}
@@ -1153,36 +1187,19 @@
? KILLABLE_STATE_NO : KILLABLE_STATE_YES;
}
- @GuardedBy("mLock")
- private void writeStatsLocked() {
- 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;
- }
- 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));
- }
+ private boolean writeStats(List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries,
+ SparseArray<List<String>> forgivePackagesByUserId) {
// 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.",
+ Slogf.i(TAG, "Attempted to forgive historical overuses for %d users.",
forgivePackagesByUserId.size());
}
-
+ if (ioUsageStatsEntries.isEmpty()) {
+ return true;
+ }
int result = mWatchdogStorage.saveIoUsageStats(ioUsageStatsEntries);
if (result == WatchdogStorage.FAILED_TRANSACTION) {
Slogf.e(TAG, "Failed to write %d I/O overuse stats to database",
@@ -1191,6 +1208,7 @@
Slogf.i(TAG, "Successfully saved %d/%d I/O overuse stats to database",
result, ioUsageStatsEntries.size());
}
+ return result != WatchdogStorage.FAILED_TRANSACTION;
}
@GuardedBy("mLock")
@@ -1219,6 +1237,7 @@
int killableState = usage.syncAndFetchKillableState(
componentType, isSafeToKill, defaultKillableState);
mUsageByUserPackage.put(key, usage);
+ mWatchdogStorage.markDirty();
return killableState;
}
@@ -1252,16 +1271,13 @@
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) {
- writeToDatabase();
- }
- for (int i = 0; i < mUsageByUserPackage.size(); ++i) {
+ mLatestStatsReportDate = currentDate;
+ }
+ writeToDatabase();
+ synchronized (mLock) {
+ for (int i = 0; i < mUsageByUserPackage.size(); i++) {
mUsageByUserPackage.valueAt(i).resetStats();
}
- mLatestStatsReportDate = currentDate;
}
syncHistoricalNotForgivenOveruses();
if (DEBUG) {
diff --git a/service/src/com/android/car/watchdog/WatchdogStorage.java b/service/src/com/android/car/watchdog/WatchdogStorage.java
index 58c0121..7b4726a 100644
--- a/service/src/com/android/car/watchdog/WatchdogStorage.java
+++ b/service/src/com/android/car/watchdog/WatchdogStorage.java
@@ -18,6 +18,7 @@
import static com.android.car.watchdog.TimeSource.ZONE_OFFSET;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.automotive.watchdog.PerStateBytes;
@@ -41,6 +42,8 @@
import com.android.server.utils.Slogf;
import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.time.Instant;
import java.time.Period;
import java.time.ZonedDateTime;
@@ -59,6 +62,38 @@
private static final String TAG = CarLog.tagFor(WatchdogStorage.class);
private static final int RETENTION_PERIOD_IN_DAYS = 30;
+ /**
+ * The database is clean when it is synchronized with the in-memory cache. Cannot start a
+ * write while in this state.
+ */
+ private static final int DB_STATE_CLEAN = 1;
+
+ /**
+ * The database is dirty when it is not synchronized with the in-memory cache. When the
+ * database is in this state, no write is in progress.
+ */
+ private static final int DB_STATE_DIRTY = 2;
+
+ /**
+ * Database write in progress. Cannot start a new write when the database is in this state.
+ */
+ private static final int DB_STATE_WRITE_IN_PROGRESS = 3;
+
+ /**
+ * The database enters this state when the database is marked dirty while a write is in
+ * progress.
+ */
+ private static final int DB_STATE_WRITE_IN_PROGRESS_DIRTY = 4;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"DB_STATE_"}, value = {
+ DB_STATE_CLEAN,
+ DB_STATE_DIRTY,
+ DB_STATE_WRITE_IN_PROGRESS,
+ DB_STATE_WRITE_IN_PROGRESS_DIRTY
+ })
+ private @interface DatabaseStateType{}
+
public static final int FAILED_TRANSACTION = -1;
/* Stats are stored on a daily basis. */
public static final TemporalUnit STATS_TEMPORAL_UNIT = ChronoUnit.DAYS;
@@ -77,6 +112,8 @@
// the cache won't change until the next boot, so it is safe to cache the data in memory.
@GuardedBy("mLock")
private final List<IoUsageStatsEntry> mTodayIoUsageStatsEntries = new ArrayList<>();
+ @GuardedBy("mLock")
+ private @DatabaseStateType int mCurrentDbState = DB_STATE_CLEAN;
public WatchdogStorage(Context context, TimeSource timeSource) {
this(context, /* useDataSystemCarDir= */ true, timeSource);
@@ -100,6 +137,54 @@
}
}
+ /**
+ * Marks the database as dirty. The database is dirty when it is not synchronized with the
+ * memory cache.
+ */
+ public void markDirty() {
+ synchronized (mLock) {
+ mCurrentDbState = mCurrentDbState == DB_STATE_WRITE_IN_PROGRESS
+ ? DB_STATE_WRITE_IN_PROGRESS_DIRTY : DB_STATE_DIRTY;
+ }
+ Slogf.i(TAG, "Database marked dirty.");
+ }
+
+ /**
+ * Starts write to database only if database is dirty and no writing is in progress.
+ *
+ * @return {@code true} if start was successful, otherwise {@code false}.
+ */
+ public boolean startWrite() {
+ synchronized (mLock) {
+ if (mCurrentDbState != DB_STATE_DIRTY) {
+ Slogf.e(TAG, "Cannot start a new write while the DB state is %s",
+ toDbStateString(mCurrentDbState));
+ return false;
+ }
+ mCurrentDbState = DB_STATE_WRITE_IN_PROGRESS;
+ return true;
+ }
+ }
+
+ /** Ends write to database if write is in progress. */
+ public void endWrite() {
+ synchronized (mLock) {
+ mCurrentDbState = mCurrentDbState == DB_STATE_CLEAN ? DB_STATE_CLEAN : DB_STATE_DIRTY;
+ }
+ }
+
+ /** Marks the database as clean during an in progress database write. */
+ public void markWriteSuccessful() {
+ synchronized (mLock) {
+ if (mCurrentDbState != DB_STATE_WRITE_IN_PROGRESS) {
+ Slogf.e(TAG, "Failed to mark write successful as the current db state is %s",
+ toDbStateString(mCurrentDbState));
+ return;
+ }
+ mCurrentDbState = DB_STATE_CLEAN;
+ }
+ }
+
/** Saves the given user package settings entries and returns whether the change succeeded. */
public boolean saveUserPackageSettings(List<UserPackageSettingsEntry> entries) {
ArraySet<Integer> usersWithMissingIds = new ArraySet<>();
@@ -453,6 +538,21 @@
return rows.size();
}
+ private static String toDbStateString(int dbState) {
+ switch (dbState) {
+ case DB_STATE_CLEAN:
+ return "DB_STATE_CLEAN";
+ case DB_STATE_DIRTY:
+ return "DB_STATE_DIRTY";
+ case DB_STATE_WRITE_IN_PROGRESS:
+ return "DB_STATE_WRITE_IN_PROGRESS";
+ case DB_STATE_WRITE_IN_PROGRESS_DIRTY:
+ return "DB_STATE_WRITE_IN_PROGRESS_DIRTY";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
/** Defines the user package settings entry stored in the UserPackageSettingsTable. */
static final class UserPackageSettingsEntry {
public final @UserIdInt int userId;
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 5e5988b..db9966b 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
@@ -45,6 +45,7 @@
import static com.android.car.watchdog.TimeSource.ZONE_OFFSET;
import static com.android.car.watchdog.WatchdogPerfHandler.INTENT_EXTRA_ID;
import static com.android.car.watchdog.WatchdogStorage.RETENTION_PERIOD;
+import static com.android.car.watchdog.WatchdogStorage.WatchdogDbHelper.DATABASE_NAME;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -199,7 +200,6 @@
@Mock private Resources mMockResources;
@Mock private IBinder mMockBinder;
@Mock private ICarWatchdog mMockCarWatchdogDaemon;
- @Mock private WatchdogStorage mMockWatchdogStorage;
@Mock private UserNotificationHelper mMockUserNotificationHelper;
@Captor private ArgumentCaptor<ICarPowerStateListener> mICarPowerStateListenerCaptor;
@@ -231,6 +231,7 @@
private CarWatchdogService mCarWatchdogService;
private ICarWatchdogServiceForSystem mWatchdogServiceForSystemImpl;
private IBinder.DeathRecipient mCarWatchdogDaemonBinderDeathRecipient;
+ private WatchdogStorage mMockWatchdogStorage;
private BroadcastReceiver mBroadcastReceiver;
private boolean mIsDaemonCrashed;
private ICarPowerStateListener mCarPowerStateListener;
@@ -304,6 +305,13 @@
mTempSystemCarDir = Files.createTempDirectory("watchdog_test").toFile();
when(mMockSystemInterface.getSystemCarDir()).thenReturn(mTempSystemCarDir);
+ File tempDbFile = new File(mTempSystemCarDir.getPath(), DATABASE_NAME);
+ when(mMockContext.createDeviceProtectedStorageContext()).thenReturn(mMockContext);
+ when(mMockContext.getDatabasePath(DATABASE_NAME)).thenReturn(tempDbFile);
+ mMockWatchdogStorage =
+ spy(new WatchdogStorage(mMockContext, /* useDataSystemCarDir= */ false,
+ mTimeSource));
+
setupUsers();
mockWatchdogDaemon();
mockWatchdogStorage();
@@ -623,10 +631,10 @@
long startTime = mTimeSource.getCurrentDateTime().minusDays(4).toEpochSecond();
long duration = mTimeSource.now().getEpochSecond() - startTime;
- when(mMockWatchdogStorage.getHistoricalIoOveruseStats(
- UserHandle.getUserId(uid), packageName, 6))
- .thenReturn(new IoOveruseStats.Builder(startTime, duration).setTotalOveruses(5)
- .setTotalTimesKilled(2).setTotalBytesWritten(24_000).build());
+ doReturn(new IoOveruseStats.Builder(startTime, duration).setTotalOveruses(5)
+ .setTotalTimesKilled(2).setTotalBytesWritten(24_000).build())
+ .when(mMockWatchdogStorage)
+ .getHistoricalIoOveruseStats(UserHandle.getUserId(uid), packageName, 6);
injectIoOveruseStatsForPackages(mGenericPackageNameByUid,
/* killablePackages= */ Collections.singleton(packageName),
@@ -656,8 +664,8 @@
injectPackageInfos(Collections.singletonList(constructPackageManagerPackageInfo(
packageName, uid, null, ApplicationInfo.FLAG_SYSTEM, 0)));
- when(mMockWatchdogStorage.getHistoricalIoOveruseStats(
- UserHandle.getUserId(uid), packageName, 6)).thenReturn(null);
+ doReturn(null).when(mMockWatchdogStorage)
+ .getHistoricalIoOveruseStats(UserHandle.getUserId(uid), packageName, 6);
injectIoOveruseStatsForPackages(mGenericPackageNameByUid,
/* killablePackages= */ Collections.singleton(packageName),
@@ -688,10 +696,10 @@
long startTime = mTimeSource.getCurrentDateTime().minusDays(4).toEpochSecond();
long duration = mTimeSource.now().getEpochSecond() - startTime;
- when(mMockWatchdogStorage.getHistoricalIoOveruseStats(
- UserHandle.getUserId(uid), packageName, 6))
- .thenReturn(new IoOveruseStats.Builder(startTime, duration).setTotalOveruses(5)
- .setTotalTimesKilled(2).setTotalBytesWritten(24_000).build());
+ doReturn(new IoOveruseStats.Builder(startTime, duration).setTotalOveruses(5)
+ .setTotalTimesKilled(2).setTotalBytesWritten(24_000).build())
+ .when(mMockWatchdogStorage)
+ .getHistoricalIoOveruseStats(UserHandle.getUserId(uid), packageName, 6);
ResourceOveruseStats actualStats = mCarWatchdogService.getResourceOveruseStats(
CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
@@ -806,16 +814,15 @@
IoOveruseStats thirdPartyPkgOldStats = new IoOveruseStats.Builder(
startTime, now.toEpochSecond() - startTime).setTotalOveruses(5)
.setTotalTimesKilled(2).setTotalBytesWritten(24_000).build();
- when(mMockWatchdogStorage.getHistoricalIoOveruseStats(11, "third_party_package", 6))
- .thenReturn(thirdPartyPkgOldStats);
+ doReturn(thirdPartyPkgOldStats).when(mMockWatchdogStorage)
+ .getHistoricalIoOveruseStats(11, "third_party_package", 6);
startTime = now.minusDays(6).toEpochSecond();
IoOveruseStats vendorPkgOldStats = new IoOveruseStats.Builder(
startTime, now.toEpochSecond() - startTime).setTotalOveruses(2)
.setTotalTimesKilled(0).setTotalBytesWritten(35_000).build();
- when(mMockWatchdogStorage.getHistoricalIoOveruseStats(12, "vendor_package.critical", 6))
- .thenReturn(vendorPkgOldStats);
-
+ doReturn(vendorPkgOldStats).when(mMockWatchdogStorage)
+ .getHistoricalIoOveruseStats(12, "vendor_package.critical", 6);
List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */ 0,
@@ -991,15 +998,15 @@
IoOveruseStats thirdPartyPkgOldStats = new IoOveruseStats.Builder(
startTime, now.toEpochSecond() - startTime).setTotalOveruses(5)
.setTotalTimesKilled(2).setTotalBytesWritten(24_000).build();
- when(mMockWatchdogStorage.getHistoricalIoOveruseStats(11, "third_party_package", 6))
- .thenReturn(thirdPartyPkgOldStats);
+ doReturn(thirdPartyPkgOldStats).when(mMockWatchdogStorage)
+ .getHistoricalIoOveruseStats(11, "third_party_package", 6);
startTime = now.minusDays(6).toEpochSecond();
IoOveruseStats vendorPkgOldStats = new IoOveruseStats.Builder(
startTime, now.toEpochSecond() - startTime).setTotalOveruses(2)
.setTotalTimesKilled(0).setTotalBytesWritten(6_900_000).build();
- when(mMockWatchdogStorage.getHistoricalIoOveruseStats(12, "vendor_package.critical", 6))
- .thenReturn(vendorPkgOldStats);
+ doReturn(vendorPkgOldStats).when(mMockWatchdogStorage)
+ .getHistoricalIoOveruseStats(12, "vendor_package.critical", 6);
List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
@@ -1085,8 +1092,9 @@
IoOveruseStats vendorPkgOldStats = new IoOveruseStats.Builder(
startTime, now.toEpochSecond() - startTime).setTotalOveruses(2)
.setTotalTimesKilled(0).setTotalBytesWritten(6_900_000).build();
- when(mMockWatchdogStorage.getHistoricalIoOveruseStats(12, "vendor_package.critical", 6))
- .thenReturn(vendorPkgOldStats);
+
+ doReturn(vendorPkgOldStats).when(mMockWatchdogStorage)
+ .getHistoricalIoOveruseStats(12, "vendor_package.critical", 6);
ResourceOveruseStats actualStats =
mCarWatchdogService.getResourceOveruseStatsForUserPackage(
@@ -1368,6 +1376,8 @@
PackageKillableState.KILLABLE_STATE_NEVER),
new PackageKillableState("third_party_package", 13,
PackageKillableState.KILLABLE_STATE_YES));
+
+ verify(mMockWatchdogStorage, times(11)).markDirty();
}
@Test
@@ -1413,6 +1423,8 @@
PackageKillableState.KILLABLE_STATE_NO),
new PackageKillableState("third_party_package.D", 11,
PackageKillableState.KILLABLE_STATE_NO));
+
+ verify(mMockWatchdogStorage, times(7)).markDirty();
}
@Test
@@ -1460,6 +1472,8 @@
PackageKillableState.KILLABLE_STATE_NEVER),
new PackageKillableState("third_party_package", 13,
PackageKillableState.KILLABLE_STATE_NO));
+
+ verify(mMockWatchdogStorage, times(11)).markDirty();
}
@Test
@@ -1511,6 +1525,8 @@
PackageKillableState.KILLABLE_STATE_NO),
new PackageKillableState("third_party_package.B", 13,
PackageKillableState.KILLABLE_STATE_NO));
+
+ verify(mMockWatchdogStorage, times(5)).markDirty();
}
@Test
@@ -1529,6 +1545,8 @@
PackageKillableState.KILLABLE_STATE_YES),
new PackageKillableState("vendor_package.critical", 11,
PackageKillableState.KILLABLE_STATE_NEVER));
+
+ verify(mMockWatchdogStorage, times(2)).markDirty();
}
@Test
@@ -1557,6 +1575,8 @@
PackageKillableState.KILLABLE_STATE_YES),
new PackageKillableState("vendor_package.critical.B", 101,
PackageKillableState.KILLABLE_STATE_NEVER));
+
+ verify(mMockWatchdogStorage, times(6)).markDirty();
}
@Test
@@ -1577,6 +1597,8 @@
PackageKillableStateSubject.assertThat(killableStates)
.containsExactly(new PackageKillableState("some_pkg_as_vendor_pkg", 100,
PackageKillableState.KILLABLE_STATE_YES));
+
+ verify(mMockWatchdogStorage).markDirty();
}
@Test
@@ -1607,6 +1629,8 @@
PackageKillableState.KILLABLE_STATE_YES),
new PackageKillableState("third_party_package.D", 11,
PackageKillableState.KILLABLE_STATE_YES));
+
+ verify(mMockWatchdogStorage, times(2)).markDirty();
}
@Test
@@ -1638,6 +1662,8 @@
PackageKillableState.KILLABLE_STATE_YES),
new PackageKillableState("third_party_package.D", 100,
PackageKillableState.KILLABLE_STATE_YES));
+
+ verify(mMockWatchdogStorage, times(2)).markDirty();
}
@Test
@@ -1662,6 +1688,8 @@
PackageKillableState.KILLABLE_STATE_YES),
new PackageKillableState("vendor_package.B", 100,
PackageKillableState.KILLABLE_STATE_YES));
+
+ verify(mMockWatchdogStorage).markDirty();
}
@Test
@@ -1683,6 +1711,8 @@
PackageKillableState.KILLABLE_STATE_YES),
new PackageKillableState("vendor_package.critical", 12,
PackageKillableState.KILLABLE_STATE_NEVER));
+
+ verify(mMockWatchdogStorage, times(4)).markDirty();
}
@Test
@@ -1717,6 +1747,8 @@
PackageKillableState.KILLABLE_STATE_NEVER),
new PackageKillableState("vendor_package.B", 12,
PackageKillableState.KILLABLE_STATE_NEVER));
+
+ verify(mMockWatchdogStorage, times(3)).markDirty();
}
@Test
@@ -2284,6 +2316,9 @@
new PackageKillableState("third_party_package.A", 12,
PackageKillableState.KILLABLE_STATE_NO));
+ // Changing and getting package killable states marks the database as dirty
+ verify(mMockWatchdogStorage, times(5)).markDirty();
+
ResourceOveruseStatsSubject.assertThat(actualStats)
.containsExactlyElementsIn(expectedStats);
@@ -2581,10 +2616,10 @@
@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)));
+ doReturn(Arrays.asList(new WatchdogStorage.NotForgivenOverusesEntry(100,
+ "system_package.non_critical", 2)))
+ .when(mMockWatchdogStorage)
+ .getNotForgivenHistoricalIoOveruses(RECURRING_OVERUSE_PERIOD_IN_DAYS);
// Force CarWatchdogService to fetch historical not forgiven overuses.
restartService(/* totalRestarts= */ 1, /* wantedDbWrites= */ 0);
@@ -2904,10 +2939,9 @@
@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)));
+ doReturn(Arrays.asList(new WatchdogStorage.NotForgivenOverusesEntry(100,
+ "third_party_package", 2))).when(mMockWatchdogStorage)
+ .getNotForgivenHistoricalIoOveruses(RECURRING_OVERUSE_PERIOD_IN_DAYS);
// Force CarWatchdogService to fetch historical not forgiven overuses.
restartService(/* totalRestarts= */ 1, /* wantedDbWrites= */ 0);
@@ -2968,10 +3002,9 @@
@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)));
+ doReturn(Arrays.asList(new WatchdogStorage.NotForgivenOverusesEntry(100,
+ "third_party_package", 2))).when(mMockWatchdogStorage)
+ .getNotForgivenHistoricalIoOveruses(RECURRING_OVERUSE_PERIOD_IN_DAYS);
mTimeSource.updateNow(/* numDaysAgo= */ 1);
setRequiresDistractionOptimization(true);
@@ -3611,11 +3644,11 @@
}
private void mockWatchdogStorage() {
- when(mMockWatchdogStorage.saveUserPackageSettings(any())).thenAnswer((args) -> {
+ doAnswer((args) -> {
mUserPackageSettingsEntries.addAll(args.getArgument(0));
return true;
- });
- when(mMockWatchdogStorage.saveIoUsageStats(any())).thenAnswer((args) -> {
+ }).when(mMockWatchdogStorage).saveUserPackageSettings(any());
+ doAnswer((args) -> {
List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = args.getArgument(0);
for (WatchdogStorage.IoUsageStatsEntry entry : ioUsageStatsEntries) {
mIoUsageStatsEntries.add(
@@ -3627,27 +3660,30 @@
entry.ioUsage.getTotalTimesKilled())));
}
return ioUsageStatsEntries.size();
- });
- when(mMockWatchdogStorage.getUserPackageSettings()).thenReturn(mUserPackageSettingsEntries);
- when(mMockWatchdogStorage.getTodayIoUsageStats()).thenReturn(mIoUsageStatsEntries);
- when(mMockWatchdogStorage.getDailySystemIoUsageSummaries(anyLong(), anyLong(), anyLong()))
- .thenAnswer(args -> sampleDailyIoUsageSummariesForAWeek(args.getArgument(1),
- SYSTEM_DAILY_IO_USAGE_SUMMARY_MULTIPLIER));
- when(mMockWatchdogStorage.getTopUsersDailyIoUsageSummaries(
- anyInt(), anyLong(), anyLong(), anyLong())).thenAnswer(args -> {
- ArrayList<WatchdogStorage.UserPackageDailySummaries> summaries =
- new ArrayList<>();
- for (int i = 0; i < mGenericPackageNameByUid.size(); ++i) {
- int uid = mGenericPackageNameByUid.keyAt(i);
- summaries.add(new WatchdogStorage.UserPackageDailySummaries(
- UserHandle.getUserId(uid), mGenericPackageNameByUid.valueAt(i),
- sampleDailyIoUsageSummariesForAWeek(args.getArgument(2),
- /* sysOrUidMultiplier= */ uid)));
- }
- summaries.sort(Comparator.comparingLong(WatchdogStorage
- .UserPackageDailySummaries::getTotalWrittenBytes).reversed());
- return summaries;
- });
+ }).when(mMockWatchdogStorage).saveIoUsageStats(any());
+ doReturn(mUserPackageSettingsEntries).when(mMockWatchdogStorage).getUserPackageSettings();
+ doReturn(mIoUsageStatsEntries).when(mMockWatchdogStorage).getTodayIoUsageStats();
+ doReturn(List.of()).when(mMockWatchdogStorage)
+ .getNotForgivenHistoricalIoOveruses(RECURRING_OVERUSE_PERIOD_IN_DAYS);
+ doAnswer(args -> sampleDailyIoUsageSummariesForAWeek(args.getArgument(1),
+ SYSTEM_DAILY_IO_USAGE_SUMMARY_MULTIPLIER))
+ .when(mMockWatchdogStorage)
+ .getDailySystemIoUsageSummaries(anyLong(), anyLong(), anyLong());
+ doAnswer(args -> {
+ ArrayList<WatchdogStorage.UserPackageDailySummaries> summaries =
+ new ArrayList<>();
+ for (int i = 0; i < mGenericPackageNameByUid.size(); ++i) {
+ int uid = mGenericPackageNameByUid.keyAt(i);
+ summaries.add(new WatchdogStorage.UserPackageDailySummaries(
+ UserHandle.getUserId(uid), mGenericPackageNameByUid.valueAt(i),
+ sampleDailyIoUsageSummariesForAWeek(args.getArgument(2),
+ /* sysOrUidMultiplier= */ uid)));
+ }
+ summaries.sort(Comparator.comparingLong(WatchdogStorage
+ .UserPackageDailySummaries::getTotalWrittenBytes).reversed());
+ return summaries;
+ }).when(mMockWatchdogStorage)
+ .getTopUsersDailyIoUsageSummaries(anyInt(), anyLong(), anyLong(), anyLong());
}
private void setupUsers() {
@@ -3670,8 +3706,11 @@
setCarPowerState(CarPowerStateListener.SHUTDOWN_PREPARE);
setCarPowerState(CarPowerStateListener.SHUTDOWN_ENTER);
mCarWatchdogService.release();
+ verify(mMockWatchdogStorage, times(totalRestarts)).startWrite();
verify(mMockWatchdogStorage, times(wantedDbWrites)).saveIoUsageStats(any());
verify(mMockWatchdogStorage, times(wantedDbWrites)).saveUserPackageSettings(any());
+ verify(mMockWatchdogStorage, times(wantedDbWrites)).markWriteSuccessful();
+ verify(mMockWatchdogStorage, times(wantedDbWrites)).endWrite();
verify(mMockWatchdogStorage, times(totalRestarts)).release();
mCarWatchdogService = new CarWatchdogService(mMockContext, mMockWatchdogStorage,
mMockUserNotificationHelper, mTimeSource);
@@ -3983,6 +4022,10 @@
// OVERUSE_HANDLING_DELAY_MILLS delay. Wait until the below message is processed before
// returning, so the resource overuse handling is completed.
delayedRunOnMainSync(() -> {}, OVERUSE_HANDLING_DELAY_MILLS * 2);
+
+ if (mGenericPackageNameByUid.size() > 0) {
+ verify(mMockWatchdogStorage, atLeastOnce()).markDirty();
+ }
}
private void setUpSampleUserAndPackages() {
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 8d7cf30..34c3282 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
@@ -85,6 +85,55 @@
}
@Test
+ public void testStartWrite() {
+ assertWithMessage("Start write on clean db").that(mService.startWrite()).isFalse();
+
+ mService.markDirty();
+
+ assertWithMessage("Start write on a dirty DB").that(mService.startWrite()).isTrue();
+
+ mService.markWriteSuccessful();
+ mService.endWrite();
+
+ assertWithMessage("Start write again").that(mService.startWrite()).isFalse();
+ }
+
+ @Test
+ public void testStartWriteAndMarkDirty() {
+ mService.markDirty();
+
+ assertWithMessage("Start database write").that(mService.startWrite()).isTrue();
+
+ mService.markDirty();
+ mService.markWriteSuccessful();
+ mService.endWrite();
+
+ assertWithMessage("Start write again").that(mService.startWrite()).isTrue();
+ }
+
+ @Test
+ public void testStartWriteTwice() {
+ mService.markDirty();
+
+ assertWithMessage("Start database write").that(mService.startWrite()).isTrue();
+ assertWithMessage("Start database write twice").that(mService.startWrite())
+ .isFalse();
+
+ mService.endWrite();
+ }
+
+ @Test
+ public void testMarkSuccessfulWriteWithNoWriteInProgress() {
+ mService.markDirty();
+
+ // Database not marked as clean since, no write in progress
+ mService.markWriteSuccessful();
+
+ // Write successful since database is still dirty.
+ assertWithMessage("Start database write").that(mService.startWrite()).isTrue();
+ }
+
+ @Test
public void testSaveUserPackageSettings() throws Exception {
List<WatchdogStorage.UserPackageSettingsEntry> expected = sampleSettings();