Fetch today's I/O usage stats collected during previous boot.

Watchdog daemon fetches today's I/O usage stat, that were collected
during the previous boot, from CarWatchdogService, so the daemon can the
have the aggregated view of the total bytes written to disk since the
beginning of the day.

Test: atest CarWatchdogServiceUnitTest WatchdogStorageUnitTest
libwatchdog_test
Bug: 192665981

Change-Id: Ieb3f3ba27d4d94f68f94788a364007fbc022a872
Merged-In: Ieb3f3ba27d4d94f68f94788a364007fbc022a872
(cherry picked from commit 766a91829ffdb341a6d59ed65e7e0d2f38273357)
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl
index c0cfd23..8f36fad 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl
@@ -19,6 +19,7 @@
 import android.automotive.watchdog.internal.PackageInfo;
 import android.automotive.watchdog.internal.PackageIoOveruseStats;
 import android.automotive.watchdog.internal.TimeoutLength;
+import android.automotive.watchdog.internal.UserPackageIoUsageStats;
 
 /**
  * ICarWatchdogServiceForSystem interface used by the watchdog server to communicate with the
@@ -69,4 +70,10 @@
    * @param packageNames       Package names for which to reset the stats.
    */
   oneway void resetResourceOveruseStats(in @utf8InCpp List<String> packageNames);
+
+  /**
+   * Fetches today's I/O usage stats for all packages collected during the
+   * previous boot.
+   */
+  List<UserPackageIoUsageStats> getTodayIoUsageStats();
 }
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/IoUsageStats.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/IoUsageStats.aidl
new file mode 100644
index 0000000..468ca99
--- /dev/null
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/IoUsageStats.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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 android.automotive.watchdog.internal;
+
+import android.automotive.watchdog.PerStateBytes;
+
+/**
+ * Structure that describes the I/O usage stats.
+ */
+parcelable IoUsageStats {
+  /**
+   * Total number of bytes written to disk.
+   */
+  PerStateBytes writtenBytes;
+
+  /**
+   * Number of bytes written to disk but forgiven.
+   */
+  PerStateBytes forgivenWriteBytes;
+
+  /**
+   * Total number of overuses.
+   */
+  int totalOveruses;
+}
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl
index c0dd86e..b4ff4c1 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl
@@ -21,7 +21,7 @@
  */
 parcelable PackageIdentifier {
   /**
-   * Name of the package.
+   * Generic name of the package.
    */
   @utf8InCpp String name;
 
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/UserPackageIoUsageStats.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/UserPackageIoUsageStats.aidl
new file mode 100644
index 0000000..fb867e6
--- /dev/null
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/UserPackageIoUsageStats.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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 android.automotive.watchdog.internal;
+
+import android.automotive.watchdog.internal.IoUsageStats;
+
+/**
+ * Structure that describes the I/O usage stats for a user package.
+ */
+parcelable UserPackageIoUsageStats {
+  /**
+   * User ID for the package.
+   */
+  int userId;
+
+  /**
+   * Generic name of the package whose stats are recorded in this parcelable.
+   */
+  @utf8InCpp String packageName;
+
+   /**
+    * I/O overuse stats for the package.
+    */
+  IoUsageStats ioUsageStats;
+}
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.cpp b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
index 23e14ee..9d40f8b 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.cpp
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
@@ -40,16 +40,20 @@
 namespace automotive {
 namespace watchdog {
 
+namespace {
+
 using ::android::IPCThreadState;
 using ::android::sp;
 using ::android::automotive::watchdog::internal::ComponentType;
 using ::android::automotive::watchdog::internal::IoOveruseConfiguration;
+using ::android::automotive::watchdog::internal::IoUsageStats;
 using ::android::automotive::watchdog::internal::PackageIdentifier;
 using ::android::automotive::watchdog::internal::PackageInfo;
 using ::android::automotive::watchdog::internal::PackageIoOveruseStats;
 using ::android::automotive::watchdog::internal::PackageResourceOveruseAction;
 using ::android::automotive::watchdog::internal::ResourceOveruseConfiguration;
 using ::android::automotive::watchdog::internal::UidType;
+using ::android::automotive::watchdog::internal::UserPackageIoUsageStats;
 using ::android::base::Error;
 using ::android::base::Result;
 using ::android::base::WriteStringToFd;
@@ -66,10 +70,25 @@
         "%s <package name>, <package name>,...: Reset resource overuse stats for the given package "
         "names. Value for this flag is a comma-separated value containing package names.\n";
 
-namespace {
+std::string uniquePackageIdStr(const std::string& name, userid_t userId) {
+    return StringPrintf("%s:%" PRId32, name.c_str(), userId);
+}
 
 std::string uniquePackageIdStr(const PackageIdentifier& id) {
-    return StringPrintf("%s:%" PRId32, id.name.c_str(), multiuser_get_user_id(id.uid));
+    return uniquePackageIdStr(id.name, multiuser_get_user_id(id.uid));
+}
+
+PerStateBytes sum(const PerStateBytes& lhs, const PerStateBytes& rhs) {
+    const auto sum = [](const int64_t& l, const int64_t& r) -> int64_t {
+        return (std::numeric_limits<int64_t>::max() - l) > r ? (l + r)
+                                                             : std::numeric_limits<int64_t>::max();
+    };
+
+    PerStateBytes result;
+    result.foregroundBytes = sum(lhs.foregroundBytes, rhs.foregroundBytes);
+    result.backgroundBytes = sum(lhs.backgroundBytes, rhs.backgroundBytes);
+    result.garageModeBytes = sum(lhs.garageModeBytes, rhs.garageModeBytes);
+    return result;
 }
 
 PerStateBytes diff(const PerStateBytes& lhs, const PerStateBytes& rhs) {
@@ -116,6 +135,7 @@
         const android::sp<IWatchdogServiceHelper>& watchdogServiceHelper) :
       mMinSyncWrittenBytes(kMinSyncWrittenBytes),
       mWatchdogServiceHelper(watchdogServiceHelper),
+      mDidReadTodayPrevBootStats(false),
       mSystemWideWrittenBytes({}),
       mPeriodicMonitorBufferSize(0),
       mLastSystemWideIoMonitorTime(0),
@@ -179,6 +199,9 @@
     }
 
     std::unique_lock writeLock(mRwMutex);
+    if (!mDidReadTodayPrevBootStats) {
+        syncTodayIoUsageStatsLocked();
+    }
     struct tm prevGmt, curGmt;
     gmtime_r(&mLastUserPackageIoMonitorTime, &prevGmt);
     gmtime_r(&time, &curGmt);
@@ -216,6 +239,11 @@
             cachedUsage->second += curUsage;
             dailyIoUsage = &cachedUsage->second;
         } else {
+            if (auto prevBootStats = mPrevBootIoUsageStatsById.find(curUsage.id());
+                prevBootStats != mPrevBootIoUsageStatsById.end()) {
+                curUsage += prevBootStats->second;
+                mPrevBootIoUsageStatsById.erase(prevBootStats);
+            }
             const auto& [it, wasInserted] = mUserPackageDailyIoUsageById.insert(
                     std::pair(curUsage.id(), std::move(curUsage)));
             dailyIoUsage = &it->second;
@@ -380,6 +408,27 @@
                            fd);
 }
 
+void IoOveruseMonitor::syncTodayIoUsageStatsLocked() {
+    std::vector<UserPackageIoUsageStats> userPackageIoUsageStats;
+    if (const auto status = mWatchdogServiceHelper->getTodayIoUsageStats(&userPackageIoUsageStats);
+        !status.isOk()) {
+        ALOGE("Failed to fetch today I/O usage stats collected during previous boot: %s",
+              status.exceptionMessage().c_str());
+        return;
+    }
+    for (const auto& statsEntry : userPackageIoUsageStats) {
+        std::string uniqueId = uniquePackageIdStr(statsEntry.packageName,
+                                                  static_cast<userid_t>(statsEntry.userId));
+        if (auto it = mUserPackageDailyIoUsageById.find(uniqueId);
+            it != mUserPackageDailyIoUsageById.end()) {
+            it->second += statsEntry.ioUsageStats;
+            continue;
+        }
+        mPrevBootIoUsageStatsById.insert(std::pair(uniqueId, statsEntry.ioUsageStats));
+    }
+    mDidReadTodayPrevBootStats = true;
+}
+
 void IoOveruseMonitor::notifyNativePackagesLocked(
         const std::unordered_map<uid_t, IoOveruseStats>& statsByUid) {
     for (const auto& [uid, ioOveruseStats] : statsByUid) {
@@ -585,17 +634,15 @@
     if (id() == r.id()) {
         packageInfo = r.packageInfo;
     }
-    const auto sum = [](const int64_t& l, const int64_t& r) -> int64_t {
-        return (std::numeric_limits<int64_t>::max() - l) > r ? (l + r)
-                                                             : std::numeric_limits<int64_t>::max();
-    };
-    writtenBytes.foregroundBytes =
-            sum(writtenBytes.foregroundBytes, r.writtenBytes.foregroundBytes);
-    writtenBytes.backgroundBytes =
-            sum(writtenBytes.backgroundBytes, r.writtenBytes.backgroundBytes);
-    writtenBytes.garageModeBytes =
-            sum(writtenBytes.garageModeBytes, r.writtenBytes.garageModeBytes);
+    writtenBytes = sum(writtenBytes, r.writtenBytes);
+    return *this;
+}
 
+IoOveruseMonitor::UserPackageIoUsage& IoOveruseMonitor::UserPackageIoUsage::operator+=(
+        const IoUsageStats& ioUsageStats) {
+    writtenBytes = sum(writtenBytes, ioUsageStats.writtenBytes);
+    forgivenWriteBytes = sum(forgivenWriteBytes, ioUsageStats.forgivenWriteBytes);
+    totalOveruses += ioUsageStats.totalOveruses;
     return *this;
 }
 
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.h b/cpp/watchdog/server/src/IoOveruseMonitor.h
index df7ff60..57c01c0 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.h
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.h
@@ -193,6 +193,8 @@
         uint64_t lastSyncedWrittenBytes = 0;
 
         UserPackageIoUsage& operator+=(const UserPackageIoUsage& r);
+        UserPackageIoUsage& operator+=(
+                const android::automotive::watchdog::internal::IoUsageStats& r);
 
         const std::string id() const;
         void resetStats();
@@ -214,6 +216,8 @@
 private:
     bool isInitializedLocked() const { return mIoOveruseConfigs != nullptr; }
 
+    void syncTodayIoUsageStatsLocked();
+
     void notifyNativePackagesLocked(const std::unordered_map<uid_t, IoOveruseStats>& statsByUid);
 
     void handleBinderDeath(const wp<IBinder>& who);
@@ -237,6 +241,10 @@
     // Makes sure only one collection is running at any given time.
     mutable std::shared_mutex mRwMutex;
 
+    // Indicates whether or not today's I/O usage stats, that were collected during previous boot,
+    // are read from CarService because CarService persists these stats in database across reboot.
+    bool mDidReadTodayPrevBootStats GUARDED_BY(mRwMutex);
+
     // Summary of configs available for all the components and system-wide overuse alert thresholds.
     sp<IIoOveruseConfigs> mIoOveruseConfigs GUARDED_BY(mRwMutex);
 
@@ -248,6 +256,11 @@
     size_t mPeriodicMonitorBufferSize GUARDED_BY(mRwMutex);
     time_t mLastSystemWideIoMonitorTime GUARDED_BY(mRwMutex);
 
+    // Cache of I/O usage stats from previous boot that happened today. Key is a unique ID with
+    // the format `packageName:userId`.
+    std::unordered_map<std::string, android::automotive::watchdog::internal::IoUsageStats>
+            mPrevBootIoUsageStatsById GUARDED_BY(mRwMutex);
+
     // Cache of per user package I/O usage. Key is a unique ID with the format `packageName:userId`.
     std::unordered_map<std::string, UserPackageIoUsage> mUserPackageDailyIoUsageById
             GUARDED_BY(mRwMutex);
diff --git a/cpp/watchdog/server/src/WatchdogServiceHelper.cpp b/cpp/watchdog/server/src/WatchdogServiceHelper.cpp
index 4c1258f..702ad95 100644
--- a/cpp/watchdog/server/src/WatchdogServiceHelper.cpp
+++ b/cpp/watchdog/server/src/WatchdogServiceHelper.cpp
@@ -32,6 +32,7 @@
 using aawi::ICarWatchdogServiceForSystem;
 using aawi::PackageInfo;
 using aawi::PackageIoOveruseStats;
+using aawi::UserPackageIoUsageStats;
 using ::android::IBinder;
 using ::android::sp;
 using ::android::wp;
@@ -214,6 +215,17 @@
     return service->resetResourceOveruseStats(packageNames);
 }
 
+Status WatchdogServiceHelper::getTodayIoUsageStats(
+        std::vector<UserPackageIoUsageStats>* userPackageIoUsageStats) {
+    sp<ICarWatchdogServiceForSystem> service;
+    if (std::shared_lock readLock(mRWMutex); mService == nullptr) {
+        return fromExceptionCode(Status::EX_ILLEGAL_STATE, "Watchdog service is not initialized");
+    } else {
+        service = mService;
+    }
+    return service->getTodayIoUsageStats(userPackageIoUsageStats);
+}
+
 }  // namespace watchdog
 }  // namespace automotive
 }  // namespace android
diff --git a/cpp/watchdog/server/src/WatchdogServiceHelper.h b/cpp/watchdog/server/src/WatchdogServiceHelper.h
index 390013a..eb25c6d 100644
--- a/cpp/watchdog/server/src/WatchdogServiceHelper.h
+++ b/cpp/watchdog/server/src/WatchdogServiceHelper.h
@@ -70,6 +70,9 @@
                     packageIoOveruseStats) = 0;
     virtual android::binder::Status resetResourceOveruseStats(
             const std::vector<std::string>& packageNames) = 0;
+    virtual android::binder::Status getTodayIoUsageStats(
+            std::vector<android::automotive::watchdog::internal::UserPackageIoUsageStats>*
+                    userPackageIoUsageStats) = 0;
 
 protected:
     virtual android::base::Result<void> init(
@@ -110,6 +113,9 @@
             const std::vector<android::automotive::watchdog::internal::PackageIoOveruseStats>&
                     packageIoOveruseStats);
     android::binder::Status resetResourceOveruseStats(const std::vector<std::string>& packageNames);
+    android::binder::Status getTodayIoUsageStats(
+            std::vector<android::automotive::watchdog::internal::UserPackageIoUsageStats>*
+                    userPackageIoUsageStats);
 
 protected:
     android::base::Result<void> init(
diff --git a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
index 76f3cbb..f4dad98 100644
--- a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
+++ b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
@@ -44,6 +44,7 @@
 using ::android::automotive::watchdog::internal::PackageIoOveruseStats;
 using ::android::automotive::watchdog::internal::ResourceOveruseConfiguration;
 using ::android::automotive::watchdog::internal::UidType;
+using ::android::automotive::watchdog::internal::UserPackageIoUsageStats;
 using ::android::base::Error;
 using ::android::base::Result;
 using ::android::base::StringAppendF;
@@ -54,6 +55,7 @@
 using ::testing::Return;
 using ::testing::ReturnRef;
 using ::testing::SaveArg;
+using ::testing::SetArgPointee;
 using ::testing::UnorderedElementsAreArray;
 
 namespace {
@@ -120,6 +122,20 @@
     return stats;
 }
 
+UserPackageIoUsageStats constructUserPackageIoUsageStats(userid_t userId,
+                                                         const std::string& packageName,
+                                                         const PerStateBytes& writtenBytes,
+                                                         const PerStateBytes& forgivenWriteBytes,
+                                                         int32_t totalOveruses) {
+    UserPackageIoUsageStats stats;
+    stats.userId = userId;
+    stats.packageName = packageName;
+    stats.ioUsageStats.writtenBytes = writtenBytes;
+    stats.ioUsageStats.forgivenWriteBytes = forgivenWriteBytes;
+    stats.ioUsageStats.totalOveruses = totalOveruses;
+    return stats;
+}
+
 class ScopedChangeCallingUid : public RefBase {
 public:
     explicit ScopedChangeCallingUid(uid_t uid) {
@@ -219,6 +235,10 @@
                  {constructPerStateBytes(/*fgBytes=*/70'000, /*bgBytes=*/30'000,
                                          /*gmBytes=*/100'000),
                   /*isSafeToKill=*/true}},
+                {"com.android.kitchensink",
+                 {constructPerStateBytes(/*fgBytes=*/30'000, /*bgBytes=*/15'000,
+                                         /*gmBytes=*/10'000),
+                  /*isSafeToKill=*/true}},
         });
     }
 
@@ -229,6 +249,8 @@
             PackageInfo packageInfo;
             if (kPackageInfosByUid.find(uid) != kPackageInfosByUid.end()) {
                 packageInfo = kPackageInfosByUid.at(uid);
+            } else {
+                packageInfo.packageIdentifier.uid = uid;
             }
             uidStats.push_back(UidStats{.packageInfo = packageInfo,
                                         .ioStats = {/*fgRdBytes=*/989'000,
@@ -272,6 +294,10 @@
           constructPackageInfo(
                   /*packageName=*/"com.android.google.package",
                   /*uid=*/1212345, UidType::APPLICATION)},
+         {1245678,
+          constructPackageInfo(
+                  /*packageName=*/"com.android.kitchensink",
+                  /*uid=*/1245678, UidType::APPLICATION)},
          {1312345,
           constructPackageInfo(
                   /*packageName=*/"com.android.google.package",
@@ -605,6 +631,121 @@
                                                     mMockUidStatsCollector, nullptr));
 }
 
+TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithPrevBootStats) {
+    std::vector<UserPackageIoUsageStats> todayIoUsageStats =
+            {constructUserPackageIoUsageStats(
+                     /*userId=*/11, "com.android.google.package",
+                     /*writtenBytes=*/constructPerStateBytes(100'000, 85'000, 120'000),
+                     /*forgivenWriteBytes=*/constructPerStateBytes(70'000, 60'000, 100'000),
+                     /*totalOveruses=*/3),
+             constructUserPackageIoUsageStats(
+                     /*userId=*/12, "com.android.kitchensink",
+                     /*writtenBytes=*/constructPerStateBytes(50'000, 40'000, 35'000),
+                     /*forgivenWriteBytes=*/constructPerStateBytes(30'000, 30'000, 30'000),
+                     /*totalOveruses=*/6)};
+    EXPECT_CALL(*mMockWatchdogServiceHelper, getTodayIoUsageStats(_))
+            .WillOnce(DoAll(SetArgPointee<0>(todayIoUsageStats), Return(Status::ok())));
+
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{1001000, {/*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000}},
+                                       {1112345, {/*fgWrBytes=*/35'000, /*bgWrBytes=*/15'000}}})));
+
+    std::vector<PackageIoOveruseStats> actualIoOveruseStats;
+    EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
+            .WillOnce(DoAll(SaveArg<0>(&actualIoOveruseStats), Return(Status::ok())));
+
+    time_t currentTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+    const auto [startTime, durationInSeconds] = calculateStartAndDuration(currentTime);
+
+    ASSERT_RESULT_OK(mIoOveruseMonitor->onPeriodicCollection(currentTime, SystemState::NORMAL_MODE,
+                                                             mMockUidStatsCollector, nullptr));
+
+    std::vector<PackageIoOveruseStats> expectedIoOveruseStats =
+            {constructPackageIoOveruseStats(
+                     /*uid*=*/1001000, /*shouldNotify=*/false, /*isKillable=*/false,
+                     /*remaining=*/constructPerStateBytes(10'000, 20'000, 100'000),
+                     /*written=*/constructPerStateBytes(70'000, 20'000, 0),
+                     /*totalOveruses=*/0, startTime, durationInSeconds),
+             constructPackageIoOveruseStats(
+                     /*uid*=*/1112345, /*shouldNotify=*/true, /*isKillable=*/true,
+                     /*remaining=*/constructPerStateBytes(5'000, 0, 80'000),
+                     /*written=*/constructPerStateBytes(135'000, 100'000, 120'000),
+                     /*totalOveruses=*/4, startTime, durationInSeconds)};
+    EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
+            << "Expected: " << toString(expectedIoOveruseStats)
+            << "\nActual: " << toString(actualIoOveruseStats);
+
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{1112345, {/*fgWrBytes=*/70'000, /*bgWrBytes=*/40'000}},
+                                       {1245678, {/*fgWrBytes=*/30'000, /*bgWrBytes=*/10'000}}})));
+
+    actualIoOveruseStats.clear();
+    EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
+            .WillOnce(DoAll(SaveArg<0>(&actualIoOveruseStats), Return(Status::ok())));
+
+    ASSERT_RESULT_OK(mIoOveruseMonitor->onPeriodicCollection(currentTime, SystemState::GARAGE_MODE,
+                                                             mMockUidStatsCollector, nullptr));
+
+    expectedIoOveruseStats = {constructPackageIoOveruseStats(
+                                      /*uid*=*/1112345, /*shouldNotify=*/true, /*isKillable=*/true,
+                                      /*remaining=*/constructPerStateBytes(70'000, 30'000, 0),
+                                      /*written=*/constructPerStateBytes(135'000, 100'000, 230'000),
+                                      /*totalOveruses=*/5, startTime, durationInSeconds),
+                              constructPackageIoOveruseStats(
+                                      /*uid*=*/1245678, /*shouldNotify=*/true, /*isKillable=*/true,
+                                      /*remaining=*/constructPerStateBytes(10'000, 5'000, 0),
+                                      /*written=*/constructPerStateBytes(50'000, 40'000, 75'000),
+                                      /*totalOveruses=*/7, startTime, durationInSeconds)};
+    EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
+            << "Expected: " << toString(expectedIoOveruseStats)
+            << "\nActual: " << toString(actualIoOveruseStats);
+}
+
+TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithErrorFetchingPrevBootStats) {
+    EXPECT_CALL(*mMockWatchdogServiceHelper, getTodayIoUsageStats(_))
+            .WillOnce(Return(Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Illegal state")));
+
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{1112345, {/*fgWrBytes=*/15'000, /*bgWrBytes=*/15'000}}})));
+
+    time_t currentTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+    const auto [startTime, durationInSeconds] = calculateStartAndDuration(currentTime);
+
+    ASSERT_RESULT_OK(mIoOveruseMonitor->onPeriodicCollection(currentTime, SystemState::NORMAL_MODE,
+                                                             mMockUidStatsCollector, nullptr));
+
+    std::vector<UserPackageIoUsageStats> todayIoUsageStats = {constructUserPackageIoUsageStats(
+            /*userId=*/11, "com.android.google.package",
+            /*writtenBytes=*/constructPerStateBytes(100'000, 85'000, 120'000),
+            /*forgivenWriteBytes=*/constructPerStateBytes(70'000, 60'000, 100'000),
+            /*totalOveruses=*/3)};
+    EXPECT_CALL(*mMockWatchdogServiceHelper, getTodayIoUsageStats(_))
+            .WillOnce(DoAll(SetArgPointee<0>(todayIoUsageStats), Return(Status::ok())));
+
+    std::vector<PackageIoOveruseStats> actualIoOveruseStats;
+    EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
+            .WillOnce(DoAll(SaveArg<0>(&actualIoOveruseStats), Return(Status::ok())));
+
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{1112345, {/*fgWrBytes=*/20'000, /*bgWrBytes=*/40'000}}})));
+
+    ASSERT_RESULT_OK(mIoOveruseMonitor->onPeriodicCollection(currentTime, SystemState::NORMAL_MODE,
+                                                             mMockUidStatsCollector, nullptr));
+
+    std::vector<PackageIoOveruseStats> expectedIoOveruseStats = {constructPackageIoOveruseStats(
+            /*uid*=*/1112345, /*shouldNotify=*/true, /*isKillable=*/true,
+            /*remaining=*/constructPerStateBytes(5'000, 0, 80'000),
+            /*written=*/constructPerStateBytes(135'000, 140'000, 120'000),
+            /*totalOveruses=*/4, startTime, durationInSeconds)};
+    EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
+            << "Expected: " << toString(expectedIoOveruseStats)
+            << "\nActual: " << toString(actualIoOveruseStats);
+}
+
 TEST_F(IoOveruseMonitorTest, TestOnPeriodicMonitor) {
     IIoOveruseConfigs::IoOveruseAlertThresholdSet alertThresholds =
             {toIoOveruseAlertThreshold(
diff --git a/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h b/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h
index d484463..07997da 100644
--- a/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h
+++ b/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h
@@ -53,6 +53,9 @@
             (override));
     MOCK_METHOD(android::binder::Status, resetResourceOveruseStats,
                 (const std::vector<std::string>&), (override));
+    MOCK_METHOD(android::binder::Status, getTodayIoUsageStats,
+                (std::vector<android::automotive::watchdog::internal::UserPackageIoUsageStats>*),
+                (override));
 
 private:
     android::sp<MockBinder> mBinder;
diff --git a/cpp/watchdog/server/tests/MockWatchdogServiceHelper.h b/cpp/watchdog/server/tests/MockWatchdogServiceHelper.h
index 3d71345..171d48c 100644
--- a/cpp/watchdog/server/tests/MockWatchdogServiceHelper.h
+++ b/cpp/watchdog/server/tests/MockWatchdogServiceHelper.h
@@ -61,6 +61,9 @@
             (override));
     MOCK_METHOD(android::binder::Status, resetResourceOveruseStats,
                 (const std::vector<std::string>&), (override));
+    MOCK_METHOD(android::binder::Status, getTodayIoUsageStats,
+                (std::vector<android::automotive::watchdog::internal::UserPackageIoUsageStats>*),
+                (override));
     MOCK_METHOD(void, terminate, (), (override));
 };
 
diff --git a/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp b/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
index de05300..1d2f570 100644
--- a/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
@@ -28,6 +28,8 @@
 namespace automotive {
 namespace watchdog {
 
+namespace {
+
 namespace aawi = ::android::automotive::watchdog::internal;
 
 using aawi::ApplicationCategoryType;
@@ -36,6 +38,7 @@
 using aawi::PackageInfo;
 using aawi::PackageIoOveruseStats;
 using aawi::UidType;
+using aawi::UserPackageIoUsageStats;
 using ::android::IBinder;
 using ::android::RefBase;
 using ::android::sp;
@@ -49,6 +52,23 @@
 using ::testing::SetArgPointee;
 using ::testing::UnorderedElementsAreArray;
 
+UserPackageIoUsageStats sampleUserPackageIoUsageStats(userid_t userId,
+                                                      const std::string& packageName) {
+    UserPackageIoUsageStats stats;
+    stats.userId = userId;
+    stats.packageName = packageName;
+    stats.ioUsageStats.writtenBytes.foregroundBytes = 100;
+    stats.ioUsageStats.writtenBytes.backgroundBytes = 200;
+    stats.ioUsageStats.writtenBytes.garageModeBytes = 300;
+    stats.ioUsageStats.forgivenWriteBytes.foregroundBytes = 1100;
+    stats.ioUsageStats.forgivenWriteBytes.backgroundBytes = 1200;
+    stats.ioUsageStats.forgivenWriteBytes.garageModeBytes = 1300;
+    stats.ioUsageStats.totalOveruses = 10;
+    return stats;
+}
+
+}  // namespace
+
 namespace internal {
 
 class WatchdogServiceHelperPeer : public RefBase {
@@ -439,6 +459,50 @@
                                    "service API returns error";
 }
 
+TEST_F(WatchdogServiceHelperTest, TestGetTodayIoUsageStats) {
+    std::vector<UserPackageIoUsageStats>
+            expectedStats{sampleUserPackageIoUsageStats(10, "vendor.package"),
+                          sampleUserPackageIoUsageStats(11, "third_party.package")};
+
+    registerCarWatchdogService();
+
+    EXPECT_CALL(*mMockCarWatchdogServiceForSystem, getTodayIoUsageStats(_))
+            .WillOnce(DoAll(SetArgPointee<0>(expectedStats), Return(Status::ok())));
+
+    std::vector<UserPackageIoUsageStats> actualStats;
+    Status status = mWatchdogServiceHelper->getTodayIoUsageStats(&actualStats);
+
+    ASSERT_TRUE(status.isOk()) << status;
+    EXPECT_THAT(actualStats, UnorderedElementsAreArray(expectedStats));
+}
+
+TEST_F(WatchdogServiceHelperTest,
+       TestErrorOnGetTodayIoUsageStatsWithNoCarWatchdogServiceRegistered) {
+    EXPECT_CALL(*mMockCarWatchdogServiceForSystem, getTodayIoUsageStats(_)).Times(0);
+
+    std::vector<UserPackageIoUsageStats> actualStats;
+    Status status = mWatchdogServiceHelper->getTodayIoUsageStats(&actualStats);
+
+    ASSERT_FALSE(status.isOk()) << "getTodayIoUsageStats should fail when no "
+                                   "car watchdog service registered with the helper";
+    EXPECT_THAT(actualStats, IsEmpty());
+}
+
+TEST_F(WatchdogServiceHelperTest,
+       TestErrorOnGetTodayIoUsageStatsWithErrorStatusFromCarWatchdogService) {
+    registerCarWatchdogService();
+
+    EXPECT_CALL(*mMockCarWatchdogServiceForSystem, getTodayIoUsageStats(_))
+            .WillOnce(Return(Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Illegal state")));
+
+    std::vector<UserPackageIoUsageStats> actualStats;
+    Status status = mWatchdogServiceHelper->getTodayIoUsageStats(&actualStats);
+
+    ASSERT_FALSE(status.isOk()) << "getTodayIoUsageStats should fail when car watchdog "
+                                   "service API returns error";
+    ASSERT_TRUE(actualStats.empty());
+}
+
 }  // namespace watchdog
 }  // namespace automotive
 }  // namespace android
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 360a559..6353958 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -28,6 +28,7 @@
 import android.automotive.watchdog.internal.PackageIoOveruseStats;
 import android.automotive.watchdog.internal.PowerCycle;
 import android.automotive.watchdog.internal.StateType;
+import android.automotive.watchdog.internal.UserPackageIoUsageStats;
 import android.automotive.watchdog.internal.UserState;
 import android.car.Car;
 import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
@@ -626,5 +627,15 @@
             }
             service.mWatchdogPerfHandler.resetResourceOveruseStats(new ArraySet<>(packageNames));
         }
+
+        @Override
+        public List<UserPackageIoUsageStats> getTodayIoUsageStats() {
+            CarWatchdogService service = mService.get();
+            if (service == null) {
+                Slogf.w(TAG, "CarWatchdogService is not available");
+                return null;
+            }
+            return service.mWatchdogPerfHandler.getTodayIoUsageStats();
+        }
     }
 }
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index 0ab0a2b..08f7265 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -45,12 +45,14 @@
 import android.automotive.watchdog.ResourceType;
 import android.automotive.watchdog.internal.ApplicationCategoryType;
 import android.automotive.watchdog.internal.ComponentType;
+import android.automotive.watchdog.internal.IoUsageStats;
 import android.automotive.watchdog.internal.PackageIdentifier;
 import android.automotive.watchdog.internal.PackageIoOveruseStats;
 import android.automotive.watchdog.internal.PackageMetadata;
 import android.automotive.watchdog.internal.PackageResourceOveruseAction;
 import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
 import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
+import android.automotive.watchdog.internal.UserPackageIoUsageStats;
 import android.car.drivingstate.CarUxRestrictions;
 import android.car.drivingstate.ICarUxRestrictionsChangeListener;
 import android.car.watchdog.CarWatchdogManager;
@@ -733,6 +735,26 @@
         }
     }
 
+    /** Returns today's I/O usage stats for all packages collected during the previous boot. */
+    public List<UserPackageIoUsageStats> getTodayIoUsageStats() {
+        List<UserPackageIoUsageStats> userPackageIoUsageStats = new ArrayList<>();
+        List<WatchdogStorage.IoUsageStatsEntry> entries = mWatchdogStorage.getTodayIoUsageStats();
+        for (int i = 0; i < entries.size(); ++i) {
+            WatchdogStorage.IoUsageStatsEntry entry = entries.get(i);
+            UserPackageIoUsageStats stats = new UserPackageIoUsageStats();
+            stats.userId = entry.userId;
+            stats.packageName = entry.packageName;
+            stats.ioUsageStats = new IoUsageStats();
+            android.automotive.watchdog.IoOveruseStats internalIoUsage =
+                    entry.ioUsage.getInternalIoOveruseStats();
+            stats.ioUsageStats.writtenBytes = internalIoUsage.writtenBytes;
+            stats.ioUsageStats.forgivenWriteBytes = entry.ioUsage.getForgivenWriteBytes();
+            stats.ioUsageStats.totalOveruses = internalIoUsage.totalOveruses;
+            userPackageIoUsageStats.add(stats);
+        }
+        return userPackageIoUsageStats;
+    }
+
     /** Deletes all data for specific user. */
     public void deleteUser(@UserIdInt int userId) {
         synchronized (mLock) {
diff --git a/service/src/com/android/car/watchdog/WatchdogStorage.java b/service/src/com/android/car/watchdog/WatchdogStorage.java
index 42f6313..5b0ebb5 100644
--- a/service/src/com/android/car/watchdog/WatchdogStorage.java
+++ b/service/src/com/android/car/watchdog/WatchdogStorage.java
@@ -36,6 +36,7 @@
 import android.util.Slog;
 
 import com.android.car.CarLog;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.utils.Slogf;
 
@@ -67,6 +68,11 @@
     private final ArrayMap<String, UserPackage> mUserPackagesByKey = new ArrayMap<>();
     private final ArrayMap<String, UserPackage> mUserPackagesById = new ArrayMap<>();
     private TimeSourceInterface mTimeSource = SYSTEM_INSTANCE;
+    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);
@@ -136,27 +142,33 @@
 
     /** Returns the saved I/O usage stats for the current day. */
     public List<IoUsageStatsEntry> getTodayIoUsageStats() {
-        ZonedDateTime statsDate =
-                mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
-        long startEpochSeconds = statsDate.toEpochSecond();
-        long endEpochSeconds = mTimeSource.now().atZone(ZONE_OFFSET).toEpochSecond();
-        ArrayMap<String, WatchdogPerfHandler.PackageIoUsage> ioUsagesById;
-        try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) {
-            ioUsagesById = IoUsageStatsTable.queryStats(db, startEpochSeconds, endEpochSeconds);
-        }
-        List<IoUsageStatsEntry> entries = new ArrayList<>();
-        for (int i = 0; i < ioUsagesById.size(); ++i) {
-            String id = ioUsagesById.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;
+        synchronized (mLock) {
+            if (!mTodayIoUsageStatsEntries.isEmpty()) {
+                return new ArrayList<>(mTodayIoUsageStatsEntries);
             }
-            entries.add(new IoUsageStatsEntry(userPackage.getUserId(), userPackage.getPackageName(),
-                    ioUsagesById.valueAt(i)));
+            ZonedDateTime statsDate =
+                    mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
+            long startEpochSeconds = statsDate.toEpochSecond();
+            long endEpochSeconds = mTimeSource.now().atZone(ZONE_OFFSET).toEpochSecond();
+            ArrayMap<String, WatchdogPerfHandler.PackageIoUsage> ioUsagesById;
+            try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) {
+                ioUsagesById = IoUsageStatsTable.queryStats(db, startEpochSeconds, endEpochSeconds);
+            }
+            for (int i = 0; i < ioUsagesById.size(); ++i) {
+                String id = ioUsagesById.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;
+                }
+                mTodayIoUsageStatsEntries.add(new IoUsageStatsEntry(
+                        userPackage.getUserId(), userPackage.getPackageName(),
+                        ioUsagesById.valueAt(i)));
+            }
+            return new ArrayList<>(mTodayIoUsageStatsEntries);
         }
-        return entries;
     }
 
     /** Deletes user package settings and resource overuse stats. */
diff --git a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
index 99bbc4c..80cab06 100644
--- a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
@@ -38,6 +38,7 @@
 import android.automotive.watchdog.internal.PowerCycle;
 import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
 import android.automotive.watchdog.internal.StateType;
+import android.automotive.watchdog.internal.UserPackageIoUsageStats;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -273,5 +274,10 @@
 
         @Override
         public void resetResourceOveruseStats(List<String> packageNames) {}
+
+        @Override
+        public List<UserPackageIoUsageStats> getTodayIoUsageStats() {
+            return new ArrayList<>();
+        }
     }
 }
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 9a689e7..b048b19 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
@@ -55,6 +55,7 @@
 import android.automotive.watchdog.internal.GarageMode;
 import android.automotive.watchdog.internal.ICarWatchdog;
 import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
+import android.automotive.watchdog.internal.IoUsageStats;
 import android.automotive.watchdog.internal.PackageIdentifier;
 import android.automotive.watchdog.internal.PackageInfo;
 import android.automotive.watchdog.internal.PackageIoOveruseStats;
@@ -64,6 +65,7 @@
 import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
 import android.automotive.watchdog.internal.StateType;
 import android.automotive.watchdog.internal.UidType;
+import android.automotive.watchdog.internal.UserPackageIoUsageStats;
 import android.car.drivingstate.CarUxRestrictions;
 import android.car.drivingstate.ICarUxRestrictionsChangeListener;
 import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
@@ -1906,6 +1908,42 @@
     }
 
     @Test
+    public void testGetTodayIoUsageStats() throws Exception {
+        List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = Arrays.asList(
+                WatchdogStorageUnitTest.constructIoUsageStatsEntry(
+                        /* userId= */ 10, "system_package", /* startTime */ 0, /* duration= */ 1234,
+                        /* remainingWriteBytes= */ constructPerStateBytes(200, 300, 400),
+                        /* writtenBytes= */ constructPerStateBytes(1000, 2000, 3000),
+                        /* forgivenWriteBytes= */ constructPerStateBytes(100, 100, 100),
+                        /* totalOveruses= */ 2, /* totalTimesKilled= */ 1),
+                WatchdogStorageUnitTest.constructIoUsageStatsEntry(
+                        /* userId= */ 11, "vendor_package", /* startTime */ 0, /* duration= */ 1234,
+                        /* remainingWriteBytes= */ constructPerStateBytes(500, 600, 700),
+                        /* writtenBytes= */ constructPerStateBytes(1100, 2300, 4300),
+                        /* forgivenWriteBytes= */ constructPerStateBytes(100, 100, 100),
+                        /* totalOveruses= */ 4, /* totalTimesKilled= */ 10));
+        when(mMockWatchdogStorage.getTodayIoUsageStats()).thenReturn(ioUsageStatsEntries);
+
+        List<UserPackageIoUsageStats> actualStats =
+                mWatchdogServiceForSystemImpl.getTodayIoUsageStats();
+
+        List<UserPackageIoUsageStats> expectedStats = Arrays.asList(
+                constructUserPackageIoUsageStats(/* userId= */ 10, "system_package",
+                        /* writtenBytes= */ constructPerStateBytes(1000, 2000, 3000),
+                        /* forgivenWriteBytes= */ constructPerStateBytes(100, 100, 100),
+                        /* totalOveruses= */ 2),
+                constructUserPackageIoUsageStats(/* userId= */ 11, "vendor_package",
+                        /* writtenBytes= */ constructPerStateBytes(1100, 2300, 4300),
+                        /* forgivenWriteBytes= */ constructPerStateBytes(100, 100, 100),
+                        /* totalOveruses= */ 4));
+
+        assertThat(actualStats).comparingElementsUsing(Correspondence.from(
+                CarWatchdogServiceUnitTest::isUserPackageIoUsageStatsEquals,
+                "is user package I/O usage stats equal to"))
+                .containsExactlyElementsIn(expectedStats);
+    }
+
+    @Test
     public void testPersistStatsOnShutdownEnter() throws Exception {
         mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 10, 11, 12);
         injectPackageInfos(Arrays.asList(
@@ -3252,6 +3290,37 @@
                 .setIoOveruseStats(ioOveruseStats).build();
     }
 
+    private static UserPackageIoUsageStats constructUserPackageIoUsageStats(
+            int userId, String packageName, android.automotive.watchdog.PerStateBytes writtenBytes,
+            android.automotive.watchdog.PerStateBytes forgivenWriteBytes, int totalOveruses) {
+        UserPackageIoUsageStats stats = new UserPackageIoUsageStats();
+        stats.userId = userId;
+        stats.packageName = packageName;
+        stats.ioUsageStats = new IoUsageStats();
+        stats.ioUsageStats.writtenBytes = writtenBytes;
+        stats.ioUsageStats.forgivenWriteBytes = forgivenWriteBytes;
+        stats.ioUsageStats.totalOveruses = totalOveruses;
+        return stats;
+    }
+
+    public static boolean isUserPackageIoUsageStatsEquals(UserPackageIoUsageStats actual,
+            UserPackageIoUsageStats expected) {
+        return actual.userId == expected.userId && actual.packageName.equals(expected.packageName)
+                && isInternalPerStateBytesEquals(
+                        actual.ioUsageStats.writtenBytes, expected.ioUsageStats.writtenBytes)
+                && isInternalPerStateBytesEquals(actual.ioUsageStats.forgivenWriteBytes,
+                        expected.ioUsageStats.forgivenWriteBytes)
+                && actual.ioUsageStats.totalOveruses == expected.ioUsageStats.totalOveruses;
+    }
+
+    public static boolean isInternalPerStateBytesEquals(
+            android.automotive.watchdog.PerStateBytes actual,
+            android.automotive.watchdog.PerStateBytes expected) {
+        return actual.foregroundBytes == expected.foregroundBytes
+                && actual.backgroundBytes == expected.backgroundBytes
+                && actual.garageModeBytes == expected.garageModeBytes;
+    }
+
     private android.automotive.watchdog.IoOveruseStats constructInternalIoOveruseStats(
             boolean killableOnOveruse,
             android.automotive.watchdog.PerStateBytes remainingWriteBytes,
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java
index d54e734..91f5875 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java
@@ -17,7 +17,6 @@
 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 android.automotive.watchdog.IoOveruseStats;
@@ -26,6 +25,7 @@
 import com.google.common.truth.Correspondence;
 import com.google.common.truth.FailureMetadata;
 import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
 
 import java.util.Arrays;
 
@@ -38,6 +38,7 @@
     private static final String NULL_ENTRY_STRING = "{NULL}";
 
     private final Iterable<WatchdogStorage.IoUsageStatsEntry> mActual;
+    private String mMessagePrefix;
 
     /* User-defined entry point. */
     public static IoUsageStatsEntrySubject assertThat(
@@ -45,6 +46,16 @@
         return assertAbout(IO_OVERUSE_STATS_ENTRY_SUBJECT_FACTORY).that(stats);
     }
 
+    public static IoUsageStatsEntrySubject assertWithMessage(
+            @Nullable Iterable<WatchdogStorage.IoUsageStatsEntry> stats,
+            String format, Object... args) {
+        IoUsageStatsEntrySubject subject =
+                Truth.assertAbout(IO_OVERUSE_STATS_ENTRY_SUBJECT_FACTORY).that(stats);
+
+        subject.mMessagePrefix = String.format(format + ": ", args);
+        return subject;
+    }
+
     public static Subject.Factory<IoUsageStatsEntrySubject,
             Iterable<WatchdogStorage.IoUsageStatsEntry>> ioUsageStatsEntries() {
         return IO_OVERUSE_STATS_ENTRY_SUBJECT_FACTORY;
@@ -55,9 +66,9 @@
     }
 
     public void containsExactlyElementsIn(Iterable<WatchdogStorage.IoUsageStatsEntry> expected) {
-        assertWithMessage("Expected stats(%s) equals to actual stats(%s)", toString(expected),
-                toString(mActual)).that(mActual)
-                .comparingElementsUsing(Correspondence.from(
+        Truth.assertWithMessage("%sExpected stats(%s) equals to actual stats(%s)",
+                mMessagePrefix != null ? mMessagePrefix : "", toString(expected),
+                toString(mActual)).that(mActual).comparingElementsUsing(Correspondence.from(
                         IoUsageStatsEntrySubject::isEquals, "is equal to"))
                 .containsExactlyElementsIn(expected);
     }
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 e2c12bb..c84225d 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
@@ -59,16 +59,17 @@
 public final class WatchdogStorageUnitTest {
     private static final String TAG = WatchdogStorageUnitTest.class.getSimpleName();
 
+    private Context mContext;
     private WatchdogStorage mService;
     private File mDatabaseFile;
     private TimeSourceInterface mTimeSource;
 
     @Before
     public void setUp() throws Exception {
-        Context context = InstrumentationRegistry.getTargetContext();
-        mDatabaseFile = context.createDeviceProtectedStorageContext()
+        mContext = InstrumentationRegistry.getTargetContext().createDeviceProtectedStorageContext();
+        mDatabaseFile = mContext.createDeviceProtectedStorageContext()
                 .getDatabasePath(DATABASE_NAME);
-        mService = new WatchdogStorage(context, /* useDataSystemCarDir= */ false);
+        mService = new WatchdogStorage(mContext, /* useDataSystemCarDir= */ false);
         setDate(/* numDaysAgo= */ 0);
     }
 
@@ -162,7 +163,7 @@
                 .toEpochSecond();
         long duration = mTimeSource.now().atZone(ZONE_OFFSET).toEpochSecond() - startTime;
 
-        List<WatchdogStorage.IoUsageStatsEntry> expected = Collections.singletonList(
+        List<WatchdogStorage.IoUsageStatsEntry> statsBeforeOverwrite = Collections.singletonList(
                 constructIoUsageStatsEntry(
                         /* userId= */ 100, "system_package.non_critical.A", startTime, duration,
                         /* remainingWriteBytes= */
@@ -174,12 +175,13 @@
                         /* totalOveruses= */ 2, /* totalTimesKilled= */ 1));
 
         assertWithMessage("Saved I/O usage stats successfully")
-                .that(mService.saveIoUsageStats(expected)).isTrue();
+                .that(mService.saveIoUsageStats(statsBeforeOverwrite)).isTrue();
 
-        IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
-                .containsExactlyElementsIn(expected);
+        IoUsageStatsEntrySubject.assertWithMessage(
+                mService.getTodayIoUsageStats(), "I/O usage stats fetched from database")
+                .containsExactlyElementsIn(statsBeforeOverwrite);
 
-        expected = Collections.singletonList(
+        List<WatchdogStorage.IoUsageStatsEntry> statsAfterOverwrite = Collections.singletonList(
                 constructIoUsageStatsEntry(
                         /* userId= */ 100, "system_package.non_critical.A", startTime, duration,
                         /* remainingWriteBytes= */
@@ -191,10 +193,22 @@
                         /* totalOveruses= */ 4, /* totalTimesKilled= */ 2));
 
         assertWithMessage("Saved I/O usage stats successfully")
-                .that(mService.saveIoUsageStats(expected)).isTrue();
+                .that(mService.saveIoUsageStats(statsAfterOverwrite)).isTrue();
 
-        IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
-                .containsExactlyElementsIn(expected);
+        IoUsageStatsEntrySubject.assertWithMessage(
+                mService.getTodayIoUsageStats(), "Cached in memory I/O usage stats")
+                .containsExactlyElementsIn(statsBeforeOverwrite);
+
+        mService.release();
+        mService = new WatchdogStorage(mContext, /* useDataSystemCarDir= */ false);
+        setDate(/* numDaysAgo= */ 0);
+
+        assertWithMessage("User packages settings").that(mService.getUserPackageSettings())
+                .isNotEmpty();
+
+        IoUsageStatsEntrySubject.assertWithMessage(mService.getTodayIoUsageStats(),
+                "I/O usage stats fetched from database after restart")
+                .containsExactlyElementsIn(statsAfterOverwrite);
     }
 
     @Test
@@ -539,7 +553,7 @@
         return entries;
     }
 
-    private static WatchdogStorage.IoUsageStatsEntry constructIoUsageStatsEntry(
+    static WatchdogStorage.IoUsageStatsEntry constructIoUsageStatsEntry(
             int userId, String packageName, long startTime, long duration,
             PerStateBytes remainingWriteBytes, PerStateBytes writtenBytes,
             PerStateBytes forgivenWriteBytes, int totalOveruses, int totalTimesKilled) {