Merge changes from topic "PullIoOveruseStats-sc-v2-dev" into sc-v2-dev
* changes:
Initialize CarWatchdog's UX state during init.
Pull weekly I/O usage summary metrics from CarService.
Add watchdog storage queries to fetch I/O usage summary stats.
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index be888d3..9347a83 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -62,12 +62,14 @@
import com.android.car.CarServiceUtils;
import com.android.car.ICarImpl;
import com.android.car.power.CarPowerManagementService;
+import com.android.car.systeminterface.SystemInterface;
import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.utils.Slogf;
+import java.io.File;
import java.lang.ref.WeakReference;
import java.time.Instant;
import java.util.List;
@@ -83,7 +85,11 @@
static final String ACTION_GARAGE_MODE_OFF =
"com.android.server.jobscheduler.GARAGE_MODE_OFF";
static final int MISSING_ARG_VALUE = -1;
- static final TimeSourceInterface SYSTEM_INSTANCE = new TimeSourceInterface() {
+
+ private static final String FALLBACK_DATA_SYSTEM_CAR_DIR_PATH = "/data/system/car";
+ private static final String WATCHDOG_DIR_NAME = "watchdog";
+
+ private static final TimeSource SYSTEM_INSTANCE = new TimeSource() {
@Override
public Instant now() {
return Instant.now();
@@ -145,7 +151,14 @@
}
int powerCycle = carPowerStateToPowerCycle(powerService.getPowerState());
switch (powerCycle) {
+ case PowerCycle.POWER_CYCLE_SHUTDOWN_PREPARE:
+ // Perform time consuming disk I/O operation during shutdown prepare to avoid
+ // incomplete I/O.
+ mWatchdogPerfHandler.writeMetadataFile();
+ break;
case PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER:
+ // Watchdog service and daemon performs garage mode monitoring so delay writing
+ // to database until after shutdown enter.
mWatchdogPerfHandler.writeToDatabase();
break;
// ON covers resume.
@@ -190,11 +203,11 @@
private boolean mIsDisplayEnabled;
public CarWatchdogService(Context context) {
- this(context, new WatchdogStorage(context));
+ this(context, new WatchdogStorage(context, SYSTEM_INSTANCE), SYSTEM_INSTANCE);
}
@VisibleForTesting
- CarWatchdogService(Context context, WatchdogStorage watchdogStorage) {
+ CarWatchdogService(Context context, WatchdogStorage watchdogStorage, TimeSource timeSource) {
mContext = context;
mWatchdogStorage = watchdogStorage;
mPackageInfoHandler = new PackageInfoHandler(mContext.getPackageManager());
@@ -203,7 +216,7 @@
mWatchdogProcessHandler = new WatchdogProcessHandler(mWatchdogServiceForSystem,
mCarWatchdogDaemonHelper);
mWatchdogPerfHandler = new WatchdogPerfHandler(mContext, mCarWatchdogDaemonHelper,
- mPackageInfoHandler, mWatchdogStorage);
+ mPackageInfoHandler, mWatchdogStorage, timeSource);
mConnectionListener = (isConnected) -> {
mWatchdogPerfHandler.onDaemonConnectionChange(isConnected);
synchronized (mLock) {
@@ -420,11 +433,6 @@
}
@VisibleForTesting
- void setTimeSource(TimeSourceInterface timeSource) {
- mWatchdogPerfHandler.setTimeSource(timeSource);
- }
-
- @VisibleForTesting
void setOveruseHandlingDelay(long millis) {
mWatchdogPerfHandler.setOveruseHandlingDelay(millis);
}
@@ -434,6 +442,18 @@
mWatchdogPerfHandler.setRecurringOveruseThreshold(threshold);
}
+ @VisibleForTesting
+ void setUidIoUsageSummaryTopCount(int uidIoUsageSummaryTopCount) {
+ mWatchdogPerfHandler.setUidIoUsageSummaryTopCount(uidIoUsageSummaryTopCount);
+ }
+
+ static File getWatchdogDirFile() {
+ SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
+ String systemCarDirPath = systemInterface == null ? FALLBACK_DATA_SYSTEM_CAR_DIR_PATH
+ : systemInterface.getSystemCarDir().getAbsolutePath();
+ return new File(systemCarDirPath, WATCHDOG_DIR_NAME);
+ }
+
private void notifyAllUserStates() {
UserManager userManager = UserManager.get(mContext);
List<UserInfo> users = userManager.getUsers();
diff --git a/service/src/com/android/car/watchdog/TimeSource.java b/service/src/com/android/car/watchdog/TimeSource.java
new file mode 100644
index 0000000..6537ce2
--- /dev/null
+++ b/service/src/com/android/car/watchdog/TimeSource.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 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.car.watchdog;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * Provider for the current value of "now" for users of {@code java.time}.
+ */
+public abstract class TimeSource {
+ public static final ZoneOffset ZONE_OFFSET = ZoneOffset.UTC;
+
+ /** Returns the current instant from the time source implementation. */
+ abstract Instant now();
+
+ /** Returns the current date time for the UTC timezone. */
+ public ZonedDateTime getCurrentDateTime() {
+ return now().atZone(ZONE_OFFSET);
+ }
+
+ /** Returns the current date for the UTC timezone without time. */
+ public ZonedDateTime getCurrentDate() {
+ return now().atZone(ZONE_OFFSET).truncatedTo(ChronoUnit.DAYS);
+ }
+}
diff --git a/service/src/com/android/car/watchdog/TimeSourceInterface.java b/service/src/com/android/car/watchdog/TimeSourceInterface.java
deleted file mode 100644
index 1bd6508..0000000
--- a/service/src/com/android/car/watchdog/TimeSourceInterface.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2021 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.car.watchdog;
-
-import java.time.Instant;
-
-/**
- * Provider for the current value of "now" for users of {@code java.time}.
- */
-public interface TimeSourceInterface {
- /** Returns the current instant from the time source implementation. */
- Instant now();
-}
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index 0fcaeb0..6103560 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -16,6 +16,8 @@
package com.android.car.watchdog;
+import static android.app.StatsManager.PULL_SKIP;
+import static android.app.StatsManager.PULL_SUCCESS;
import static android.car.watchdog.CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO;
import static android.car.watchdog.CarWatchdogManager.STATS_PERIOD_CURRENT_DAY;
import static android.car.watchdog.CarWatchdogManager.STATS_PERIOD_PAST_15_DAYS;
@@ -38,18 +40,20 @@
import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_INTERACTION_MODE;
import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_NO_INTERACTION_MODE;
import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__UID_STATE__UNKNOWN_UID_STATE;
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_SYSTEM_IO_USAGE_SUMMARY;
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_UID_IO_USAGE_SUMMARY;
import static com.android.car.watchdog.CarWatchdogService.DEBUG;
-import static com.android.car.watchdog.CarWatchdogService.SYSTEM_INSTANCE;
import static com.android.car.watchdog.CarWatchdogService.TAG;
import static com.android.car.watchdog.PackageInfoHandler.SHARED_PACKAGE_PREFIX;
-import static com.android.car.watchdog.WatchdogStorage.STATS_TEMPORAL_UNIT;
-import static com.android.car.watchdog.WatchdogStorage.ZONE_OFFSET;
+import static com.android.car.watchdog.TimeSource.ZONE_OFFSET;
+import static com.android.car.watchdog.WatchdogStorage.RETENTION_PERIOD;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityThread;
-import android.automotive.watchdog.ResourceType;
+import android.app.StatsManager;
+import android.app.StatsManager.PullAtomMetadata;
import android.automotive.watchdog.internal.ApplicationCategoryType;
import android.automotive.watchdog.internal.ComponentType;
import android.automotive.watchdog.internal.GarageMode;
@@ -88,8 +92,13 @@
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Pair;
import android.util.SparseArray;
+import android.util.StatsEvent;
import android.view.Display;
import com.android.car.CarLocalServices;
@@ -98,19 +107,32 @@
import com.android.car.CarUxRestrictionsManagerService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.Preconditions;
import com.android.server.utils.Slogf;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.time.ZoneOffset;
+import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@@ -121,10 +143,26 @@
public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS = "MAPS";
public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA = "MEDIA";
public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_UNKNOWN = "UNKNOWN";
-
+ public static final int UID_IO_USAGE_SUMMARY_TOP_COUNT = 10;
+ public static final int UID_IO_USAGE_SUMMARY_MIN_WEEKLY_WRITTEN_BYTES = 500 * 1024 * 1024;
+ private static final String METADATA_FILENAME = "metadata.json";
+ private static final String SYSTEM_IO_USAGE_SUMMARY_REPORTED_DATE =
+ "systemIoUsageSummaryReportedDate";
+ private static final String UID_IO_USAGE_SUMMARY_REPORTED_DATE =
+ "uidIoUsageSummaryReportedDate";
private static final long OVERUSE_HANDLING_DELAY_MILLS = 10_000;
private static final long MAX_WAIT_TIME_MILLS = 3_000;
+ private static final PullAtomMetadata PULL_ATOM_METADATA =
+ new PullAtomMetadata.Builder()
+ // Summary atoms are populated only once a week. So, a longer cool down duration
+ // is sufficient.
+ .setCoolDownMillis(TimeUnit.MILLISECONDS.convert(1L, TimeUnit.HOURS))
+ // When summary atoms are populated once a week, watchdog needs additional time
+ // for reading from disk/DB.
+ .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.
@@ -195,11 +233,17 @@
@GuardedBy("mLock")
private @GarageMode int mCurrentGarageMode;
@GuardedBy("mLock")
- private TimeSourceInterface mTimeSource;
+ private TimeSource mTimeSource;
@GuardedBy("mLock")
private long mOveruseHandlingDelayMills;
@GuardedBy("mLock")
private long mRecurringOveruseThreshold;
+ @GuardedBy("mLock")
+ private ZonedDateTime mLastSystemIoUsageSummaryReportedDate;
+ @GuardedBy("mLock")
+ private ZonedDateTime mLastUidIoUsageSummaryReportedDate;
+ @GuardedBy("mLock")
+ private int mUidIoUsageSummaryTopCount;
private final ICarUxRestrictionsChangeListener mCarUxRestrictionsChangeListener =
new ICarUxRestrictionsChangeListener.Stub() {
@@ -213,7 +257,8 @@
};
public WatchdogPerfHandler(Context context, CarWatchdogDaemonHelper daemonHelper,
- PackageInfoHandler packageInfoHandler, WatchdogStorage watchdogStorage) {
+ PackageInfoHandler packageInfoHandler, WatchdogStorage watchdogStorage,
+ TimeSource timeSource) {
mContext = context;
mCarWatchdogDaemonHelper = daemonHelper;
mPackageInfoHandler = packageInfoHandler;
@@ -222,11 +267,12 @@
CarWatchdogService.class.getSimpleName()).getLooper());
mWatchdogStorage = watchdogStorage;
mOveruseConfigurationCache = new OveruseConfigurationCache();
- mTimeSource = SYSTEM_INSTANCE;
+ mTimeSource = timeSource;
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;
}
/** Initializes the handler. */
@@ -237,11 +283,27 @@
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.
+ StatsManager statsManager = mContext.getSystemService(StatsManager.class);
+ statsManager.setPullAtomCallback(CAR_WATCHDOG_SYSTEM_IO_USAGE_SUMMARY,
+ PULL_ATOM_METADATA, ConcurrentUtils.DIRECT_EXECUTOR, this::onPullAtom);
+ statsManager.setPullAtomCallback(CAR_WATCHDOG_UID_IO_USAGE_SUMMARY,
+ PULL_ATOM_METADATA, ConcurrentUtils.DIRECT_EXECUTOR, this::onPullAtom);
+ });
- CarLocalServices.getService(CarUxRestrictionsManagerService.class)
- .registerUxRestrictionsChangeListener(
- mCarUxRestrictionsChangeListener, Display.DEFAULT_DISPLAY);
+ CarUxRestrictionsManagerService carUxRestrictionsManagerService =
+ CarLocalServices.getService(CarUxRestrictionsManagerService.class);
+ CarUxRestrictions uxRestrictions =
+ carUxRestrictionsManagerService.getCurrentUxRestrictions();
+ synchronized (mLock) {
+ mCurrentUxRestrictions = uxRestrictions;
+ applyCurrentUxRestrictionsLocked();
+ }
+ carUxRestrictionsManagerService.registerUxRestrictionsChangeListener(
+ mCarUxRestrictionsChangeListener, Display.DEFAULT_DISPLAY);
if (DEBUG) {
Slogf.d(TAG, "WatchdogPerfHandler is initialized");
@@ -820,13 +882,6 @@
}
}
- /** Sets the time source. */
- public void setTimeSource(TimeSourceInterface timeSource) {
- synchronized (mLock) {
- mTimeSource = timeSource;
- }
- }
-
/**
* Sets the delay to handle resource overuse after the package is notified of resource overuse.
*/
@@ -843,6 +898,56 @@
}
}
+ /** Sets top N UID I/O usage summaries to report on stats pull. */
+ public void setUidIoUsageSummaryTopCount(int uidIoUsageSummaryTopCount) {
+ synchronized (mLock) {
+ mUidIoUsageSummaryTopCount = uidIoUsageSummaryTopCount;
+ }
+ }
+
+ /** Writes to watchdog metadata file. */
+ public void writeMetadataFile() {
+ ZonedDateTime systemIoUsageSummaryReportDate;
+ ZonedDateTime uidIoUsageSummaryReportDate;
+ synchronized (mLock) {
+ if (mLastSystemIoUsageSummaryReportedDate == null
+ && mLastUidIoUsageSummaryReportedDate == null) {
+ return;
+ }
+ systemIoUsageSummaryReportDate = mLastSystemIoUsageSummaryReportedDate;
+ uidIoUsageSummaryReportDate = mLastUidIoUsageSummaryReportedDate;
+ }
+ File file = getWatchdogMetadataFile();
+ AtomicFile atomicFile = new AtomicFile(file);
+ FileOutputStream fos = null;
+ try {
+ fos = atomicFile.startWrite();
+ try (JsonWriter jsonWriter =
+ new JsonWriter(new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
+ jsonWriter.beginObject();
+ if (systemIoUsageSummaryReportDate != null) {
+ jsonWriter.name(SYSTEM_IO_USAGE_SUMMARY_REPORTED_DATE)
+ .value(systemIoUsageSummaryReportDate
+ .format(DateTimeFormatter.ISO_DATE_TIME));
+ }
+ if (uidIoUsageSummaryReportDate != null) {
+ jsonWriter.name(UID_IO_USAGE_SUMMARY_REPORTED_DATE)
+ .value(uidIoUsageSummaryReportDate
+ .format(DateTimeFormatter.ISO_DATE_TIME));
+ }
+ jsonWriter.endObject();
+ }
+ atomicFile.finishWrite(fos);
+ if (DEBUG) {
+ Slogf.e(TAG, "Successfully wrote watchdog metadata file '%s'",
+ file.getAbsoluteFile());
+ }
+ } catch (IOException e) {
+ Slogf.e(TAG, e, "Failed to write watchdog metadata file '%s'", file.getAbsoluteFile());
+ atomicFile.failWrite(fos);
+ }
+ }
+
/** Fetches and syncs the resource overuse configurations from watchdog daemon. */
private void fetchAndSyncResourceOveruseConfigurations() {
List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> internalConfigs;
@@ -890,8 +995,7 @@
usage.setKillableState(entry.killableState);
mUsageByUserPackage.put(key, usage);
}
- ZonedDateTime curReportDate =
- mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
+ ZonedDateTime curReportDate = mTimeSource.getCurrentDate();
for (int i = 0; i < ioStatsEntries.size(); ++i) {
WatchdogStorage.IoUsageStatsEntry entry = ioStatsEntries.get(i);
String key = getUserPackageUniqueId(entry.userId, entry.packageName);
@@ -1025,8 +1129,7 @@
@GuardedBy("mLock")
private void checkAndHandleDateChangeLocked() {
- ZonedDateTime currentDate = mTimeSource.now().atZone(ZoneOffset.UTC)
- .truncatedTo(STATS_TEMPORAL_UNIT);
+ ZonedDateTime currentDate = mTimeSource.getCurrentDate();
if (currentDate.equals(mLatestStatsReportDate)) {
return;
}
@@ -1440,6 +1543,269 @@
writtenBytes.backgroundBytes, writtenBytes.garageModeBytes));
}
+ private int onPullAtom(int atomTag, List<StatsEvent> data) {
+ if (atomTag != CAR_WATCHDOG_SYSTEM_IO_USAGE_SUMMARY
+ && atomTag != CAR_WATCHDOG_UID_IO_USAGE_SUMMARY) {
+ Slogf.e(TAG, "Unexpected atom tag: %d", atomTag);
+ return PULL_SKIP;
+ }
+ synchronized (mLock) {
+ if (mLastSystemIoUsageSummaryReportedDate == null
+ || mLastUidIoUsageSummaryReportedDate == null) {
+ readMetadataFileLocked();
+ }
+ }
+ ZonedDateTime reportDate;
+ switch (atomTag) {
+ case CAR_WATCHDOG_SYSTEM_IO_USAGE_SUMMARY:
+ synchronized (mLock) {
+ reportDate = mLastSystemIoUsageSummaryReportedDate;
+ }
+ pullAtomsForWeeklyPeriodsSinceReportedDate(reportDate, data,
+ this::pullSystemIoUsageSummaryStatsEvents);
+ synchronized (mLock) {
+ mLastSystemIoUsageSummaryReportedDate = mTimeSource.getCurrentDate();
+ }
+ break;
+ case CAR_WATCHDOG_UID_IO_USAGE_SUMMARY:
+ synchronized (mLock) {
+ reportDate = mLastUidIoUsageSummaryReportedDate;
+ }
+ pullAtomsForWeeklyPeriodsSinceReportedDate(reportDate, data,
+ this::pullUidIoUsageSummaryStatsEvents);
+ synchronized (mLock) {
+ mLastUidIoUsageSummaryReportedDate = mTimeSource.getCurrentDate();
+ }
+ break;
+ }
+ return PULL_SUCCESS;
+ }
+
+ @GuardedBy("mLock")
+ private void readMetadataFileLocked() {
+ mLastSystemIoUsageSummaryReportedDate = mLastUidIoUsageSummaryReportedDate =
+ mTimeSource.getCurrentDate().minus(RETENTION_PERIOD);
+ File file = getWatchdogMetadataFile();
+ if (!file.exists()) {
+ Slogf.e(TAG, "Watchdog metadata file '%s' doesn't exist", file.getAbsoluteFile());
+ return;
+ }
+ AtomicFile atomicFile = new AtomicFile(file);
+ try (FileInputStream fis = atomicFile.openRead()) {
+ JsonReader reader = new JsonReader(new InputStreamReader(fis, StandardCharsets.UTF_8));
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ switch (name) {
+ case SYSTEM_IO_USAGE_SUMMARY_REPORTED_DATE:
+ mLastSystemIoUsageSummaryReportedDate =
+ ZonedDateTime.parse(reader.nextString(),
+ DateTimeFormatter.ISO_DATE_TIME.withZone(ZONE_OFFSET));
+ break;
+ case UID_IO_USAGE_SUMMARY_REPORTED_DATE:
+ mLastUidIoUsageSummaryReportedDate =
+ ZonedDateTime.parse(reader.nextString(),
+ DateTimeFormatter.ISO_DATE_TIME.withZone(ZONE_OFFSET));
+ break;
+ default:
+ Slogf.w(TAG, "Unrecognized key: %s", name);
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+ if (DEBUG) {
+ Slogf.e(TAG, "Successfully read watchdog metadata file '%s'",
+ file.getAbsoluteFile());
+ }
+ } catch (IOException e) {
+ Slogf.e(TAG, e, "Failed to read watchdog metadata file '%s'", file.getAbsoluteFile());
+ } catch (NumberFormatException | IllegalStateException | DateTimeParseException e) {
+ Slogf.e(TAG, e, "Unexpected format in watchdog metadata file '%s'",
+ file.getAbsoluteFile());
+ }
+ }
+
+ private void pullAtomsForWeeklyPeriodsSinceReportedDate(ZonedDateTime reportedDate,
+ List<StatsEvent> data, BiConsumer<Pair<ZonedDateTime, ZonedDateTime>,
+ List<StatsEvent>> pullAtomCallback) {
+ ZonedDateTime now;
+ synchronized (mLock) {
+ now = mTimeSource.getCurrentDate();
+ }
+ ZonedDateTime nextReportWeekStartDate = reportedDate.with(ChronoField.DAY_OF_WEEK, 1)
+ .truncatedTo(ChronoUnit.DAYS);
+ while (ChronoUnit.WEEKS.between(nextReportWeekStartDate, now) > 0) {
+ pullAtomCallback.accept(
+ new Pair<>(nextReportWeekStartDate, nextReportWeekStartDate.plusWeeks(1)),
+ data);
+ nextReportWeekStartDate = nextReportWeekStartDate.plusWeeks(1);
+ }
+ }
+
+ private void pullSystemIoUsageSummaryStatsEvents(Pair<ZonedDateTime, ZonedDateTime> period,
+ List<StatsEvent> data) {
+ List<AtomsProto.CarWatchdogDailyIoUsageSummary> dailyIoUsageSummaries =
+ mWatchdogStorage.getDailySystemIoUsageSummaries(period.first.toEpochSecond(),
+ period.second.toEpochSecond());
+ if (dailyIoUsageSummaries == null) {
+ Slogf.i(TAG, "No system I/O usage summary stats available to pull");
+ return;
+ }
+
+ AtomsProto.CarWatchdogEventTimePeriod evenTimePeriod =
+ AtomsProto.CarWatchdogEventTimePeriod.newBuilder()
+ .setPeriod(AtomsProto.CarWatchdogEventTimePeriod.Period.WEEKLY)
+ .setStartTimeMillis(period.first.toEpochSecond()).build();
+ data.add(CarStatsLog.buildStatsEvent(CAR_WATCHDOG_SYSTEM_IO_USAGE_SUMMARY,
+ AtomsProto.CarWatchdogIoUsageSummary.newBuilder()
+ .setEventTimePeriod(evenTimePeriod)
+ .addAllDailyIoUsageSummary(dailyIoUsageSummaries).build()
+ .toByteArray()));
+
+ Slogf.i(TAG, "Successfully pulled system I/O usage summary stats");
+ }
+
+ private void pullUidIoUsageSummaryStatsEvents(Pair<ZonedDateTime, ZonedDateTime> period,
+ List<StatsEvent> data) {
+ int numTopUsers;
+ synchronized (mLock) {
+ numTopUsers = mUidIoUsageSummaryTopCount;
+ }
+ // Fetch summaries for twice the top N user packages because if the UID cannot be resolved
+ // for some user packages, the fetched summaries will still contain enough entries to pull.
+ List<WatchdogStorage.UserPackageDailySummaries> topUsersDailyIoUsageSummaries =
+ mWatchdogStorage.getTopUsersDailyIoUsageSummaries(numTopUsers * 2,
+ UID_IO_USAGE_SUMMARY_MIN_WEEKLY_WRITTEN_BYTES,
+ period.first.toEpochSecond(), period.second.toEpochSecond());
+ if (topUsersDailyIoUsageSummaries == null) {
+ Slogf.i(TAG, "No top users' I/O usage summary stats available to pull");
+ return;
+ }
+
+ SparseArray<List<String>> genericPackageNamesByUserId = new SparseArray<>();
+ for (int i = 0; i < topUsersDailyIoUsageSummaries.size(); ++i) {
+ WatchdogStorage.UserPackageDailySummaries entry =
+ topUsersDailyIoUsageSummaries.get(i);
+ List<String> genericPackageNames = genericPackageNamesByUserId.get(entry.userId);
+ if (genericPackageNames == null) {
+ genericPackageNames = new ArrayList<>();
+ }
+ genericPackageNames.add(entry.packageName);
+ genericPackageNamesByUserId.put(entry.userId, genericPackageNames);
+ }
+
+ SparseArray<Map<String, Integer>> packageUidsByUserId =
+ getPackageUidsForUsers(genericPackageNamesByUserId);
+
+ AtomsProto.CarWatchdogEventTimePeriod.Builder evenTimePeriodBuilder =
+ AtomsProto.CarWatchdogEventTimePeriod.newBuilder()
+ .setPeriod(AtomsProto.CarWatchdogEventTimePeriod.Period.WEEKLY)
+ .setStartTimeMillis(period.first.toEpochSecond());
+
+ int numPulledUidSummaryStats = 0;
+ for (int i = 0; i < topUsersDailyIoUsageSummaries.size()
+ && numPulledUidSummaryStats < numTopUsers; ++i) {
+ WatchdogStorage.UserPackageDailySummaries entry = topUsersDailyIoUsageSummaries.get(i);
+ Map<String, Integer> uidsByGenericPackageName = packageUidsByUserId.get(entry.userId);
+ if (uidsByGenericPackageName == null
+ || !uidsByGenericPackageName.containsKey(entry.packageName)) {
+ Slogf.e(TAG, "Failed to fetch uid for package %s and user %d. So, skipping "
+ + "reporting stats for this user package", entry.packageName, entry.userId);
+ continue;
+ }
+ data.add(CarStatsLog.buildStatsEvent(CAR_WATCHDOG_UID_IO_USAGE_SUMMARY,
+ uidsByGenericPackageName.get(entry.packageName),
+ AtomsProto.CarWatchdogIoUsageSummary.newBuilder()
+ .setEventTimePeriod(evenTimePeriodBuilder)
+ .addAllDailyIoUsageSummary(entry.dailyIoUsageSummaries).build()
+ .toByteArray()));
+ ++numPulledUidSummaryStats;
+ }
+
+ Slogf.e(TAG, "Successfully pulled top %d users' I/O usage summary stats",
+ numPulledUidSummaryStats);
+ }
+
+ private SparseArray<Map<String, Integer>> getPackageUidsForUsers(
+ SparseArray<List<String>> genericPackageNamesByUserId) {
+ PackageManager pm = mContext.getPackageManager();
+ SparseArray<Map<String, Integer>> packageUidsByUserId = new SparseArray<>();
+ for (int i = 0; i < genericPackageNamesByUserId.size(); ++i) {
+ int userId = genericPackageNamesByUserId.keyAt(i);
+ Map<String, Integer> uidsByGenericPackageName = getPackageUidsForUser(pm,
+ genericPackageNamesByUserId.valueAt(i), userId);
+ if (!uidsByGenericPackageName.isEmpty()) {
+ packageUidsByUserId.put(userId, uidsByGenericPackageName);
+ }
+ }
+ return packageUidsByUserId;
+ }
+
+ /**
+ * Returns UIDs for the given generic package names belonging to the given user.
+ *
+ * <p>{@code pm.getInstalledPackagesAsUser} call is expensive as it fetches all installed
+ * packages for the given user. Thus this method should be called for all packages that requires
+ * the UIDs to be resolved in a single call.
+ */
+ private Map<String, Integer> getPackageUidsForUser(PackageManager pm,
+ List<String> genericPackageNames, int userId) {
+ Map<String, Integer> uidsByGenericPackageNames = new ArrayMap<>();
+ Set<String> resolveSharedUserIds = new ArraySet<>();
+ for (int i = 0; i < genericPackageNames.size(); ++i) {
+ String genericPackageName = genericPackageNames.get(i);
+ PackageResourceUsage usage;
+ synchronized (mLock) {
+ usage = mUsageByUserPackage.get(getUserPackageUniqueId(userId,
+ genericPackageName));
+ }
+ if (usage != null && usage.getUid() != INVALID_UID) {
+ uidsByGenericPackageNames.put(genericPackageName, usage.getUid());
+ continue;
+ }
+ if (genericPackageName.startsWith(SHARED_PACKAGE_PREFIX)) {
+ resolveSharedUserIds.add(
+ genericPackageName.substring(SHARED_PACKAGE_PREFIX.length()));
+ continue;
+ }
+ int uid = getPackageUidAsUser(pm, genericPackageName, userId);
+ if (uid != INVALID_UID) {
+ uidsByGenericPackageNames.put(genericPackageName, uid);
+ }
+ }
+ if (resolveSharedUserIds.isEmpty()) {
+ return uidsByGenericPackageNames;
+ }
+ List<PackageInfo> packageInfos = pm.getInstalledPackagesAsUser(/* flags= */ 0, userId);
+ for (int i = 0; i < packageInfos.size() && !resolveSharedUserIds.isEmpty(); ++i) {
+ PackageInfo packageInfo = packageInfos.get(i);
+ if (packageInfo.sharedUserId == null
+ || !resolveSharedUserIds.contains(packageInfo.sharedUserId)) {
+ continue;
+ }
+ int uid = getPackageUidAsUser(pm, packageInfo.packageName, userId);
+ if (uid != INVALID_UID) {
+ uidsByGenericPackageNames.put(SHARED_PACKAGE_PREFIX + packageInfo.sharedUserId,
+ uid);
+ }
+ resolveSharedUserIds.remove(packageInfo.sharedUserId);
+ }
+ return uidsByGenericPackageNames;
+ }
+
+ private int getPackageUidAsUser(PackageManager pm, String packageName, @UserIdInt int userId) {
+ try {
+ return pm.getPackageUidAsUser(packageName, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slogf.e(TAG, "Package %s for user %d is not found", packageName, userId);
+ return INVALID_UID;
+ }
+ }
+
+ private static File getWatchdogMetadataFile() {
+ return new File(CarWatchdogService.getWatchdogDirFile(), METADATA_FILENAME);
+ }
+
private static String getUserPackageUniqueId(@UserIdInt int userId, String genericPackageName) {
return userId + ":" + genericPackageName;
}
@@ -1798,10 +2164,18 @@
@VisibleForTesting
static AtomsProto.CarWatchdogPerStateBytes constructCarWatchdogPerStateBytes(
long foregroundBytes, long backgroundBytes, long garageModeBytes) {
- return AtomsProto.CarWatchdogPerStateBytes.newBuilder()
- .setForegroundBytes(foregroundBytes)
- .setBackgroundBytes(backgroundBytes)
- .setGarageModeBytes(garageModeBytes).build();
+ AtomsProto.CarWatchdogPerStateBytes.Builder perStateBytesBuilder =
+ AtomsProto.CarWatchdogPerStateBytes.newBuilder();
+ if (foregroundBytes != 0) {
+ perStateBytesBuilder.setForegroundBytes(foregroundBytes);
+ }
+ if (backgroundBytes != 0) {
+ perStateBytesBuilder.setBackgroundBytes(backgroundBytes);
+ }
+ if (garageModeBytes != 0) {
+ perStateBytesBuilder.setGarageModeBytes(garageModeBytes);
+ }
+ return perStateBytesBuilder.build();
}
private final class PackageResourceUsage {
diff --git a/service/src/com/android/car/watchdog/WatchdogStorage.java b/service/src/com/android/car/watchdog/WatchdogStorage.java
index 5b0ebb5..be1717c 100644
--- a/service/src/com/android/car/watchdog/WatchdogStorage.java
+++ b/service/src/com/android/car/watchdog/WatchdogStorage.java
@@ -16,7 +16,7 @@
package com.android.car.watchdog;
-import static com.android.car.watchdog.CarWatchdogService.SYSTEM_INSTANCE;
+import static com.android.car.watchdog.TimeSource.ZONE_OFFSET;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -40,15 +40,17 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.utils.Slogf;
+import java.io.File;
import java.time.Instant;
import java.time.Period;
-import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
/**
* Defines the database to store/retrieve system resource stats history from local storage.
@@ -61,26 +63,27 @@
/* Number of days to retain the stats in local storage. */
public static final Period RETENTION_PERIOD =
Period.ofDays(RETENTION_PERIOD_IN_DAYS).normalized();
- /* Zone offset for all date based table entries. */
- public static final ZoneOffset ZONE_OFFSET = ZoneOffset.UTC;
+ public static final String ZONE_MODIFIER = "utc";
+ public static final String DATE_MODIFIER = "unixepoch";
private final WatchdogDbHelper mDbHelper;
private final ArrayMap<String, UserPackage> mUserPackagesByKey = new ArrayMap<>();
private final ArrayMap<String, UserPackage> mUserPackagesById = new ArrayMap<>();
- private TimeSourceInterface mTimeSource = SYSTEM_INSTANCE;
+ private TimeSource mTimeSource;
private final Object mLock = new Object();
// Cache of today's I/O overuse stats collected during the previous boot. The data contained in
// 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<>();
- public WatchdogStorage(Context context) {
- this(context, /* useDataSystemCarDir= */ true);
+ public WatchdogStorage(Context context, TimeSource timeSource) {
+ this(context, /* useDataSystemCarDir= */ true, timeSource);
}
@VisibleForTesting
- WatchdogStorage(Context context, boolean useDataSystemCarDir) {
- mDbHelper = new WatchdogDbHelper(context, useDataSystemCarDir);
+ WatchdogStorage(Context context, boolean useDataSystemCarDir, TimeSource timeSource) {
+ mTimeSource = timeSource;
+ mDbHelper = new WatchdogDbHelper(context, useDataSystemCarDir, mTimeSource);
}
/** Releases resources. */
@@ -146,13 +149,12 @@
if (!mTodayIoUsageStatsEntries.isEmpty()) {
return new ArrayList<>(mTodayIoUsageStatsEntries);
}
- ZonedDateTime statsDate =
- mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
- long startEpochSeconds = statsDate.toEpochSecond();
- long endEpochSeconds = mTimeSource.now().atZone(ZONE_OFFSET).toEpochSecond();
+ long includingStartEpochSeconds = mTimeSource.getCurrentDate().toEpochSecond();
+ long excludingEndEpochSeconds = mTimeSource.getCurrentDateTime().toEpochSecond();
ArrayMap<String, WatchdogPerfHandler.PackageIoUsage> ioUsagesById;
try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) {
- ioUsagesById = IoUsageStatsTable.queryStats(db, startEpochSeconds, endEpochSeconds);
+ ioUsagesById = IoUsageStatsTable.queryStats(db, includingStartEpochSeconds,
+ excludingEndEpochSeconds);
}
for (int i = 0; i < ioUsagesById.size(); ++i) {
String id = ioUsagesById.keyAt(i);
@@ -191,12 +193,11 @@
* {@code null} when stats are not available.
*/
@Nullable
- public IoOveruseStats getHistoricalIoOveruseStats(
- @UserIdInt int userId, String packageName, int numDaysAgo) {
- ZonedDateTime currentDate =
- mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
- long startEpochSeconds = currentDate.minusDays(numDaysAgo).toEpochSecond();
- long endEpochSeconds = currentDate.toEpochSecond();
+ public IoOveruseStats getHistoricalIoOveruseStats(@UserIdInt int userId, String packageName,
+ int numDaysAgo) {
+ ZonedDateTime currentDate = mTimeSource.getCurrentDate();
+ long includingStartEpochSeconds = currentDate.minusDays(numDaysAgo).toEpochSecond();
+ long excludingEndEpochSeconds = currentDate.toEpochSecond();
try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) {
UserPackage userPackage = mUserPackagesByKey.get(
UserPackage.getKey(userId, packageName));
@@ -204,12 +205,59 @@
/* Packages without historical stats don't have userPackage entry. */
return null;
}
- return IoUsageStatsTable.queryHistoricalStats(
- db, userPackage.getUniqueId(), startEpochSeconds, endEpochSeconds);
+ return IoUsageStatsTable.queryIoOveruseStatsForUniqueId(db, userPackage.getUniqueId(),
+ includingStartEpochSeconds, excludingEndEpochSeconds);
}
}
/**
+ * Returns daily system-level I/O usage summaries for the given period or {@code null} when
+ * summaries are not available.
+ */
+ public @Nullable List<AtomsProto.CarWatchdogDailyIoUsageSummary> getDailySystemIoUsageSummaries(
+ long includingStartEpochSeconds, long excludingEndEpochSeconds) {
+ try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) {
+ return IoUsageStatsTable.queryDailySystemIoUsageSummaries(db,
+ includingStartEpochSeconds, excludingEndEpochSeconds);
+ }
+ }
+
+ /**
+ * Returns top N disk I/O users' daily I/O usage summaries for the given period or {@code null}
+ * when summaries are not available.
+ */
+ public @Nullable List<UserPackageDailySummaries> getTopUsersDailyIoUsageSummaries(
+ int numTopUsers, long minTotalWrittenBytes, long includingStartEpochSeconds,
+ long excludingEndEpochSeconds) {
+ ArrayMap<String, List<AtomsProto.CarWatchdogDailyIoUsageSummary>> summariesById;
+ try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) {
+ summariesById = IoUsageStatsTable.queryTopUsersDailyIoUsageSummaries(db,
+ numTopUsers, minTotalWrittenBytes, includingStartEpochSeconds,
+ excludingEndEpochSeconds);
+ }
+ if (summariesById == null) {
+ return null;
+ }
+ ArrayList<UserPackageDailySummaries> userPackageDailySummaries = new ArrayList<>();
+ for (int i = 0; i < summariesById.size(); ++i) {
+ String id = summariesById.keyAt(i);
+ UserPackage userPackage = mUserPackagesById.get(id);
+ if (userPackage == null) {
+ Slogf.i(TAG,
+ "Failed to find user id and package name for unique database id: '%s'",
+ id);
+ continue;
+ }
+ userPackageDailySummaries.add(new UserPackageDailySummaries(userPackage.getUserId(),
+ userPackage.getPackageName(), summariesById.valueAt(i)));
+ }
+ userPackageDailySummaries
+ .sort(Comparator.comparingLong(UserPackageDailySummaries::getTotalWrittenBytes)
+ .reversed());
+ return userPackageDailySummaries;
+ }
+
+ /**
* Deletes all user package settings and resource stats for all non-alive users.
*
* @param aliveUserIds Array of alive user ids.
@@ -230,8 +278,7 @@
@VisibleForTesting
boolean saveIoUsageStats(List<IoUsageStatsEntry> entries, boolean shouldCheckRetention) {
- ZonedDateTime currentDate =
- mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
+ ZonedDateTime currentDate = mTimeSource.getCurrentDate();
List<ContentValues> rows = new ArrayList<>(entries.size());
for (int i = 0; i < entries.size(); ++i) {
IoUsageStatsEntry entry = entries.get(i);
@@ -259,12 +306,6 @@
}
}
- @VisibleForTesting
- void setTimeSource(TimeSourceInterface timeSource) {
- mTimeSource = timeSource;
- mDbHelper.setTimeSource(timeSource);
- }
-
private void populateUserPackages(SQLiteDatabase db, ArraySet<Integer> users) {
List<UserPackage> userPackages = UserPackageSettingsTable.queryUserPackages(db, users);
for (int i = 0; i < userPackages.size(); ++i) {
@@ -302,16 +343,106 @@
/** Defines the user package settings entry stored in the UserPackageSettingsTable. */
static final class UserPackageSettingsEntry {
- public final String packageName;
public final @UserIdInt int userId;
+ public final String packageName;
public final @KillableState int killableState;
- UserPackageSettingsEntry(
- @UserIdInt int userId, String packageName, @KillableState int killableState) {
+ UserPackageSettingsEntry(@UserIdInt int userId, String packageName,
+ @KillableState int killableState) {
this.userId = userId;
this.packageName = packageName;
this.killableState = killableState;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof UserPackageSettingsEntry)) {
+ return false;
+ }
+ UserPackageSettingsEntry other = (UserPackageSettingsEntry) obj;
+ return userId == other.userId && packageName.equals(other.packageName)
+ && killableState == other.killableState;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(userId, packageName, killableState);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder().append("UserPackageSettingsEntry{userId: ").append(userId)
+ .append(", packageName: ").append(packageName)
+ .append(", killableState: ").append(killableState).append('}')
+ .toString();
+ }
+ }
+
+ /** Defines the daily summaries for user packages. */
+ static final class UserPackageDailySummaries {
+ public final @UserIdInt int userId;
+ public final String packageName;
+ public final List<AtomsProto.CarWatchdogDailyIoUsageSummary> dailyIoUsageSummaries;
+ private final long mTotalWrittenBytes;
+
+ UserPackageDailySummaries(@UserIdInt int userId, String packageName,
+ List<AtomsProto.CarWatchdogDailyIoUsageSummary> dailyIoUsageSummaries) {
+ this.userId = userId;
+ this.packageName = packageName;
+ this.dailyIoUsageSummaries = dailyIoUsageSummaries;
+ this.mTotalWrittenBytes = computeTotalWrittenBytes();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof UserPackageDailySummaries)) {
+ return false;
+ }
+ UserPackageDailySummaries other = (UserPackageDailySummaries) obj;
+ return userId == other.userId && packageName.equals(other.packageName)
+ && dailyIoUsageSummaries.equals(other.dailyIoUsageSummaries);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(userId, packageName, dailyIoUsageSummaries, mTotalWrittenBytes);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder().append("UserPackageDailySummaries{userId: ").append(userId)
+ .append(", packageName: ").append(packageName)
+ .append(", dailyIoUsageSummaries: ").append(dailyIoUsageSummaries).append('}')
+ .toString();
+ }
+
+ long getTotalWrittenBytes() {
+ return mTotalWrittenBytes;
+ }
+
+ long computeTotalWrittenBytes() {
+ long totalBytes = 0;
+ for (int i = 0; i < dailyIoUsageSummaries.size(); ++i) {
+ AtomsProto.CarWatchdogPerStateBytes writtenBytes =
+ dailyIoUsageSummaries.get(i).getWrittenBytes();
+ if (writtenBytes.hasForegroundBytes()) {
+ totalBytes += writtenBytes.getForegroundBytes();
+ }
+ if (writtenBytes.hasBackgroundBytes()) {
+ totalBytes += writtenBytes.getBackgroundBytes();
+ }
+ if (writtenBytes.hasGarageModeBytes()) {
+ totalBytes += writtenBytes.getGarageModeBytes();
+ }
+ }
+ return totalBytes;
+ }
}
/**
@@ -523,7 +654,7 @@
}
public static ArrayMap<String, WatchdogPerfHandler.PackageIoUsage> queryStats(
- SQLiteDatabase db, long startEpochSeconds, long endEpochSeconds) {
+ SQLiteDatabase db, long includingStartEpochSeconds, long excludingEndEpochSeconds) {
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append("SELECT ")
.append(COLUMN_USER_PACKAGE_ID).append(", ")
@@ -543,8 +674,8 @@
.append(COLUMN_DATE_EPOCH).append(">= ? and ")
.append(COLUMN_DATE_EPOCH).append("< ? GROUP BY ")
.append(COLUMN_USER_PACKAGE_ID);
- String[] selectionArgs = new String[]{
- String.valueOf(startEpochSeconds), String.valueOf(endEpochSeconds)};
+ String[] selectionArgs = new String[]{String.valueOf(includingStartEpochSeconds),
+ String.valueOf(excludingEndEpochSeconds)};
ArrayMap<String, WatchdogPerfHandler.PackageIoUsage> ioUsageById = new ArrayMap<>();
try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) {
@@ -552,7 +683,8 @@
android.automotive.watchdog.IoOveruseStats ioOveruseStats =
new android.automotive.watchdog.IoOveruseStats();
ioOveruseStats.startTime = cursor.getLong(1);
- ioOveruseStats.durationInSeconds = endEpochSeconds - startEpochSeconds;
+ ioOveruseStats.durationInSeconds =
+ excludingEndEpochSeconds - includingStartEpochSeconds;
ioOveruseStats.totalOveruses = cursor.getInt(2);
ioOveruseStats.writtenBytes = new PerStateBytes();
ioOveruseStats.writtenBytes.foregroundBytes = cursor.getLong(4);
@@ -574,8 +706,8 @@
return ioUsageById;
}
- public static IoOveruseStats queryHistoricalStats(SQLiteDatabase db, String uniqueId,
- long startEpochSeconds, long endEpochSeconds) {
+ public static @Nullable IoOveruseStats queryIoOveruseStatsForUniqueId(SQLiteDatabase db,
+ String uniqueId, long includingStartEpochSeconds, long excludingEndEpochSeconds) {
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append("SELECT SUM(").append(COLUMN_NUM_OVERUSES).append("), ")
.append("SUM(").append(COLUMN_NUM_TIMES_KILLED).append("), ")
@@ -588,11 +720,12 @@
.append(COLUMN_DATE_EPOCH).append(" >= ? and ")
.append(COLUMN_DATE_EPOCH).append("< ?");
String[] selectionArgs = new String[]{uniqueId,
- String.valueOf(startEpochSeconds), String.valueOf(endEpochSeconds)};
+ String.valueOf(includingStartEpochSeconds),
+ String.valueOf(excludingEndEpochSeconds)};
long totalOveruses = 0;
long totalTimesKilled = 0;
long totalBytesWritten = 0;
- long earliestEpochSecond = endEpochSeconds;
+ long earliestEpochSecond = excludingEndEpochSeconds;
try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) {
if (cursor.getCount() == 0) {
return null;
@@ -607,7 +740,7 @@
if (totalBytesWritten == 0) {
return null;
}
- long durationInSeconds = endEpochSeconds - earliestEpochSecond;
+ long durationInSeconds = excludingEndEpochSeconds - earliestEpochSecond;
IoOveruseStats.Builder statsBuilder = new IoOveruseStats.Builder(
earliestEpochSecond, durationInSeconds);
statsBuilder.setTotalOveruses(totalOveruses);
@@ -616,6 +749,118 @@
return statsBuilder.build();
}
+ public static @Nullable List<AtomsProto.CarWatchdogDailyIoUsageSummary>
+ queryDailySystemIoUsageSummaries(SQLiteDatabase db, long includingStartEpochSeconds,
+ long excludingEndEpochSeconds) {
+ StringBuilder queryBuilder = new StringBuilder();
+ queryBuilder.append("SELECT SUM(").append(COLUMN_NUM_OVERUSES).append("), ")
+ .append("SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append("), ")
+ .append("SUM(").append(COLUMN_WRITTEN_BACKGROUND_BYTES).append("), ")
+ .append("SUM(").append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append("), ")
+ .append("date(").append(COLUMN_DATE_EPOCH).append(", '").append(DATE_MODIFIER)
+ .append("', '").append(ZONE_MODIFIER).append("') as stats_date_epoch ")
+ .append("FROM ").append(TABLE_NAME).append(" WHERE ")
+ .append(COLUMN_DATE_EPOCH).append(" >= ? and ")
+ .append(COLUMN_DATE_EPOCH).append(" < ? ")
+ .append("GROUP BY stats_date_epoch ")
+ .append("HAVING SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append(" + ")
+ .append(COLUMN_WRITTEN_BACKGROUND_BYTES).append(" + ")
+ .append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append(") > 0 ")
+ .append("ORDER BY stats_date_epoch ASC");
+
+ Slogf.e(TAG, "Query: %s", queryBuilder.toString());
+
+ String[] selectionArgs = new String[]{String.valueOf(includingStartEpochSeconds),
+ String.valueOf(excludingEndEpochSeconds)};
+ List<AtomsProto.CarWatchdogDailyIoUsageSummary> summaries = new ArrayList<>();
+ try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) {
+ if (cursor.getCount() == 0) {
+ return null;
+ }
+ while (cursor.moveToNext()) {
+ summaries.add(AtomsProto.CarWatchdogDailyIoUsageSummary.newBuilder()
+ .setWrittenBytes(WatchdogPerfHandler.constructCarWatchdogPerStateBytes(
+ /* foregroundBytes= */ cursor.getLong(1),
+ /* backgroundBytes= */ cursor.getLong(2),
+ /* garageModeBytes= */ cursor.getLong(3)))
+ .setOveruseCount(cursor.getInt(0))
+ .build());
+ }
+ }
+ return summaries;
+ }
+
+ public static @Nullable ArrayMap<String, List<AtomsProto.CarWatchdogDailyIoUsageSummary>>
+ queryTopUsersDailyIoUsageSummaries(SQLiteDatabase db, int numTopUsers,
+ long minTotalWrittenBytes, long includingStartEpochSeconds,
+ long excludingEndEpochSeconds) {
+ StringBuilder innerQueryBuilder = new StringBuilder();
+ innerQueryBuilder.append("SELECT ").append(COLUMN_USER_PACKAGE_ID)
+ .append(" FROM (SELECT ").append(COLUMN_USER_PACKAGE_ID).append(", ")
+ .append("SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append(" + ")
+ .append(COLUMN_WRITTEN_BACKGROUND_BYTES).append(" + ")
+ .append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append(") AS total_written_bytes ")
+ .append("FROM ").append(TABLE_NAME).append(" WHERE ")
+ .append(COLUMN_DATE_EPOCH).append(" >= ? and ")
+ .append(COLUMN_DATE_EPOCH).append(" < ?")
+ .append(" GROUP BY ").append(COLUMN_USER_PACKAGE_ID)
+ .append(" HAVING total_written_bytes >= ").append(minTotalWrittenBytes)
+ .append(" ORDER BY total_written_bytes LIMIT ").append(numTopUsers).append(')');
+
+ StringBuilder queryBuilder = new StringBuilder();
+ queryBuilder.append("SELECT ").append(COLUMN_USER_PACKAGE_ID).append(", ")
+ .append("SUM(").append(COLUMN_NUM_OVERUSES).append("), ")
+ .append("SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append("), ")
+ .append("SUM(").append(COLUMN_WRITTEN_BACKGROUND_BYTES).append("), ")
+ .append("SUM(").append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append("), ")
+ .append("date(").append(COLUMN_DATE_EPOCH).append(", '").append(DATE_MODIFIER)
+ .append("', '").append(ZONE_MODIFIER).append("') as stats_date_epoch ")
+ .append("FROM ").append(TABLE_NAME).append(" WHERE ")
+ .append(COLUMN_DATE_EPOCH).append(" >= ? and ")
+ .append(COLUMN_DATE_EPOCH).append(" < ? and (")
+ .append(COLUMN_WRITTEN_FOREGROUND_BYTES).append(" > 0 or ")
+ .append(COLUMN_WRITTEN_BACKGROUND_BYTES).append(" > 0 or ")
+ .append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append(" > 0) and ")
+ .append(COLUMN_USER_PACKAGE_ID)
+ .append(" in (").append(innerQueryBuilder)
+ .append(") GROUP BY stats_date_epoch, ").append(COLUMN_USER_PACKAGE_ID)
+ .append(" ORDER BY ").append(COLUMN_USER_PACKAGE_ID)
+ .append(", stats_date_epoch ASC");
+
+ String[] selectionArgs = new String[]{
+ // Outer query selection arguments.
+ String.valueOf(includingStartEpochSeconds),
+ String.valueOf(excludingEndEpochSeconds),
+ // Inner query selection arguments.
+ String.valueOf(includingStartEpochSeconds),
+ String.valueOf(excludingEndEpochSeconds)};
+
+ ArrayMap<String, List<AtomsProto.CarWatchdogDailyIoUsageSummary>> summariesById =
+ new ArrayMap<>();
+ try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) {
+ if (cursor.getCount() == 0) {
+ return null;
+ }
+ while (cursor.moveToNext()) {
+ String id = cursor.getString(0);
+ List<AtomsProto.CarWatchdogDailyIoUsageSummary> summaries =
+ summariesById.get(id);
+ if (summaries == null) {
+ summaries = new ArrayList<>();
+ }
+ summaries.add(AtomsProto.CarWatchdogDailyIoUsageSummary.newBuilder()
+ .setWrittenBytes(WatchdogPerfHandler.constructCarWatchdogPerStateBytes(
+ /* foregroundBytes= */ cursor.getLong(2),
+ /* backgroundBytes= */ cursor.getLong(3),
+ /* garageModeBytes= */ cursor.getLong(4)))
+ .setOveruseCount(cursor.getInt(1))
+ .build());
+ summariesById.put(id, summaries);
+ }
+ }
+ return summariesById;
+ }
+
public static void truncateToDate(SQLiteDatabase db, ZonedDateTime latestTruncateDate) {
String selection = COLUMN_DATE_EPOCH + " <= ?";
String[] selectionArgs = { String.valueOf(latestTruncateDate.toEpochSecond()) };
@@ -645,21 +890,23 @@
* Defines the Watchdog database and database level operations.
*/
static final class WatchdogDbHelper extends SQLiteOpenHelper {
- public static final String DATABASE_DIR = "/data/system/car/watchdog/";
public static final String DATABASE_NAME = "car_watchdog.db";
private static final int DATABASE_VERSION = 1;
private ZonedDateTime mLatestShrinkDate;
- private TimeSourceInterface mTimeSource = SYSTEM_INSTANCE;
+ private TimeSource mTimeSource;
- WatchdogDbHelper(Context context, boolean useDataSystemCarDir) {
+ WatchdogDbHelper(Context context, boolean useDataSystemCarDir, TimeSource timeSource) {
/* Use device protected storage because CarService may need to access the database
* before the user has authenticated.
*/
- super(context.createDeviceProtectedStorageContext(),
- useDataSystemCarDir ? DATABASE_DIR + DATABASE_NAME : DATABASE_NAME,
+ super(context.createDeviceProtectedStorageContext(), useDataSystemCarDir
+ ? new File(CarWatchdogService.getWatchdogDirFile(), DATABASE_NAME)
+ .getAbsolutePath()
+ : DATABASE_NAME,
/* name= */ null, DATABASE_VERSION);
+ mTimeSource = timeSource;
}
@Override
@@ -675,8 +922,7 @@
}
public void onShrink(SQLiteDatabase db) {
- ZonedDateTime currentDate =
- mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(ChronoUnit.DAYS);
+ ZonedDateTime currentDate = mTimeSource.getCurrentDate();
if (currentDate.equals(mLatestShrinkDate)) {
return;
}
@@ -690,10 +936,6 @@
public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
/* Still on the 1st version so no upgrade required. */
}
-
- void setTimeSource(TimeSourceInterface timeSource) {
- mTimeSource = timeSource;
- }
}
private static final class UserPackage {
diff --git a/service/src/com/android/car/watchdog/proto/atoms.proto b/service/src/com/android/car/watchdog/proto/atoms.proto
index 95f6210..2f95757 100644
--- a/service/src/com/android/car/watchdog/proto/atoms.proto
+++ b/service/src/com/android/car/watchdog/proto/atoms.proto
@@ -158,3 +158,96 @@
// Resident set size in kb.
optional uint64 vm_rss_kb = 7;
}
+
+/**
+ * Logs total I/O usage summary for all applications and services running in the system.
+ *
+ * Keep in sync with proto file at
+ * packages/services/Car/service/src/com/android/car/watchdog/proto/atoms.proto
+ *
+ * Pulled from:
+ * packages/services/Car/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+ */
+message CarWatchdogSystemIoUsageSummary {
+ // I/O usage summary for the system.
+ optional CarWatchdogIoUsageSummary io_usage_summary = 1;
+}
+
+/**
+ * Logs I/O usage summary for an UID.
+ *
+ * Keep in sync with proto file at
+ * packages/services/Car/service/src/com/android/car/watchdog/proto/atoms.proto
+ *
+ * Pulled from:
+ * packages/services/Car/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+ */
+message CarWatchdogUidIoUsageSummary {
+ // UID of the application/service whose usage summary are recorded.
+ optional int32 uid = 1;
+
+ // I/O usage summary for the UID.
+ optional CarWatchdogIoUsageSummary io_usage_summary = 2;
+}
+
+/**
+ * Logs I/O usage summary for a time period.
+ *
+ * Keep in sync with proto file at
+ * packages/services/Car/service/src/com/android/car/watchdog/proto/atoms.proto
+ *
+ * Pulled from:
+ * packages/services/Car/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+ */
+message CarWatchdogIoUsageSummary {
+ // Summary event time period.
+ optional CarWatchdogEventTimePeriod event_time_period = 1;
+
+ // Daily I/O usage summary for the period. Logs summary entries only for days with I/O usage.
+ // The entries are ordered beginning from the event_time_period.start_time_millis.
+ repeated CarWatchdogDailyIoUsageSummary daily_io_usage_summary = 2;
+}
+
+/**
+ * Logs a car watchdog event's time period.
+ *
+ * Keep in sync with proto file at
+ * packages/services/Car/service/src/com/android/car/watchdog/proto/atoms.proto
+ *
+ * Pulled from:
+ * packages/services/Car/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+ */
+message CarWatchdogEventTimePeriod {
+ enum Period {
+ UNKNOWN_PERIOD = 0;
+ WEEKLY = 1;
+ BIWEEKLY = 2;
+ MONTHLY = 3;
+ }
+
+ // Start time of the event in milliseconds since epoch.
+ optional uint64 start_time_millis = 1;
+
+ // Period for the event.
+ optional Period period = 2;
+}
+
+/**
+ * Logs daily I/O usage summary.
+ *
+ * Keep in sync with proto file at
+ * packages/services/Car/service/src/com/android/car/watchdog/proto/atoms.proto
+ *
+ * Pulled from:
+ * packages/services/Car/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+ */
+message CarWatchdogDailyIoUsageSummary {
+ // Total bytes written to disk during a day.
+ optional CarWatchdogPerStateBytes written_bytes = 1;
+
+ // Total uptime for the system or service or application during a day.
+ optional uint64 uptime_millis = 2;
+
+ // Total disk I/O overuses during a day.
+ optional int32 overuse_count = 3;
+}
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 9a24a7e..d607522 100644
--- a/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
@@ -32,6 +32,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.StatsManager;
import android.automotive.watchdog.internal.ICarWatchdog;
import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
import android.car.Car;
@@ -50,6 +51,11 @@
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.car.CarLocalServices;
+import com.android.car.CarUxRestrictionsManagerService;
+import com.android.car.power.CarPowerManagementService;
+import com.android.car.systeminterface.SystemInterface;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -57,6 +63,8 @@
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
@@ -76,33 +84,48 @@
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
private final Executor mExecutor =
InstrumentationRegistry.getInstrumentation().getTargetContext().getMainExecutor();
+ private final TestTimeSource mTimeSource = new TestTimeSource();
private final UserInfo[] mUserInfos = new UserInfo[] {
new UserInfoBuilder(100).setName("user 1").build(),
new UserInfoBuilder(101).setName("user 2").build()
};
@Mock private Context mMockContext;
- @Mock private Car mCar;
- @Mock private UserManager mUserManager;
- @Mock private IBinder mDaemonBinder;
- @Mock private IBinder mServiceBinder;
- @Mock private ICarWatchdog mCarWatchdogDaemon;
+ @Mock private Car mMockCar;
+ @Mock private UserManager mMockUserManager;
+ @Mock private StatsManager mMockStatsManager;
+ @Mock private SystemInterface mMockSystemInterface;
+ @Mock private CarUxRestrictionsManagerService mMockCarUxRestrictionsManagerService;
+ @Mock private CarPowerManagementService mMockCarPowerManagementService;
+ @Mock private IBinder mMockDaemonBinder;
+ @Mock private IBinder mMockServiceBinder;
+ @Mock private ICarWatchdog mMockCarWatchdogDaemon;
@Mock private WatchdogStorage mMockWatchdogStorage;
private CarWatchdogService mCarWatchdogService;
private ICarWatchdogServiceForSystem mWatchdogServiceForSystemImpl;
@Before
- public void setUpMocks() throws Exception {
- mCarWatchdogService = new CarWatchdogService(mMockContext, mMockWatchdogStorage);
+ public void setUp() throws Exception {
+ mCarWatchdogService = new CarWatchdogService(mMockContext, mMockWatchdogStorage,
+ mTimeSource);
- mockQueryService(CAR_WATCHDOG_DAEMON_INTERFACE, mDaemonBinder, mCarWatchdogDaemon);
- when(mCar.getEventHandler()).thenReturn(mMainHandler);
- when(mServiceBinder.queryLocalInterface(anyString())).thenReturn(mCarWatchdogService);
- when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
- mockUmGetAllUsers(mUserManager, mUserInfos);
- mockUmIsUserRunning(mUserManager, 100, true);
- mockUmIsUserRunning(mUserManager, 101, false);
+ 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);
+
+ doReturn(mMockSystemInterface)
+ .when(() -> CarLocalServices.getService(SystemInterface.class));
+ doReturn(mMockCarUxRestrictionsManagerService)
+ .when(() -> CarLocalServices.getService(CarUxRestrictionsManagerService.class));
+ doReturn(mMockCarPowerManagementService)
+ .when(() -> CarLocalServices.getService(CarPowerManagementService.class));
+
+ mockUmGetAllUsers(mMockUserManager, mUserInfos);
+ mockUmIsUserRunning(mMockUserManager, 100, true);
+ mockUmIsUserRunning(mMockUserManager, 101, false);
mCarWatchdogService.init();
mWatchdogServiceForSystemImpl = registerCarWatchdogService();
@@ -111,6 +134,7 @@
@Override
protected void onSessionBuilder(CustomMockitoSessionBuilder builder) {
builder
+ .spyStatic(CarLocalServices.class)
.spyStatic(ServiceManager.class)
.spyStatic(UserHandle.class);
}
@@ -148,12 +172,14 @@
client.registerClient();
mWatchdogServiceForSystemImpl.checkIfAlive(123456, TIMEOUT_CRITICAL);
ArgumentCaptor<int[]> notRespondingClients = ArgumentCaptor.forClass(int[].class);
- verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
- eq(mWatchdogServiceForSystemImpl), notRespondingClients.capture(), eq(123456));
+ verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS))
+ .tellCarWatchdogServiceAlive(eq(mWatchdogServiceForSystemImpl),
+ notRespondingClients.capture(), eq(123456));
assertThat(notRespondingClients.getValue().length).isEqualTo(0);
mWatchdogServiceForSystemImpl.checkIfAlive(987654, TIMEOUT_CRITICAL);
- verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
- eq(mWatchdogServiceForSystemImpl), notRespondingClients.capture(), eq(987654));
+ verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS))
+ .tellCarWatchdogServiceAlive(eq(mWatchdogServiceForSystemImpl),
+ notRespondingClients.capture(), eq(987654));
assertThat(notRespondingClients.getValue().length).isEqualTo(0);
}
@@ -175,13 +201,15 @@
for (int i = 0; i < clients.size(); i++) {
assertThat(clients.get(i).mAndroidClient.makeSureHealthCheckDone()).isEqualTo(true);
}
- verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
- eq(mWatchdogServiceForSystemImpl), pidsCaptor.capture(), eq(123456));
+ verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS))
+ .tellCarWatchdogServiceAlive(eq(mWatchdogServiceForSystemImpl),
+ pidsCaptor.capture(), eq(123456));
assertThat(pidsCaptor.getValue().length).isEqualTo(0);
mWatchdogServiceForSystemImpl.checkIfAlive(987654, TIMEOUT_CRITICAL);
- verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
- eq(mWatchdogServiceForSystemImpl), pidsCaptor.capture(), eq(987654));
+ verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS))
+ .tellCarWatchdogServiceAlive(eq(mWatchdogServiceForSystemImpl),
+ pidsCaptor.capture(), eq(987654));
assertThat(pidsCaptor.getValue().length).isEqualTo(2);
}
@@ -190,8 +218,8 @@
ArgumentCaptor.forClass(ICarWatchdogServiceForSystem.class);
// Registering to daemon is done through a message handler. So, a buffer time of 1000ms is
// given.
- verify(mCarWatchdogDaemon, timeout(1000)).registerCarWatchdogService(
- watchdogServiceForSystemImplCaptor.capture());
+ verify(mMockCarWatchdogDaemon, timeout(1000))
+ .registerCarWatchdogService(watchdogServiceForSystemImplCaptor.capture());
return watchdogServiceForSystemImplCaptor.getValue();
}
@@ -202,8 +230,9 @@
client.registerClient();
mWatchdogServiceForSystemImpl.checkIfAlive(123456, TIMEOUT_CRITICAL);
ArgumentCaptor<int[]> notRespondingClients = ArgumentCaptor.forClass(int[].class);
- verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
- eq(mWatchdogServiceForSystemImpl), notRespondingClients.capture(), eq(123456));
+ verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS))
+ .tellCarWatchdogServiceAlive(eq(mWatchdogServiceForSystemImpl),
+ notRespondingClients.capture(), eq(123456));
// Checking Android client health is asynchronous, so wait at most 1 second.
int repeat = 10;
while (repeat > 0) {
@@ -218,8 +247,9 @@
assertThat(notRespondingClients.getValue().length).isEqualTo(0);
assertThat(androidClient.makeSureHealthCheckDone()).isEqualTo(true);
mWatchdogServiceForSystemImpl.checkIfAlive(987654, TIMEOUT_CRITICAL);
- verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
- eq(mWatchdogServiceForSystemImpl), notRespondingClients.capture(), eq(987654));
+ verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS))
+ .tellCarWatchdogServiceAlive(eq(mWatchdogServiceForSystemImpl),
+ notRespondingClients.capture(), eq(987654));
assertThat(notRespondingClients.getValue().length).isEqualTo(badClientCount);
}
@@ -236,7 +266,7 @@
BaseAndroidClient mAndroidClient;
TestClient(BaseAndroidClient actualClient) {
- mCarWatchdogManager = new CarWatchdogManager(mCar, mServiceBinder);
+ mCarWatchdogManager = new CarWatchdogManager(mMockCar, mMockServiceBinder);
mAndroidClient = actualClient;
actualClient.setManager(mCarWatchdogManager);
}
@@ -321,4 +351,27 @@
return false;
}
}
+
+ private static final class TestTimeSource extends TimeSource {
+ private static final Instant TEST_DATE_TIME = Instant.parse("2021-11-12T13:14:15.16Z");
+ private Instant mNow;
+ TestTimeSource() {
+ mNow = TEST_DATE_TIME;
+ }
+
+ @Override
+ public Instant now() {
+ /* Return the same time, so the tests are deterministic. */
+ return mNow;
+ }
+
+ @Override
+ public String toString() {
+ return "Mocked date to " + now();
+ }
+
+ void updateNow(int numDaysAgo) {
+ mNow = TEST_DATE_TIME.minus(numDaysAgo, ChronoUnit.DAYS);
+ }
+ }
}
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 cb3eb52..a5d6758 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
@@ -16,6 +16,8 @@
package com.android.car.watchdog;
+import static android.app.StatsManager.PULL_SKIP;
+import static android.app.StatsManager.PULL_SUCCESS;
import static android.car.drivingstate.CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAllUsers;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUserHandles;
@@ -28,7 +30,11 @@
import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__GARAGE_MODE;
import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_NO_INTERACTION_MODE;
import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__UID_STATE__UNKNOWN_UID_STATE;
-import static com.android.car.watchdog.WatchdogStorage.ZONE_OFFSET;
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_SYSTEM_IO_USAGE_SUMMARY;
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_UID_IO_USAGE_SUMMARY;
+import static com.android.car.watchdog.TimeSource.ZONE_OFFSET;
+import static com.android.car.watchdog.WatchdogPerfHandler.UID_IO_USAGE_SUMMARY_MIN_WEEKLY_WRITTEN_BYTES;
+import static com.android.car.watchdog.WatchdogStorage.RETENTION_PERIOD;
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;
@@ -39,6 +45,7 @@
import static org.junit.Assert.assertThrows;
import static org.mockito.AdditionalMatchers.or;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyList;
@@ -54,6 +61,9 @@
import static org.mockito.Mockito.when;
import android.app.ActivityThread;
+import android.app.StatsManager;
+import android.app.StatsManager.PullAtomMetadata;
+import android.app.StatsManager.StatsPullAtomCallback;
import android.automotive.watchdog.internal.ApplicationCategoryType;
import android.automotive.watchdog.internal.ComponentType;
import android.automotive.watchdog.internal.GarageMode;
@@ -97,6 +107,7 @@
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Binder;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -108,6 +119,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
+import android.util.StatsEvent;
import android.view.Display;
import com.android.car.CarLocalServices;
@@ -115,6 +127,7 @@
import com.android.car.CarStatsLog;
import com.android.car.CarUxRestrictionsManagerService;
import com.android.car.power.CarPowerManagementService;
+import com.android.car.systeminterface.SystemInterface;
import com.google.common.truth.Correspondence;
@@ -127,16 +140,21 @@
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.io.File;
+import java.nio.file.Files;
import java.time.Instant;
import java.time.ZonedDateTime;
+import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
@@ -151,18 +169,21 @@
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 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;
@Mock private Context mMockContext;
@Mock private PackageManager mMockPackageManager;
+ @Mock private StatsManager mMockStatsManager;
@Mock private UserManager mMockUserManager;
+ @Mock private SystemInterface mMockSystemInterface;
@Mock private CarPowerManagementService mMockCarPowerManagementService;
@Mock private CarUxRestrictionsManagerService mMockCarUxRestrictionsManagerService;
@Mock private IBinder mMockBinder;
@Mock private ICarWatchdog mMockCarWatchdogDaemon;
@Mock private WatchdogStorage mMockWatchdogStorage;
-
@Captor private ArgumentCaptor<ICarPowerStateListener> mICarPowerStateListenerCaptor;
@Captor private ArgumentCaptor<ICarPowerPolicyListener> mICarPowerPolicyListenerCaptor;
@Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
@@ -174,6 +195,7 @@
@Captor private ArgumentCaptor<List<
android.automotive.watchdog.internal.ResourceOveruseConfiguration>>
mResourceOveruseConfigurationsCaptor;
+ @Captor private ArgumentCaptor<StatsPullAtomCallback> mStatsPullAtomCallbackCaptor;
@Captor private ArgumentCaptor<int[]> mIntArrayCaptor;
@Captor private ArgumentCaptor<byte[]> mOveruseStatsCaptor;
@Captor private ArgumentCaptor<byte[]> mKilledStatsCaptor;
@@ -183,7 +205,6 @@
@Captor private ArgumentCaptor<Integer> mSystemStateCaptor;
@Captor private ArgumentCaptor<Integer> mKillReasonCaptor;
-
private CarWatchdogService mCarWatchdogService;
private ICarWatchdogServiceForSystem mWatchdogServiceForSystemImpl;
private IBinder.DeathRecipient mCarWatchdogDaemonBinderDeathRecipient;
@@ -192,8 +213,10 @@
private ICarPowerStateListener mCarPowerStateListener;
private ICarPowerPolicyListener mCarPowerPolicyListener;
private ICarUxRestrictionsChangeListener mCarUxRestrictionsChangeListener;
- private TimeSourceInterface mTimeSource;
+ private StatsPullAtomCallback mStatsPullAtomCallback;
+ private File mTempSystemCarDir;
+ private final TestTimeSource mTimeSource = new TestTimeSource();
private final SparseArray<String> mGenericPackageNameByUid = new SparseArray<>();
private final SparseArray<List<String>> mPackagesBySharedUid = new SparseArray<>();
private final ArrayMap<String, android.content.pm.PackageInfo> mPmPackageInfoByUserPackage =
@@ -202,6 +225,10 @@
private final List<WatchdogStorage.UserPackageSettingsEntry> mUserPackageSettingsEntries =
new ArrayList<>();
private final List<WatchdogStorage.IoUsageStatsEntry> mIoUsageStatsEntries = new ArrayList<>();
+ private final List<AtomsProto.CarWatchdogSystemIoUsageSummary> mPulledSystemIoUsageSummaries =
+ new ArrayList<>();
+ private final List<AtomsProto.CarWatchdogUidIoUsageSummary> mPulledUidIoUsageSummaries =
+ new ArrayList<>();
private final IPackageManager mSpiedPackageManager = spy(ActivityThread.getPackageManager());
@Override
@@ -220,28 +247,33 @@
@Before
public void setUp() throws Exception {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockContext.getSystemService(StatsManager.class)).thenReturn(mMockStatsManager);
when(mMockContext.getPackageName()).thenReturn(
CarWatchdogServiceUnitTest.class.getCanonicalName());
+ doReturn(mMockSystemInterface)
+ .when(() -> CarLocalServices.getService(SystemInterface.class));
doReturn(mMockCarPowerManagementService)
.when(() -> CarLocalServices.getService(CarPowerManagementService.class));
doReturn(mMockCarUxRestrictionsManagerService)
.when(() -> CarLocalServices.getService(CarUxRestrictionsManagerService.class));
doReturn(mSpiedPackageManager).when(() -> ActivityThread.getPackageManager());
- mCarWatchdogService = new CarWatchdogService(mMockContext, mMockWatchdogStorage);
- mCarWatchdogService.setOveruseHandlingDelay(OVERUSE_HANDLING_DELAY_MILLS);
- mCarWatchdogService.setRecurringOveruseThreshold(RECURRING_OVERUSE_THRESHOLD);
- setDate(/* numDaysAgo= */ 0);
+ when(mMockCarUxRestrictionsManagerService.getCurrentUxRestrictions())
+ .thenReturn(new CarUxRestrictions.Builder(/* reqOpt= */ false,
+ UX_RESTRICTIONS_BASELINE, /* time= */ 0).build());
+
+ mTempSystemCarDir = Files.createTempDirectory("watchdog_test").toFile();
+ when(mMockSystemInterface.getSystemCarDir()).thenReturn(mTempSystemCarDir);
+
+ setupUsers();
mockWatchdogDaemon();
mockWatchdogStorage();
- setupUsers();
- mCarWatchdogService.init();
- captureCarPowerListeners();
- captureBroadcastReceiver();
- captureCarUxRestrictionsChangeListener();
- captureAndVerifyRegistrationWithDaemon(/* waitOnMain= */ true);
- verifyDatabaseInit(/* wantedInvocations= */ 1);
mockPackageManager();
+ mockBuildStatsEventCalls();
+
+ mCarWatchdogService = new CarWatchdogService(mMockContext, mMockWatchdogStorage,
+ mTimeSource);
+ initService(/* wantedInvocations= */ 1);
}
/**
@@ -263,6 +295,11 @@
mPackagesBySharedUid.clear();
mPmPackageInfoByUserPackage.clear();
mDisabledUserPackages.clear();
+ mPulledSystemIoUsageSummaries.clear();
+ mPulledUidIoUsageSummaries.clear();
+ if (mTempSystemCarDir != null) {
+ FileUtils.deleteContentsAndDir(mTempSystemCarDir);
+ }
}
@Test
@@ -396,7 +433,7 @@
injectPackageInfos(Collections.singletonList(constructPackageManagerPackageInfo(
packageName, uid, null, ApplicationInfo.FLAG_SYSTEM, 0)));
- long startTime = mTimeSource.now().atZone(ZONE_OFFSET).minusDays(4).toEpochSecond();
+ long startTime = mTimeSource.getCurrentDateTime().minusDays(4).toEpochSecond();
long duration = mTimeSource.now().getEpochSecond() - startTime;
when(mMockWatchdogStorage.getHistoricalIoOveruseStats(
UserHandle.getUserId(uid), packageName, 6))
@@ -461,7 +498,7 @@
injectPackageInfos(Collections.singletonList(constructPackageManagerPackageInfo(
packageName, uid, null, ApplicationInfo.FLAG_SYSTEM, 0)));
- long startTime = mTimeSource.now().atZone(ZONE_OFFSET).minusDays(4).toEpochSecond();
+ long startTime = mTimeSource.getCurrentDateTime().minusDays(4).toEpochSecond();
long duration = mTimeSource.now().getEpochSecond() - startTime;
when(mMockWatchdogStorage.getHistoricalIoOveruseStats(
UserHandle.getUserId(uid), packageName, 6))
@@ -576,7 +613,7 @@
/* totalOveruses= */ 0)));
pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
- ZonedDateTime now = mTimeSource.now().atZone(ZONE_OFFSET);
+ ZonedDateTime now = mTimeSource.getCurrentDateTime();
long startTime = now.minusDays(4).toEpochSecond();
IoOveruseStats thirdPartyPkgOldStats = new IoOveruseStats.Builder(
startTime, now.toEpochSecond() - startTime).setTotalOveruses(5)
@@ -761,7 +798,7 @@
/* totalOveruses= */ 0)));
pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
- ZonedDateTime now = mTimeSource.now().atZone(ZONE_OFFSET);
+ ZonedDateTime now = mTimeSource.getCurrentDateTime();
long startTime = now.minusDays(4).toEpochSecond();
IoOveruseStats thirdPartyPkgOldStats = new IoOveruseStats.Builder(
startTime, now.toEpochSecond() - startTime).setTotalOveruses(5)
@@ -855,7 +892,7 @@
/* totalOveruses= */ 3)));
pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
- ZonedDateTime now = mTimeSource.now().atZone(ZONE_OFFSET);
+ ZonedDateTime now = mTimeSource.getCurrentDateTime();
long startTime = now.minusDays(4).toEpochSecond();
IoOveruseStats vendorPkgOldStats = new IoOveruseStats.Builder(
startTime, now.toEpochSecond() - startTime).setTotalOveruses(2)
@@ -2030,14 +2067,7 @@
mCarWatchdogService.setKillablePackageAsUser(
"third_party_package.A", new UserHandle(12), /* isKillable= */ false);
- setCarPowerState(CarPowerStateListener.SHUTDOWN_ENTER);
- verify(mMockWatchdogStorage).saveIoUsageStats(any());
- verify(mMockWatchdogStorage).saveUserPackageSettings(any());
- mCarWatchdogService.release();
- verify(mMockWatchdogStorage).release();
- mCarWatchdogService = new CarWatchdogService(mMockContext, mMockWatchdogStorage);
- mCarWatchdogService.init();
- verifyDatabaseInit(/* wantedInvocations= */ 2);
+ restartService(/* totalRestarts= */ 1);
List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */ 0,
@@ -2078,7 +2108,7 @@
constructPackageManagerPackageInfo("third_party_package", 1001100, null)));
setDisplayStateEnabled(false);
- setDate(1);
+ mTimeSource.updateNow(/* numDaysAgo= */ 1);
List<PackageIoOveruseStats> prevDayStats = Arrays.asList(
constructPackageIoOveruseStats(1011200, /* shouldNotify= */ false,
/* forgivenWriteBytes= */ constructPerStateBytes(600, 700, 800),
@@ -2105,7 +2135,7 @@
/* totalTimesKilled= */ 0)));
setDisplayStateEnabled(true);
- setDate(0);
+ mTimeSource.updateNow(/* numDaysAgo= */ 0);
List<PackageIoOveruseStats> currentDayStats = Arrays.asList(
constructPackageIoOveruseStats(1011200, /* shouldNotify= */ false,
/* forgivenWriteBytes= */ constructPerStateBytes(0, 0, 0),
@@ -2337,7 +2367,7 @@
@Test
public void testDisableRecurrentlyOverusingAppWhenDisplayDisabledAfterDateChange()
throws Exception {
- setDate(1);
+ mTimeSource.updateNow(/* numDaysAgo= */ 1);
setUpSampleUserAndPackages();
setRequiresDistractionOptimization(true);
setDisplayStateEnabled(true);
@@ -2349,7 +2379,7 @@
assertWithMessage("Disabled user packages").that(mDisabledUserPackages).isEmpty();
- setDate(0);
+ mTimeSource.updateNow(/* numDaysAgo= */ 0);
pushLatestIoOveruseStatsAndWait(new ArrayList<>());
@@ -2606,6 +2636,190 @@
}
@Test
+ public void testPullSystemIoUsageSummaryAtomsWithRestart() throws Exception {
+ List<StatsEvent> events = new ArrayList<>();
+ assertWithMessage("Stats pull atom callback status")
+ .that(mStatsPullAtomCallback.onPullAtom(CAR_WATCHDOG_SYSTEM_IO_USAGE_SUMMARY,
+ events)).isEqualTo(PULL_SUCCESS);
+
+ List<AtomsProto.CarWatchdogSystemIoUsageSummary> expectedSummaries =
+ verifyAndGetSystemIoUsageSummaries(
+ mTimeSource.getCurrentDate().minus(RETENTION_PERIOD));
+
+ assertWithMessage("First pulled system I/O usage summary atoms")
+ .that(mPulledSystemIoUsageSummaries).containsExactlyElementsIn(expectedSummaries);
+ mPulledSystemIoUsageSummaries.clear();
+
+ restartService(/* totalRestarts= */ 1);
+
+ assertWithMessage("Status of stats pull atom callback after restart")
+ .that(mStatsPullAtomCallback.onPullAtom(CAR_WATCHDOG_SYSTEM_IO_USAGE_SUMMARY,
+ events)).isEqualTo(PULL_SUCCESS);
+
+ assertWithMessage("Pulled system I/O usage summary atoms after restart")
+ .that(mPulledSystemIoUsageSummaries).isEmpty();
+
+ verifyNoMoreInteractions(mMockWatchdogStorage);
+ }
+
+ @Test
+ public void testPullSystemIoUsageSummaryAtomsWithDateChange() throws Exception {
+ mTimeSource.updateNow(/* numDaysAgo= */ 7);
+
+ List<StatsEvent> events = new ArrayList<>();
+ assertWithMessage("Stats pull atom callback status")
+ .that(mStatsPullAtomCallback.onPullAtom(CAR_WATCHDOG_SYSTEM_IO_USAGE_SUMMARY,
+ events)).isEqualTo(PULL_SUCCESS);
+
+ List<AtomsProto.CarWatchdogSystemIoUsageSummary> expectedSummaries =
+ verifyAndGetSystemIoUsageSummaries(
+ mTimeSource.getCurrentDate().minus(RETENTION_PERIOD));
+
+ assertWithMessage("First pulled system I/O usage summary atoms")
+ .that(mPulledSystemIoUsageSummaries).containsExactlyElementsIn(expectedSummaries);
+ mPulledSystemIoUsageSummaries.clear();
+
+ mTimeSource.updateNow(/* numDaysAgo= */ 6);
+
+ assertWithMessage("Status of stats pull atom callback within the same week")
+ .that(mStatsPullAtomCallback.onPullAtom(CAR_WATCHDOG_SYSTEM_IO_USAGE_SUMMARY,
+ events)).isEqualTo(PULL_SUCCESS);
+
+ assertWithMessage("Pulled system I/O usage summary atoms within the same week")
+ .that(mPulledSystemIoUsageSummaries).isEmpty();
+
+ mTimeSource.updateNow(/* numDaysAgo= */ 0);
+
+ assertWithMessage("Status of stats pull atom callback after a week")
+ .that(mStatsPullAtomCallback.onPullAtom(CAR_WATCHDOG_SYSTEM_IO_USAGE_SUMMARY,
+ events)).isEqualTo(PULL_SUCCESS);
+
+ expectedSummaries = verifyAndGetSystemIoUsageSummaries(
+ mTimeSource.getCurrentDate().minus(1, ChronoUnit.WEEKS));
+
+ assertWithMessage("Pulled system I/O usage summary atoms after a week")
+ .that(mPulledSystemIoUsageSummaries).containsExactlyElementsIn(expectedSummaries);
+
+ verifyNoMoreInteractions(mMockWatchdogStorage);
+ }
+
+ @Test
+ public void testPullUidIoUsageSummaryAtomsForTopUids() throws Exception {
+ injectPackageInfos(Arrays.asList(
+ constructPackageManagerPackageInfo("system_package.critical.A", 10000345, null),
+ constructPackageManagerPackageInfo("third_party_package.B", 10004675, null),
+ constructPackageManagerPackageInfo("system_package.critical.B", 10010001, null),
+ constructPackageManagerPackageInfo("vendor_package.non_critical", 10110004, null),
+ constructPackageManagerPackageInfo("third_party_package.A", 10110005,
+ "third_party_shared_package")));
+
+ List<StatsEvent> events = new ArrayList<>();
+ assertWithMessage("Stats pull atom callback status")
+ .that(mStatsPullAtomCallback.onPullAtom(CAR_WATCHDOG_UID_IO_USAGE_SUMMARY,
+ events)).isEqualTo(PULL_SUCCESS);
+
+ List<AtomsProto.CarWatchdogUidIoUsageSummary> expectedSummaries =
+ verifyAndGetUidIoUsageSummaries(
+ mTimeSource.getCurrentDate().minus(RETENTION_PERIOD),
+ /* expectUids= */ Arrays.asList(10010001, 10110004, 10110005));
+
+ assertWithMessage(String.format("Pulled uid I/O usage summary atoms for top %d UIDs",
+ UID_IO_USAGE_SUMMARY_TOP_COUNT)).that(mPulledUidIoUsageSummaries)
+ .containsExactlyElementsIn(expectedSummaries);
+ }
+
+ @Test
+ public void testPullUidIoUsageSummaryAtomsWithRestart() throws Exception {
+ injectPackageInfos(Arrays.asList(
+ constructPackageManagerPackageInfo("system_package.critical", 10010001, null),
+ constructPackageManagerPackageInfo("vendor_package.non_critical", 10110004, null),
+ constructPackageManagerPackageInfo("third_party_package.A", 10110005,
+ "third_party_shared_package")));
+
+ List<StatsEvent> events = new ArrayList<>();
+ assertWithMessage("Stats pull atom callback status")
+ .that(mStatsPullAtomCallback.onPullAtom(CAR_WATCHDOG_UID_IO_USAGE_SUMMARY,
+ events)).isEqualTo(PULL_SUCCESS);
+
+ List<AtomsProto.CarWatchdogUidIoUsageSummary> expectedSummaries =
+ verifyAndGetUidIoUsageSummaries(
+ mTimeSource.getCurrentDate().minus(RETENTION_PERIOD),
+ /* expectUids= */ Arrays.asList(10010001, 10110004, 10110005));
+
+ assertWithMessage("First pulled uid I/O usage summary atoms")
+ .that(mPulledUidIoUsageSummaries).containsExactlyElementsIn(expectedSummaries);
+ mPulledUidIoUsageSummaries.clear();
+
+ restartService(/* totalRestarts= */ 1);
+
+ assertWithMessage("Status of stats pull atom callback after restart")
+ .that(mStatsPullAtomCallback.onPullAtom(CAR_WATCHDOG_UID_IO_USAGE_SUMMARY,
+ events)).isEqualTo(PULL_SUCCESS);
+
+ assertWithMessage("Pulled uid I/O usage summary atoms after restart")
+ .that(mPulledUidIoUsageSummaries).isEmpty();
+
+ verifyNoMoreInteractions(mMockWatchdogStorage);
+ }
+
+ @Test
+ public void testPullUidIoUsageSummaryAtomsWithDateChange() throws Exception {
+ injectPackageInfos(Arrays.asList(
+ constructPackageManagerPackageInfo("system_package.critical", 10010001, null),
+ constructPackageManagerPackageInfo("vendor_package.non_critical", 10110004, null),
+ constructPackageManagerPackageInfo("third_party_package.A", 10110005,
+ "third_party_shared_package")));
+
+ mTimeSource.updateNow(/* numDaysAgo= */ 7);
+
+ List<StatsEvent> events = new ArrayList<>();
+ assertWithMessage("Stats pull atom callback status")
+ .that(mStatsPullAtomCallback.onPullAtom(CAR_WATCHDOG_UID_IO_USAGE_SUMMARY,
+ events)).isEqualTo(PULL_SUCCESS);
+
+ List<AtomsProto.CarWatchdogUidIoUsageSummary> expectedSummaries =
+ verifyAndGetUidIoUsageSummaries(
+ mTimeSource.getCurrentDate().minus(RETENTION_PERIOD),
+ /* expectUids= */ Arrays.asList(10010001, 10110004, 10110005));
+
+ assertWithMessage("First pulled uid I/O usage summary atoms")
+ .that(mPulledUidIoUsageSummaries).containsExactlyElementsIn(expectedSummaries);
+ mPulledUidIoUsageSummaries.clear();
+
+ mTimeSource.updateNow(/* numDaysAgo= */ 6);
+
+ assertWithMessage("Status of stats pull atom callback within the same week")
+ .that(mStatsPullAtomCallback.onPullAtom(CAR_WATCHDOG_UID_IO_USAGE_SUMMARY,
+ events)).isEqualTo(PULL_SUCCESS);
+
+ assertWithMessage("Pulled uid I/O usage summary atoms within the same week")
+ .that(mPulledUidIoUsageSummaries).isEmpty();
+
+ mTimeSource.updateNow(/* numDaysAgo= */ 0);
+
+ assertWithMessage("Status of stats pull atom callback after a week")
+ .that(mStatsPullAtomCallback.onPullAtom(CAR_WATCHDOG_UID_IO_USAGE_SUMMARY,
+ events)).isEqualTo(PULL_SUCCESS);
+
+ expectedSummaries = verifyAndGetUidIoUsageSummaries(
+ mTimeSource.getCurrentDate().minus(1, ChronoUnit.WEEKS),
+ /* expectUids= */ Arrays.asList(10010001, 10110004, 10110005));
+
+ assertWithMessage("Pulled uid I/O usage summary atoms after a week")
+ .that(mPulledUidIoUsageSummaries).containsExactlyElementsIn(expectedSummaries);
+
+ verifyNoMoreInteractions(mMockWatchdogStorage);
+ }
+
+ @Test
+ public void testPullInvalidAtoms() throws Exception {
+ List<StatsEvent> actualEvents = new ArrayList<>();
+ assertWithMessage("Stats pull atom callback status").that(mStatsPullAtomCallback.onPullAtom(
+ 0, actualEvents)).isEqualTo(PULL_SKIP);
+ assertWithMessage("Pulled stats events").that(actualEvents).isEmpty();
+ }
+
+ @Test
public void testGetPackageInfosForUids() throws Exception {
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo(
@@ -2939,6 +3153,24 @@
});
when(mMockWatchdogStorage.getUserPackageSettings()).thenReturn(mUserPackageSettingsEntries);
when(mMockWatchdogStorage.getTodayIoUsageStats()).thenReturn(mIoUsageStatsEntries);
+ when(mMockWatchdogStorage.getDailySystemIoUsageSummaries(anyLong(), anyLong())).thenAnswer(
+ args -> sampleDailyIoUsageSummariesForAWeek(args.getArgument(0),
+ 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;
+ });
}
private void setupUsers() {
@@ -2946,29 +3178,59 @@
mockUmGetAllUsers(mMockUserManager, new UserInfo[0]);
}
- private void captureCarPowerListeners() {
- verify(mMockCarPowerManagementService).registerListener(
+ 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();
+ captureCarPowerListeners(wantedInvocations);
+ captureBroadcastReceiver(wantedInvocations);
+ captureCarUxRestrictionsChangeListener(wantedInvocations);
+ captureAndVerifyRegistrationWithDaemon(/* waitOnMain= */ true);
+ verifyDatabaseInit(wantedInvocations);
+ captureStatsPullAtomCallback(wantedInvocations);
+ }
+
+ private void restartService(int totalRestarts) throws Exception {
+ setCarPowerState(CarPowerStateListener.SHUTDOWN_PREPARE);
+ setCarPowerState(CarPowerStateListener.SHUTDOWN_ENTER);
+ mCarWatchdogService.release();
+ verify(mMockWatchdogStorage, times(totalRestarts)).saveIoUsageStats(any());
+ verify(mMockWatchdogStorage, times(totalRestarts)).saveUserPackageSettings(any());
+ verify(mMockWatchdogStorage, times(totalRestarts)).release();
+ mCarWatchdogService = new CarWatchdogService(mMockContext, mMockWatchdogStorage,
+ mTimeSource);
+ initService(/* wantedInvocations= */ totalRestarts + 1);
+ }
+
+ private void captureCarPowerListeners(int wantedInvocations) {
+ verify(mMockCarPowerManagementService, times(wantedInvocations)).registerListener(
mICarPowerStateListenerCaptor.capture());
mCarPowerStateListener = mICarPowerStateListenerCaptor.getValue();
assertWithMessage("Car power state listener").that(mCarPowerStateListener).isNotNull();
- verify(mMockCarPowerManagementService).addPowerPolicyListener(
+ verify(mMockCarPowerManagementService, times(wantedInvocations)).addPowerPolicyListener(
any(), mICarPowerPolicyListenerCaptor.capture());
mCarPowerPolicyListener = mICarPowerPolicyListenerCaptor.getValue();
assertWithMessage("Car power policy listener").that(mCarPowerPolicyListener).isNotNull();
}
- private void captureBroadcastReceiver() {
- verify(mMockContext)
+ private void captureBroadcastReceiver(int wantedInvocations) {
+ verify(mMockContext, times(wantedInvocations))
.registerReceiverForAllUsers(mBroadcastReceiverCaptor.capture(), any(), any(),
any());
mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
assertWithMessage("Broadcast receiver").that(mBroadcastReceiver).isNotNull();
}
- private void captureCarUxRestrictionsChangeListener() {
- verify(mMockCarUxRestrictionsManagerService).registerUxRestrictionsChangeListener(
- mICarUxRestrictionsChangeListener.capture(), eq(Display.DEFAULT_DISPLAY));
+ private void captureCarUxRestrictionsChangeListener(int wantedInvocations) {
+ verify(mMockCarUxRestrictionsManagerService, times(wantedInvocations))
+ .getCurrentUxRestrictions();
+ verify(mMockCarUxRestrictionsManagerService, times(wantedInvocations))
+ .registerUxRestrictionsChangeListener(mICarUxRestrictionsChangeListener.capture(),
+ eq(Display.DEFAULT_DISPLAY));
mCarUxRestrictionsChangeListener = mICarUxRestrictionsChangeListener.getValue();
assertWithMessage("UX restrictions change listener").that(mCarUxRestrictionsChangeListener)
.isNotNull();
@@ -3016,6 +3278,44 @@
verify(mMockWatchdogStorage, times(wantedInvocations)).getTodayIoUsageStats();
}
+ private void captureStatsPullAtomCallback(int wantedInvocations) {
+ verify(mMockStatsManager, times(wantedInvocations)).setPullAtomCallback(
+ eq(CAR_WATCHDOG_SYSTEM_IO_USAGE_SUMMARY), any(PullAtomMetadata.class),
+ any(Executor.class), mStatsPullAtomCallbackCaptor.capture());
+ verify(mMockStatsManager, times(wantedInvocations)).setPullAtomCallback(
+ eq(CAR_WATCHDOG_UID_IO_USAGE_SUMMARY), any(PullAtomMetadata.class),
+ any(Executor.class), mStatsPullAtomCallbackCaptor.capture());
+
+ // The same callback is set in the above calls, so fetch the latest captured callback.
+ mStatsPullAtomCallback = mStatsPullAtomCallbackCaptor.getValue();
+ assertWithMessage("Stats pull atom callback").that(mStatsPullAtomCallback).isNotNull();
+ }
+
+ private void mockBuildStatsEventCalls() {
+ when(CarStatsLog.buildStatsEvent(eq(CAR_WATCHDOG_SYSTEM_IO_USAGE_SUMMARY),
+ any(byte[].class))).thenAnswer(args -> {
+ mPulledSystemIoUsageSummaries.add(AtomsProto.CarWatchdogSystemIoUsageSummary
+ .newBuilder()
+ .setIoUsageSummary(AtomsProto.CarWatchdogIoUsageSummary.parseFrom(
+ (byte[]) args.getArgument(1)))
+ .build());
+ // Returned event is not used in tests, so return an empty event.
+ return StatsEvent.newBuilder().build();
+ });
+
+ when(CarStatsLog.buildStatsEvent(eq(CAR_WATCHDOG_UID_IO_USAGE_SUMMARY), anyInt(),
+ any(byte[].class))).thenAnswer(args -> {
+ mPulledUidIoUsageSummaries.add(AtomsProto.CarWatchdogUidIoUsageSummary
+ .newBuilder()
+ .setUid(args.getArgument(1))
+ .setIoUsageSummary(AtomsProto.CarWatchdogIoUsageSummary.parseFrom(
+ (byte[]) args.getArgument(2)))
+ .build());
+ // Returned event is not used in tests, so return an empty event.
+ return StatsEvent.newBuilder().build();
+ });
+ }
+
private void mockPackageManager() throws Exception {
when(mMockPackageManager.getNamesForUids(any())).thenAnswer(args -> {
int[] uids = args.getArgument(0);
@@ -3042,10 +3342,9 @@
}
return packageInfo.applicationInfo;
});
- when(mMockPackageManager.getPackageInfoAsUser(anyString(), anyInt(), anyInt())).thenAnswer(
- args -> {
- int userId = args.getArgument(2);
- String userPackageId = userId + ":" + args.getArgument(0);
+ when(mMockPackageManager.getPackageInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenAnswer(args -> {
+ String userPackageId = args.getArgument(2) + ":" + args.getArgument(0);
android.content.pm.PackageInfo packageInfo =
mPmPackageInfoByUserPackage.get(userPackageId);
if (packageInfo == null) {
@@ -3054,8 +3353,8 @@
}
return packageInfo;
});
- when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt())).thenAnswer(
- args -> {
+ when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
+ .thenAnswer(args -> {
int userId = args.getArgument(1);
List<android.content.pm.PackageInfo> packageInfos = new ArrayList<>();
for (android.content.pm.PackageInfo packageInfo :
@@ -3066,6 +3365,18 @@
}
return packageInfos;
});
+ when(mMockPackageManager.getPackageUidAsUser(anyString(), anyInt()))
+ .thenAnswer(args -> {
+ String userPackageId = args.getArgument(1) + ":" + args.getArgument(0);
+ android.content.pm.PackageInfo packageInfo =
+ mPmPackageInfoByUserPackage.get(userPackageId);
+ if (packageInfo == null) {
+ throw new PackageManager.NameNotFoundException(
+ "User package id '" + userPackageId + "' not found");
+ }
+ return packageInfo.applicationInfo.uid;
+ });
+
doAnswer((args) -> {
String value = args.getArgument(3) + ":" + args.getArgument(0);
mDisabledUserPackages.add(value);
@@ -3118,26 +3429,6 @@
captureAndVerifyRegistrationWithDaemon(/* waitOnMain= */ false);
}
- private void setDate(int numDaysAgo) {
- TimeSourceInterface timeSource = new TimeSourceInterface() {
- @Override
- public Instant now() {
- /* Return the same time, so the tests are deterministic. */
- return mNow;
- }
-
- @Override
- public String toString() {
- return "Mocked date to " + now();
- }
-
- private final Instant mNow = Instant.now().minus(numDaysAgo, ChronoUnit.DAYS);
- };
- mCarWatchdogService.setTimeSource(timeSource);
- mTimeSource = timeSource;
- }
-
-
private void testClientHealthCheck(TestClient client, int badClientCount) throws Exception {
mCarWatchdogService.registerClient(client, TIMEOUT_CRITICAL);
mWatchdogServiceForSystemImpl.checkIfAlive(123456, TIMEOUT_CRITICAL);
@@ -3180,8 +3471,7 @@
return packageIoOveruseStatsByUid;
}
- private void injectPackageInfos(
- List<android.content.pm.PackageInfo> packageInfos) {
+ private void injectPackageInfos(List<android.content.pm.PackageInfo> packageInfos) {
for (android.content.pm.PackageInfo packageInfo : packageInfos) {
String genericPackageName = packageInfo.packageName;
int uid = packageInfo.applicationInfo.uid;
@@ -3208,8 +3498,8 @@
}
}
- private void pushLatestIoOveruseStatsAndWait(
- List<PackageIoOveruseStats> packageIoOveruseStats) throws Exception {
+ private void pushLatestIoOveruseStatsAndWait(List<PackageIoOveruseStats> packageIoOveruseStats)
+ throws Exception {
mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
// Resource overuse handling is done on the main thread by posting a new message with
@@ -3661,9 +3951,7 @@
AtomsProto.CarWatchdogIoOveruseStats.parseFrom(allOveruseStatsValues.get(i))));
}
assertWithMessage("I/O overuse stats reported to statsd").that(actual)
- .comparingElementsUsing(Correspondence.from(
- CarWatchdogServiceUnitTest::isCarWatchdogIoOveruseStatsReportedEquals,
- "is equal to")).containsExactlyElementsIn(expected);
+ .containsExactlyElementsIn(expected);
}
private void captureAndVerifyKillStatsReported(
@@ -3691,55 +3979,87 @@
allIoOveruseStatsValues.get(i))));
}
assertWithMessage("I/O overuse kill stats reported to statsd").that(actual)
- .comparingElementsUsing(Correspondence.from(
- CarWatchdogServiceUnitTest::isCarWatchdogKillStatsReported, "is equal to"))
.containsExactlyElementsIn(expected);
}
- public static boolean isCarWatchdogIoOveruseStatsReportedEquals(
- AtomsProto.CarWatchdogIoOveruseStatsReported actual,
- AtomsProto.CarWatchdogIoOveruseStatsReported expected) {
- return actual.getUid() == expected.getUid()
- && isCarWatchdogIoOveruseStatsEquals(
- actual.getIoOveruseStats(), expected.getIoOveruseStats());
- }
-
- public static boolean isCarWatchdogKillStatsReported(
- AtomsProto.CarWatchdogKillStatsReported actual,
- AtomsProto.CarWatchdogKillStatsReported expected) {
- return actual.getUid() == expected.getUid()
- && actual.getUidState() == expected.getUidState()
- && actual.getSystemState() == expected.getSystemState()
- && actual.getKillReason() == expected.getKillReason()
- // Process stats is null on I/O overuse kill stats, so equality checking on the
- // objects is sufficient.
- && actual.getProcessStats().equals(expected.getProcessStats())
- && isCarWatchdogIoOveruseStatsEquals(
- actual.getIoOveruseStats(), expected.getIoOveruseStats());
- }
-
- private static boolean isCarWatchdogIoOveruseStatsEquals(
- AtomsProto.CarWatchdogIoOveruseStats actual,
- AtomsProto.CarWatchdogIoOveruseStats expected) {
- if (actual == null || expected == null) {
- return (actual == null) && (expected == null);
+ private List<AtomsProto.CarWatchdogSystemIoUsageSummary> verifyAndGetSystemIoUsageSummaries(
+ ZonedDateTime beginReportDate) {
+ ZonedDateTime beginWeekStartDate = beginReportDate.with(ChronoField.DAY_OF_WEEK, 1);
+ ZonedDateTime endWeekStartDate = mTimeSource.getCurrentDate()
+ .with(ChronoField.DAY_OF_WEEK, 1);
+ List<AtomsProto.CarWatchdogSystemIoUsageSummary> expectedSummaries = new ArrayList<>();
+ while (!beginWeekStartDate.equals(endWeekStartDate)) {
+ long startEpochSecond = beginWeekStartDate.toEpochSecond();
+ verify(mMockWatchdogStorage).getDailySystemIoUsageSummaries(startEpochSecond,
+ beginWeekStartDate.plusWeeks(1).toEpochSecond());
+ expectedSummaries.add(AtomsProto.CarWatchdogSystemIoUsageSummary.newBuilder()
+ .setIoUsageSummary(constructCarWatchdogIoUsageSummary(startEpochSecond,
+ sampleDailyIoUsageSummariesForAWeek(startEpochSecond,
+ SYSTEM_DAILY_IO_USAGE_SUMMARY_MULTIPLIER)))
+ .build());
+ beginWeekStartDate = beginWeekStartDate.plusWeeks(1);
}
- return actual.getPeriod() == expected.getPeriod()
- && isCarWatchdogPerStateBytesEquals(actual.getThreshold(), expected.getThreshold())
- && isCarWatchdogPerStateBytesEquals(
- actual.getWrittenBytes(), expected.getWrittenBytes())
- && actual.getUptimeMillis() == expected.getUptimeMillis();
+ return expectedSummaries;
}
- private static boolean isCarWatchdogPerStateBytesEquals(
- AtomsProto.CarWatchdogPerStateBytes actual,
- AtomsProto.CarWatchdogPerStateBytes expected) {
- if (actual == null || expected == null) {
- return (actual == null) && (expected == null);
+ private List<AtomsProto.CarWatchdogUidIoUsageSummary> verifyAndGetUidIoUsageSummaries(
+ ZonedDateTime beginReportDate, List<Integer> expectUids) {
+ ZonedDateTime beginWeekStartDate = beginReportDate.with(ChronoField.DAY_OF_WEEK, 1);
+ ZonedDateTime endWeekStartDate = mTimeSource.getCurrentDate()
+ .with(ChronoField.DAY_OF_WEEK, 1);
+ List<AtomsProto.CarWatchdogUidIoUsageSummary> expectedSummaries = new ArrayList<>();
+ while (!beginWeekStartDate.equals(endWeekStartDate)) {
+ long startEpochSecond = beginWeekStartDate.toEpochSecond();
+ verify(mMockWatchdogStorage).getTopUsersDailyIoUsageSummaries(
+ UID_IO_USAGE_SUMMARY_TOP_COUNT * 2,
+ UID_IO_USAGE_SUMMARY_MIN_WEEKLY_WRITTEN_BYTES, startEpochSecond,
+ beginWeekStartDate.plusWeeks(1).toEpochSecond());
+ for (Integer uid : expectUids) {
+ expectedSummaries.add(AtomsProto.CarWatchdogUidIoUsageSummary.newBuilder()
+ .setUid(uid)
+ .setIoUsageSummary(constructCarWatchdogIoUsageSummary(startEpochSecond,
+ sampleDailyIoUsageSummariesForAWeek(startEpochSecond,
+ uid)))
+ .build());
+ }
+ beginWeekStartDate = beginWeekStartDate.plusWeeks(1);
}
- return actual.getForegroundBytes() == expected.getForegroundBytes()
- && actual.getBackgroundBytes() == expected.getBackgroundBytes()
- && actual.getGarageModeBytes() == expected.getGarageModeBytes();
+ return expectedSummaries;
+ }
+
+ private static AtomsProto.CarWatchdogIoUsageSummary constructCarWatchdogIoUsageSummary(
+ long startTimeMillis, List<AtomsProto.CarWatchdogDailyIoUsageSummary> dailySummaries) {
+ return AtomsProto.CarWatchdogIoUsageSummary.newBuilder()
+ .setEventTimePeriod(AtomsProto.CarWatchdogEventTimePeriod.newBuilder()
+ .setPeriod(AtomsProto.CarWatchdogEventTimePeriod.Period.WEEKLY)
+ .setStartTimeMillis(startTimeMillis).build())
+ .addAllDailyIoUsageSummary(dailySummaries)
+ .build();
+ }
+
+ private List<AtomsProto.CarWatchdogDailyIoUsageSummary> sampleDailyIoUsageSummariesForAWeek(
+ long startEpochSeconds, long sysOrUidMultiplier) {
+ List<AtomsProto.CarWatchdogDailyIoUsageSummary> summaries = new ArrayList<>();
+ long weekMultiplier = ChronoUnit.WEEKS.between(
+ ZonedDateTime.ofInstant(Instant.ofEpochSecond(startEpochSeconds), ZONE_OFFSET),
+ mTimeSource.getCurrentDate());
+ for (int i = 1; i < 8; ++i) {
+ summaries.add(constructCarWatchdogDailyIoUsageSummary(
+ /* fgWrBytes= */ 100 * i * weekMultiplier * sysOrUidMultiplier,
+ /* bgWrBytes= */ 200 * i * weekMultiplier * sysOrUidMultiplier,
+ /* gmWrBytes= */ 300 * i * weekMultiplier * sysOrUidMultiplier,
+ /* overuseCount= */ 2 * i));
+ }
+ return summaries;
+ }
+
+ static AtomsProto.CarWatchdogDailyIoUsageSummary constructCarWatchdogDailyIoUsageSummary(
+ long fgWrBytes, long bgWrBytes, long gmWrBytes, int overuseCount) {
+ return AtomsProto.CarWatchdogDailyIoUsageSummary.newBuilder()
+ .setWrittenBytes(WatchdogPerfHandler
+ .constructCarWatchdogPerStateBytes(fgWrBytes, bgWrBytes, gmWrBytes))
+ .setOveruseCount(overuseCount)
+ .build();
}
private class TestClient extends ICarWatchdogServiceCallback.Stub {
@@ -3788,13 +4108,6 @@
return packageInfo;
}
- private static ApplicationInfo constructApplicationInfo(int flags, int privateFlags) {
- ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.flags = flags;
- applicationInfo.privateFlags = privateFlags;
- return applicationInfo;
- }
-
private static String toPackageInfosString(List<PackageInfo> packageInfos) {
StringBuilder builder = new StringBuilder();
for (PackageInfo packageInfo : packageInfos) {
@@ -3877,4 +4190,27 @@
packageInfo.applicationInfo.privateFlags = privateFlags;
return packageInfo;
}
+
+ private static final class TestTimeSource extends TimeSource {
+ private static final Instant TEST_DATE_TIME = Instant.parse("2021-11-12T13:14:15.16Z");
+ private Instant mNow;
+ TestTimeSource() {
+ mNow = TEST_DATE_TIME;
+ }
+
+ @Override
+ public Instant now() {
+ /* Return the same time, so the tests are deterministic. */
+ return mNow;
+ }
+
+ @Override
+ public String toString() {
+ return "Mocked date to " + now();
+ }
+
+ void updateNow(int numDaysAgo) {
+ mNow = TEST_DATE_TIME.minus(numDaysAgo, ChronoUnit.DAYS);
+ }
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/UserPackageSettingsEntrySubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/UserPackageSettingsEntrySubject.java
deleted file mode 100644
index aa1df9e..0000000
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/UserPackageSettingsEntrySubject.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2021 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.car.watchdog;
-
-import static com.google.common.truth.Truth.assertAbout;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.annotation.Nullable;
-
-import com.google.common.truth.Correspondence;
-import com.google.common.truth.FailureMetadata;
-import com.google.common.truth.Subject;
-
-import java.util.Arrays;
-
-public final class UserPackageSettingsEntrySubject extends Subject {
- /* Boiler-plate Subject.Factory for UserPackageSettingsEntrySubject. */
- private static final Subject.Factory<
- com.android.car.watchdog.UserPackageSettingsEntrySubject,
- Iterable<WatchdogStorage.UserPackageSettingsEntry>>
- USER_PACKAGE_SETTINGS_ENTRY_SUBJECT_FACTORY =
- com.android.car.watchdog.UserPackageSettingsEntrySubject::new;
-
- private final Iterable<WatchdogStorage.UserPackageSettingsEntry> mActual;
-
- /* User-defined entry point. */
- public static UserPackageSettingsEntrySubject assertThat(
- @Nullable Iterable<WatchdogStorage.UserPackageSettingsEntry> stats) {
- return assertAbout(USER_PACKAGE_SETTINGS_ENTRY_SUBJECT_FACTORY).that(stats);
- }
-
- public static Subject.Factory<UserPackageSettingsEntrySubject,
- Iterable<WatchdogStorage.UserPackageSettingsEntry>> userPackageSettingsEntries() {
- return USER_PACKAGE_SETTINGS_ENTRY_SUBJECT_FACTORY;
- }
-
- public void containsExactly(WatchdogStorage.UserPackageSettingsEntry... stats) {
- containsExactlyElementsIn(Arrays.asList(stats));
- }
-
- public void containsExactlyElementsIn(
- Iterable<WatchdogStorage.UserPackageSettingsEntry> expected) {
- assertWithMessage("Expected entries (%s) equals to actual entries (%s)",
- toString(expected), toString(mActual)).that(mActual)
- .comparingElementsUsing(Correspondence.from(
- UserPackageSettingsEntrySubject::isEquals, "is equal to"))
- .containsExactlyElementsIn(expected);
- }
-
- public static boolean isEquals(WatchdogStorage.UserPackageSettingsEntry actual,
- WatchdogStorage.UserPackageSettingsEntry expected) {
- if (actual == null || expected == null) {
- return (actual == null) && (expected == null);
- }
- return actual.userId == expected.userId && actual.packageName.equals(expected.packageName)
- && actual.killableState == expected.killableState;
- }
-
- private static String toString(Iterable<WatchdogStorage.UserPackageSettingsEntry> entries) {
- StringBuilder builder = new StringBuilder();
- builder.append("[");
- for (WatchdogStorage.UserPackageSettingsEntry entry : entries) {
- toStringBuilder(builder, entry).append(", ");
- }
- if (builder.length() > 1) {
- builder.delete(builder.length() - 2, builder.length());
- }
- builder.append("]");
- return builder.toString();
- }
-
- private static StringBuilder toStringBuilder(StringBuilder builder,
- WatchdogStorage.UserPackageSettingsEntry entry) {
- return builder.append("{UserId: ").append(entry.userId)
- .append(", Package name: ").append(entry.packageName)
- .append(", Killable state: ").append(entry.killableState).append("}");
- }
-
- private UserPackageSettingsEntrySubject(FailureMetadata failureMetadata,
- @Nullable Iterable<WatchdogStorage.UserPackageSettingsEntry> iterableSubject) {
- super(failureMetadata, iterableSubject);
- this.mActual = iterableSubject;
- }
-}
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 c84225d..32d824c 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
@@ -23,7 +23,6 @@
import static com.android.car.watchdog.WatchdogStorage.RETENTION_PERIOD;
import static com.android.car.watchdog.WatchdogStorage.STATS_TEMPORAL_UNIT;
import static com.android.car.watchdog.WatchdogStorage.WatchdogDbHelper.DATABASE_NAME;
-import static com.android.car.watchdog.WatchdogStorage.ZONE_OFFSET;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -62,15 +61,16 @@
private Context mContext;
private WatchdogStorage mService;
private File mDatabaseFile;
- private TimeSourceInterface mTimeSource;
+
+ private final TestTimeSource mTimeSource = new TestTimeSource();
@Before
public void setUp() throws Exception {
+ mTimeSource.updateNow(/* numDaysAgo= */ 0);
mContext = InstrumentationRegistry.getTargetContext().createDeviceProtectedStorageContext();
mDatabaseFile = mContext.createDeviceProtectedStorageContext()
.getDatabasePath(DATABASE_NAME);
- mService = new WatchdogStorage(mContext, /* useDataSystemCarDir= */ false);
- setDate(/* numDaysAgo= */ 0);
+ mService = new WatchdogStorage(mContext, /* useDataSystemCarDir= */ false, mTimeSource);
}
@After
@@ -87,7 +87,7 @@
assertThat(mService.saveUserPackageSettings(expected)).isTrue();
- UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
+ assertWithMessage("User package settings").that(mService.getUserPackageSettings())
.containsExactlyElementsIn(expected);
}
@@ -109,23 +109,21 @@
assertThat(mService.saveUserPackageSettings(expected)).isTrue();
- UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
- .containsExactlyElementsIn(expected);
+ assertWithMessage("User package settings after overwrite")
+ .that(mService.getUserPackageSettings()).containsExactlyElementsIn(expected);
}
@Test
public void testSaveAndGetIoOveruseStats() throws Exception {
injectSampleUserPackageSettings();
/* Start time aligned to the beginning of the day. */
- long startTime = mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT)
- .toEpochSecond();
+ long startTime = mTimeSource.getCurrentDate().toEpochSecond();
assertWithMessage("Saved I/O usage stats successfully")
.that(mService.saveIoUsageStats(sampleStatsForDate(startTime, /* duration= */ 60)))
.isTrue();
- long expectedDuration =
- mTimeSource.now().atZone(ZONE_OFFSET).toEpochSecond() - startTime;
+ long expectedDuration = mTimeSource.getCurrentDateTime().toEpochSecond() - startTime;
List<WatchdogStorage.IoUsageStatsEntry> expected = sampleStatsForDate(
startTime, expectedDuration);
@@ -137,18 +135,16 @@
public void testSaveAndGetIoOveruseStatsWithOffsettedStartTime() throws Exception {
injectSampleUserPackageSettings();
/* Start time in the middle of the day. */
- long startTime = mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT)
- .plusHours(12).toEpochSecond();
+ long startTime = mTimeSource.getCurrentDate().plusHours(12).toEpochSecond();
List<WatchdogStorage.IoUsageStatsEntry> entries = sampleStatsForDate(
startTime, /* duration= */ 60);
assertWithMessage("Saved I/O usage stats successfully")
.that(mService.saveIoUsageStats(entries)).isTrue();
- long expectedStartTime = mTimeSource.now().atZone(ZONE_OFFSET)
- .truncatedTo(STATS_TEMPORAL_UNIT).toEpochSecond();
+ long expectedStartTime = mTimeSource.getCurrentDate().toEpochSecond();
long expectedDuration =
- mTimeSource.now().atZone(ZONE_OFFSET).toEpochSecond() - expectedStartTime;
+ mTimeSource.getCurrentDateTime().toEpochSecond() - expectedStartTime;
List<WatchdogStorage.IoUsageStatsEntry> expected = sampleStatsForDate(
expectedStartTime, expectedDuration);
@@ -159,9 +155,8 @@
@Test
public void testOverwriteIoOveruseStats() throws Exception {
injectSampleUserPackageSettings();
- long startTime = mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT)
- .toEpochSecond();
- long duration = mTimeSource.now().atZone(ZONE_OFFSET).toEpochSecond() - startTime;
+ long startTime = mTimeSource.getCurrentDate().toEpochSecond();
+ long duration = mTimeSource.getCurrentDateTime().toEpochSecond() - startTime;
List<WatchdogStorage.IoUsageStatsEntry> statsBeforeOverwrite = Collections.singletonList(
constructIoUsageStatsEntry(
@@ -200,8 +195,8 @@
.containsExactlyElementsIn(statsBeforeOverwrite);
mService.release();
- mService = new WatchdogStorage(mContext, /* useDataSystemCarDir= */ false);
- setDate(/* numDaysAgo= */ 0);
+ mTimeSource.updateNow(/* numDaysAgo= */ 0);
+ mService = new WatchdogStorage(mContext, /* useDataSystemCarDir= */ false, mTimeSource);
assertWithMessage("User packages settings").that(mService.getUserPackageSettings())
.isNotEmpty();
@@ -243,12 +238,11 @@
* Returned stats shouldn't include stats for the current date as WatchdogPerfHandler fills
* the current day's stats.
*/
- ZonedDateTime currentDate = mTimeSource.now().atZone(ZONE_OFFSET)
- .truncatedTo(STATS_TEMPORAL_UNIT);
+ ZonedDateTime currentDate = mTimeSource.getCurrentDate();
long startTime = currentDate.minus(4, STATS_TEMPORAL_UNIT).toEpochSecond();
long duration = currentDate.toEpochSecond() - startTime;
IoOveruseStats expected = new IoOveruseStats.Builder(startTime, duration)
- .setTotalOveruses(8).setTotalTimesKilled(4).setTotalBytesWritten(24_000).build();
+ .setTotalOveruses(8).setTotalTimesKilled(4).setTotalBytesWritten(25_200).build();
IoOveruseStatsSubject.assertWithMessage(
"Fetched stats only for 4 days. Expected stats (%s) equals actual stats (%s)",
@@ -272,12 +266,11 @@
* Returned stats shouldn't include stats for the current date as WatchdogPerfHandler fills
* the current day's stats.
*/
- ZonedDateTime currentDate = mTimeSource.now().atZone(ZONE_OFFSET)
- .truncatedTo(STATS_TEMPORAL_UNIT);
+ ZonedDateTime currentDate = mTimeSource.getCurrentDate();
long startTime = currentDate.minus(4, STATS_TEMPORAL_UNIT).toEpochSecond();
long duration = currentDate.toEpochSecond() - startTime;
IoOveruseStats expected = new IoOveruseStats.Builder(startTime, duration)
- .setTotalOveruses(4).setTotalTimesKilled(2).setTotalBytesWritten(12_000).build();
+ .setTotalOveruses(4).setTotalTimesKilled(2).setTotalBytesWritten(12_600).build();
IoOveruseStatsSubject.assertWithMessage(
"Fetched stats only for 2 days. Expected stats (%s) equals actual stats (%s)",
@@ -286,6 +279,120 @@
}
@Test
+ public void testGetDailySystemIoUsageSummaries() throws Exception {
+ injectSampleUserPackageSettings();
+ List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
+ for (int i = 1; i <= 30; ++i) {
+ entries.addAll(sampleStatsBetweenDates(/* includingStartDaysAgo= */ i,
+ /* excludingEndDaysAgo= */ i + 1, /* writtenBytesMultiplier= */ i));
+ }
+
+ assertWithMessage("Save I/O usage stats").that(mService.saveIoUsageStats(entries)).isTrue();
+
+ ZonedDateTime currentDate = mTimeSource.getCurrentDate();
+
+ List<AtomsProto.CarWatchdogDailyIoUsageSummary> actual =
+ mService.getDailySystemIoUsageSummaries(
+ /* includingStartEpochSeconds= */ currentDate.minusDays(15).toEpochSecond(),
+ /* excludingEndEpochSeconds= */ currentDate.minusDays(7).toEpochSecond());
+
+ List<AtomsProto.CarWatchdogDailyIoUsageSummary> expected = new ArrayList<>();
+ for (int i = 15; i > 7; --i) {
+ expected.add(CarWatchdogServiceUnitTest
+ .constructCarWatchdogDailyIoUsageSummary(/* fgWrBytes= */ 10402L * i,
+ /* bgWrBytes= */ 14402L * i, /* gmWrBytes= */ 18402L * i,
+ /* overuseCount= */ 6));
+ }
+
+ assertWithMessage("Daily system I/O usage summary stats").that(actual).isEqualTo(expected);
+ }
+
+ @Test
+ public void testGetDailySystemIoUsageSummariesWithoutStats() throws Exception {
+ injectSampleUserPackageSettings();
+ List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
+ for (int i = 1; i <= 7; ++i) {
+ entries.addAll(sampleStatsBetweenDates(/* includingStartDaysAgo= */ i,
+ /* excludingEndDaysAgo= */ i + 1, /* writtenBytesMultiplier= */ i));
+ }
+
+ assertWithMessage("Save I/O usage stats").that(mService.saveIoUsageStats(entries)).isTrue();
+
+ ZonedDateTime currentDate = mTimeSource.getCurrentDate();
+
+ List<AtomsProto.CarWatchdogDailyIoUsageSummary> actual =
+ mService.getDailySystemIoUsageSummaries(
+ /* includingStartEpochSeconds= */ currentDate.minusDays(15).toEpochSecond(),
+ /* excludingEndEpochSeconds= */ currentDate.minusDays(7).toEpochSecond());
+
+ assertWithMessage("Daily system I/O usage summary stats").that(actual).isNull();
+ }
+
+ @Test
+ public void testGetTopUsersDailyIoUsageSummaries() throws Exception {
+ injectSampleUserPackageSettings();
+ List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
+ for (int i = 1; i <= 30; ++i) {
+ entries.addAll(sampleStatsBetweenDates(/* includingStartDaysAgo= */ i,
+ /* excludingEndDaysAgo= */ i + 1, /* writtenBytesMultiplier= */ i));
+ }
+
+ assertWithMessage("Save I/O usage stats").that(mService.saveIoUsageStats(entries)).isTrue();
+
+ ZonedDateTime currentDate = mTimeSource.getCurrentDate();
+
+ List<WatchdogStorage.UserPackageDailySummaries> actual =
+ mService.getTopUsersDailyIoUsageSummaries(/* numTopUsers= */ 3,
+ /* minTotalWrittenBytes= */ 600_000,
+ /* includingStartEpochSeconds= */ currentDate.minusDays(15).toEpochSecond(),
+ /* excludingEndEpochSeconds= */ currentDate.minusDays(7).toEpochSecond());
+
+ List<AtomsProto.CarWatchdogDailyIoUsageSummary> user101VendorPkgSummaries =
+ new ArrayList<>();
+ List<AtomsProto.CarWatchdogDailyIoUsageSummary> user100VendorPkgSummaries =
+ new ArrayList<>();
+ for (int i = 15; i > 7; --i) {
+ user101VendorPkgSummaries.add(CarWatchdogServiceUnitTest
+ .constructCarWatchdogDailyIoUsageSummary(/* fgWrBytes= */ 4101L * i,
+ /* bgWrBytes= */ 5101L * i, /* gmWrBytes= */ 6101L * i,
+ /* overuseCount= */ 1));
+ user100VendorPkgSummaries.add(CarWatchdogServiceUnitTest
+ .constructCarWatchdogDailyIoUsageSummary(/* fgWrBytes= */ 4100L * i,
+ /* bgWrBytes= */ 5100L * i, /* gmWrBytes= */ 6100L * i,
+ /* overuseCount= */ 1));
+ }
+ List<WatchdogStorage.UserPackageDailySummaries> expected = Arrays.asList(
+ new WatchdogStorage.UserPackageDailySummaries(/* userId= */ 101,
+ /* packageName= */ "vendor_package.critical.C", user101VendorPkgSummaries),
+ new WatchdogStorage.UserPackageDailySummaries(/* userId= */ 100,
+ /* packageName= */ "vendor_package.critical.C", user100VendorPkgSummaries));
+
+ assertWithMessage("Top users daily I/O usage summaries").that(actual).isEqualTo(expected);
+ }
+
+ @Test
+ public void testGetTopUsersDailyIoUsageSummariesWithoutStats() throws Exception {
+ injectSampleUserPackageSettings();
+ List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
+ for (int i = 1; i <= 7; ++i) {
+ entries.addAll(sampleStatsBetweenDates(/* includingStartDaysAgo= */ i,
+ /* excludingEndDaysAgo= */ i + 1, /* writtenBytesMultiplier= */ i));
+ }
+
+ assertWithMessage("Save I/O usage stats").that(mService.saveIoUsageStats(entries)).isTrue();
+
+ ZonedDateTime currentDate = mTimeSource.getCurrentDate();
+
+ List<WatchdogStorage.UserPackageDailySummaries> actual =
+ mService.getTopUsersDailyIoUsageSummaries(/* numTopUsers= */ 3,
+ /* minTotalWrittenBytes= */ 600_000,
+ /* includingStartEpochSeconds= */ currentDate.minusDays(15).toEpochSecond(),
+ /* excludingEndEpochSeconds= */ currentDate.minusDays(7).toEpochSecond());
+
+ assertWithMessage("Top users daily I/O usage summaries").that(actual).isNull();
+ }
+
+ @Test
public void testDeleteUserPackage() throws Exception {
ArrayList<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = sampleStatsForToday();
@@ -301,8 +408,8 @@
settingsEntries.removeIf(
(s) -> s.userId == deleteUserId && s.packageName.equals(deletePackageName));
- UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
- .containsExactlyElementsIn(settingsEntries);
+ assertWithMessage("User package settings after deleting a user package")
+ .that(mService.getUserPackageSettings()).containsExactlyElementsIn(settingsEntries);
ioUsageStatsEntries.removeIf(
(e) -> e.userId == deleteUserId && e.packageName.equals(deletePackageName));
@@ -323,7 +430,7 @@
mService.deleteUserPackage(deleteUserId, deletePackageName);
- UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
+ assertWithMessage("User package settings").that(mService.getUserPackageSettings())
.containsExactlyElementsIn(sampleSettings());
ioUsageStatsEntries.removeIf(
@@ -350,8 +457,8 @@
settingsEntries.removeIf(
(s) -> s.userId == deleteUserId && s.packageName.equals(deletePackageName));
- UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
- .containsExactlyElementsIn(settingsEntries);
+ assertWithMessage("User package settings after deleting user package with historical stats")
+ .that(mService.getUserPackageSettings()).containsExactlyElementsIn(settingsEntries);
IoOveruseStats actual = mService.getHistoricalIoOveruseStats(
/* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 7);
@@ -372,8 +479,8 @@
settingsEntries.removeIf((s) -> s.userId == 100);
ioUsageStatsEntries.removeIf((e) -> e.userId == 100);
- UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
- .containsExactlyElementsIn(settingsEntries);
+ assertWithMessage("User package settings after syncing alive users")
+ .that(mService.getUserPackageSettings()).containsExactlyElementsIn(settingsEntries);
IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
.containsExactlyElementsIn(ioUsageStatsEntries);
@@ -391,8 +498,8 @@
settingsEntries.removeIf((s) -> s.userId == 100);
- UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
- .containsExactlyElementsIn(settingsEntries);
+ assertWithMessage("User package settings after syncing alive users with historical stats")
+ .that(mService.getUserPackageSettings()).containsExactlyElementsIn(settingsEntries);
IoOveruseStats actualSystemPackage = mService.getHistoricalIoOveruseStats(
/* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 7);
@@ -415,8 +522,8 @@
mService.syncUsers(/* aliveUserIds= */ new int[] {100, 101});
- UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
- .containsExactlyElementsIn(settingsEntries);
+ assertWithMessage("User package settings after syncing users")
+ .that(mService.getUserPackageSettings()).containsExactlyElementsIn(settingsEntries);
IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
.containsExactlyElementsIn(ioUsageStatsEntries);
}
@@ -424,7 +531,7 @@
@Test
public void testTruncateStatsOutsideRetentionPeriodOnDateChange() throws Exception {
injectSampleUserPackageSettings();
- setDate(/* numDaysAgo= */ 1);
+ mTimeSource.updateNow(/* numDaysAgo= */ 1);
assertThat(mService.saveIoUsageStats(sampleStatsBetweenDates(
/* includingStartDaysAgo= */ 0, /* excludingEndDaysAgo= */ 40),
@@ -435,19 +542,18 @@
assertWithMessage("Fetched I/O overuse stats").that(actual).isNotNull();
- ZonedDateTime currentDate = mTimeSource.now().atZone(ZONE_OFFSET)
- .truncatedTo(STATS_TEMPORAL_UNIT);
+ ZonedDateTime currentDate = mTimeSource.getCurrentDate();
long startTime = currentDate.minus(39, STATS_TEMPORAL_UNIT).toEpochSecond();
long duration = currentDate.toEpochSecond() - startTime;
IoOveruseStats expected = new IoOveruseStats.Builder(startTime, duration)
- .setTotalOveruses(78).setTotalTimesKilled(39).setTotalBytesWritten(234_000).build();
+ .setTotalOveruses(78).setTotalTimesKilled(39).setTotalBytesWritten(245_700).build();
IoOveruseStatsSubject.assertWithMessage(
"Fetched stats only for 39 days. Expected stats (%s) equals actual stats (%s)",
expected.toString(), actual.toString()).that(actual)
.isEqualTo(expected);
- setDate(/* numDaysAgo= */ 0);
+ mTimeSource.updateNow(/* numDaysAgo= */ 0);
mService.shrinkDatabase();
actual = mService.getHistoricalIoOveruseStats(
@@ -455,36 +561,17 @@
assertWithMessage("Fetched I/O overuse stats").that(actual).isNotNull();
- currentDate = mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
+ currentDate = mTimeSource.getCurrentDate();
startTime = currentDate.minus(RETENTION_PERIOD.minusDays(1)).toEpochSecond();
duration = currentDate.toEpochSecond() - startTime;
expected = new IoOveruseStats.Builder(startTime, duration)
- .setTotalOveruses(58).setTotalTimesKilled(29).setTotalBytesWritten(174_000).build();
+ .setTotalOveruses(58).setTotalTimesKilled(29).setTotalBytesWritten(182_700).build();
IoOveruseStatsSubject.assertWithMessage("Fetched stats only within retention period. "
+ "Expected stats (%s) equals actual stats (%s)",
expected.toString(), actual.toString()).that(actual).isEqualTo(expected);
}
- private void setDate(int numDaysAgo) {
- TimeSourceInterface timeSource = new TimeSourceInterface() {
- @Override
- public Instant now() {
- /* Return the same time, so the tests are deterministic. */
- return mNow;
- }
-
- @Override
- public String toString() {
- return "Mocked date to " + now();
- }
-
- private final Instant mNow = Instant.now().minus(numDaysAgo, ChronoUnit.DAYS);
- };
- mService.setTimeSource(timeSource);
- mTimeSource = timeSource;
- }
-
private void injectSampleUserPackageSettings() throws Exception {
List<WatchdogStorage.UserPackageSettingsEntry> expected = sampleSettings();
@@ -509,45 +596,60 @@
private ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsBetweenDates(
int includingStartDaysAgo, int excludingEndDaysAgo) {
- ZonedDateTime currentDate = mTimeSource.now().atZone(ZONE_OFFSET)
- .truncatedTo(STATS_TEMPORAL_UNIT);
+ return sampleStatsBetweenDates(includingStartDaysAgo, excludingEndDaysAgo,
+ /* writtenBytesMultiplier= */ 1);
+ }
+
+ private ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsBetweenDates(
+ int includingStartDaysAgo, int excludingEndDaysAgo, int writtenBytesMultiplier) {
+ ZonedDateTime currentDate = mTimeSource.getCurrentDate();
ArrayList<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
for (int i = includingStartDaysAgo; i < excludingEndDaysAgo; ++i) {
- entries.addAll(sampleStatsForDate(
- currentDate.minus(i, STATS_TEMPORAL_UNIT).toEpochSecond(),
- STATS_TEMPORAL_UNIT.getDuration().toSeconds()));
+ entries.addAll(
+ sampleStatsForDate(currentDate.minus(i, STATS_TEMPORAL_UNIT).toEpochSecond(),
+ STATS_TEMPORAL_UNIT.getDuration().toSeconds(), writtenBytesMultiplier));
}
return entries;
}
private ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsForToday() {
- long currentTime = mTimeSource.now().atZone(ZONE_OFFSET)
- .truncatedTo(STATS_TEMPORAL_UNIT).toEpochSecond();
- long duration = mTimeSource.now().atZone(ZONE_OFFSET).toEpochSecond() - currentTime;
- return sampleStatsForDate(currentTime, duration);
+ long statsDateEpoch = mTimeSource.getCurrentDate().toEpochSecond();
+ long duration = mTimeSource.getCurrentDateTime().toEpochSecond() - statsDateEpoch;
+ return sampleStatsForDate(statsDateEpoch, duration, /* writtenBytesMultiplier= */ 1);
}
private static ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsForDate(
long statsDateEpoch, long duration) {
+ return sampleStatsForDate(statsDateEpoch, duration, /* writtenBytesMultiplier= */ 1);
+ }
+
+ private static ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsForDate(
+ long statsDateEpoch, long duration, int writtenBytesMultiplier) {
ArrayList<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
for (int i = 100; i <= 101; ++i) {
entries.add(constructIoUsageStatsEntry(
/* userId= */ i, "system_package.non_critical.A", statsDateEpoch, duration,
/* remainingWriteBytes= */
- CarWatchdogServiceUnitTest.constructPerStateBytes(200, 300, 400),
+ CarWatchdogServiceUnitTest.constructPerStateBytes(200L, 300L, 400L),
/* writtenBytes= */
- CarWatchdogServiceUnitTest.constructPerStateBytes(1000, 2000, 3000),
+ CarWatchdogServiceUnitTest.constructPerStateBytes(
+ (1000L + i) * writtenBytesMultiplier,
+ (2000L + i) * writtenBytesMultiplier,
+ (3000L + i) * writtenBytesMultiplier),
/* forgivenWriteBytes= */
- CarWatchdogServiceUnitTest.constructPerStateBytes(100, 100, 100),
+ CarWatchdogServiceUnitTest.constructPerStateBytes(100L, 100L, 100L),
/* totalOveruses= */ 2, /* totalTimesKilled= */ 1));
entries.add(constructIoUsageStatsEntry(
/* userId= */ i, "vendor_package.critical.C", statsDateEpoch, duration,
/* remainingWriteBytes= */
- CarWatchdogServiceUnitTest.constructPerStateBytes(500, 600, 700),
+ CarWatchdogServiceUnitTest.constructPerStateBytes(500L, 600L, 700L),
/* writtenBytes= */
- CarWatchdogServiceUnitTest.constructPerStateBytes(4000, 5000, 6000),
+ CarWatchdogServiceUnitTest.constructPerStateBytes(
+ (4000L + i) * writtenBytesMultiplier,
+ (5000L + i) * writtenBytesMultiplier,
+ (6000L + i) * writtenBytesMultiplier),
/* forgivenWriteBytes= */
- CarWatchdogServiceUnitTest.constructPerStateBytes(200, 200, 200),
+ CarWatchdogServiceUnitTest.constructPerStateBytes(200L, 200L, 200L),
/* totalOveruses= */ 1, /* totalTimesKilled= */ 0));
}
return entries;
@@ -575,4 +677,27 @@
stats.totalOveruses = totalOveruses;
return stats;
}
+
+ private static final class TestTimeSource extends TimeSource {
+ private static final Instant TEST_DATE_TIME = Instant.parse("2021-11-12T13:14:15.16Z");
+ private Instant mNow;
+ TestTimeSource() {
+ mNow = TEST_DATE_TIME;
+ }
+
+ @Override
+ public Instant now() {
+ /* Return the same time, so the tests are deterministic. */
+ return mNow;
+ }
+
+ @Override
+ public String toString() {
+ return "Mocked date to " + now();
+ }
+
+ void updateNow(int numDaysAgo) {
+ mNow = TEST_DATE_TIME.minus(numDaysAgo, ChronoUnit.DAYS);
+ }
+ };
}