Merge "Periodically read cpuset categories until boot complete." into main
diff --git a/services/core/java/com/android/server/cpu/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java
index 984ad1d..a68451a 100644
--- a/services/core/java/com/android/server/cpu/CpuInfoReader.java
+++ b/services/core/java/com/android/server/cpu/CpuInfoReader.java
@@ -40,6 +40,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -80,13 +81,14 @@
     /** package **/ @interface CpusetCategory{}
 
     // TODO(b/242722241): Protect updatable variables with a local lock.
-    private final File mCpusetDir;
     private final long mMinReadIntervalMillis;
     private final SparseIntArray mCpusetCategoriesByCpus = new SparseIntArray();
     private final SparseArray<File> mCpuFreqPolicyDirsById = new SparseArray<>();
     private final SparseArray<StaticPolicyInfo> mStaticPolicyInfoById = new SparseArray<>();
     private final SparseArray<LongSparseLongArray> mTimeInStateByPolicyId = new SparseArray<>();
+    private final AtomicBoolean mShouldReadCpusetCategories;
 
+    private File mCpusetDir;
     private File mCpuFreqDir;
     private File mProcStatFile;
     private SparseArray<CpuUsageStats> mCumulativeCpuUsageStats = new SparseArray<>();
@@ -106,10 +108,13 @@
         mCpuFreqDir = cpuFreqDir;
         mProcStatFile = procStatFile;
         mMinReadIntervalMillis = minReadIntervalMillis;
+        mShouldReadCpusetCategories = new AtomicBoolean(true);
     }
 
     /**
      * Initializes CpuInfoReader and returns a boolean to indicate whether the reader is enabled.
+     *
+     * <p>Returns {@code true} on success. Otherwise, returns {@code false}.
      */
     public boolean init() {
         if (mCpuFreqPolicyDirsById.size() > 0) {
@@ -139,8 +144,7 @@
             Slogf.e(TAG, "Missing proc stat file at %s", mProcStatFile.getAbsolutePath());
             return false;
         }
-        readCpusetCategories();
-        if (mCpusetCategoriesByCpus.size() == 0) {
+        if (!readCpusetCategories()) {
             Slogf.e(TAG, "Failed to read cpuset information from %s", mCpusetDir.getAbsolutePath());
             return false;
         }
@@ -163,10 +167,19 @@
         return true;
     }
 
+  public void stopPeriodicCpusetReading() {
+        mShouldReadCpusetCategories.set(false);
+        if (!readCpusetCategories()) {
+            Slogf.e(TAG, "Failed to read cpuset information from %s",
+                    mCpusetDir.getAbsolutePath());
+            mIsEnabled = false;
+        }
+    }
+
     /**
      * Reads CPU information from proc and sys fs files exposed by the Kernel.
      *
-     * @return SparseArray keyed by CPU core ID; {@code null} on error or when disabled.
+     * <p>Returns SparseArray keyed by CPU core ID; {@code null} on error or when disabled.
      */
     @Nullable
     public SparseArray<CpuInfo> readCpuInfos() {
@@ -183,6 +196,12 @@
         }
         mLastReadUptimeMillis = uptimeMillis;
         mLastReadCpuInfos = null;
+        if (mShouldReadCpusetCategories.get() && !readCpusetCategories()) {
+            Slogf.e(TAG, "Failed to read cpuset information from %s",
+                    mCpusetDir.getAbsolutePath());
+            mIsEnabled = false;
+            return null;
+        }
         SparseArray<CpuUsageStats> cpuUsageStatsByCpus = readLatestCpuUsageStats();
         if (cpuUsageStatsByCpus == null || cpuUsageStatsByCpus.size() == 0) {
             Slogf.e(TAG, "Failed to read latest CPU usage stats");
@@ -324,7 +343,7 @@
     /**
      * Sets the CPU frequency for testing.
      *
-     * <p>Return {@code true} on success. Otherwise, returns {@code false}.
+     * <p>Returns {@code true} on success. Otherwise, returns {@code false}.
      */
     @VisibleForTesting
     boolean setCpuFreqDir(File cpuFreqDir) {
@@ -354,7 +373,7 @@
     /**
      * Sets the proc stat file for testing.
      *
-     * <p>Return true on success. Otherwise, returns false.
+     * <p>Returns {@code true} on success. Otherwise, returns {@code false}.
      */
     @VisibleForTesting
     boolean setProcStatFile(File procStatFile) {
@@ -366,6 +385,21 @@
         return true;
     }
 
+    /**
+     * Set the cpuset directory for testing.
+     *
+     * <p>Returns {@code true} on success. Otherwise, returns {@code false}.
+     */
+    @VisibleForTesting
+    boolean setCpusetDir(File cpusetDir) {
+        if (!cpusetDir.exists() && !cpusetDir.isDirectory()) {
+            Slogf.e(TAG, "Missing or invalid cpuset directory at %s", cpusetDir.getAbsolutePath());
+            return false;
+        }
+        mCpusetDir = cpusetDir;
+        return true;
+    }
+
     private void populateCpuFreqPolicyDirsById(File[] policyDirs) {
         mCpuFreqPolicyDirsById.clear();
         for (int i = 0; i < policyDirs.length; i++) {
@@ -381,12 +415,27 @@
         }
     }
 
-    private void readCpusetCategories() {
+    /**
+     * Reads cpuset categories by CPU.
+     *
+     * <p>The cpusets are read from the cpuset category specific directories
+     * under the /dev/cpuset directory. The cpuset categories are subject to change at any point
+     * during system bootup, as determined by the init rules specified within the init.rc files.
+     * Therefore, it's necessary to read the cpuset categories each time before accessing CPU usage
+     * statistics until the system boot completes. Once the boot is complete, the latest changes to
+     * the cpuset categories will take a few seconds to propagate. Thus, on boot complete,
+     * the periodic reading is stopped with a delay of
+     * {@link CpuMonitorService#STOP_PERIODIC_CPUSET_READING_DELAY_MILLISECONDS}.
+     *
+     * <p>Returns {@code true} on success. Otherwise, returns {@code false}.
+     */
+    private boolean readCpusetCategories() {
         File[] cpusetDirs = mCpusetDir.listFiles(File::isDirectory);
         if (cpusetDirs == null) {
             Slogf.e(TAG, "Missing cpuset directories at %s", mCpusetDir.getAbsolutePath());
-            return;
+            return false;
         }
+        mCpusetCategoriesByCpus.clear();
         for (int i = 0; i < cpusetDirs.length; i++) {
             File dir = cpusetDirs[i];
             @CpusetCategory int cpusetCategory;
@@ -418,6 +467,7 @@
                 }
             }
         }
+        return mCpusetCategoriesByCpus.size() > 0;
     }
 
     private void readStaticPolicyInfo() {
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java
index 7ea2c1b..88ff7e4 100644
--- a/services/core/java/com/android/server/cpu/CpuMonitorService.java
+++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java
@@ -22,6 +22,7 @@
 import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND;
 import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND;
 import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP;
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -82,6 +83,15 @@
     //  frequently. Should this duration be increased as well when this happens?
     private static final long LATEST_AVAILABILITY_DURATION_MILLISECONDS =
             TimeUnit.SECONDS.toMillis(30);
+    /**
+     * Delay to stop the periodic cpuset reading after boot complete.
+     *
+     * Device specific implementations can update cpuset on boot complete. This may take
+     * a few seconds to propagate. So, wait for a few minutes before stopping the periodic cpuset
+     * reading.
+     */
+    private static final long STOP_PERIODIC_CPUSET_READING_DELAY_MILLISECONDS =
+            TimeUnit.MINUTES.toMillis(2);
 
     private final Context mContext;
     private final HandlerThread mHandlerThread;
@@ -90,6 +100,7 @@
     private final long mNormalMonitoringIntervalMillis;
     private final long mDebugMonitoringIntervalMillis;
     private final long mLatestAvailabilityDurationMillis;
+    private final long mStopPeriodicCpusetReadingDelayMillis;
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private final SparseArrayMap<CpuMonitorInternal.CpuAvailabilityCallback,
@@ -153,13 +164,15 @@
         this(context, new CpuInfoReader(), new ServiceThread(TAG,
                         Process.THREAD_PRIORITY_BACKGROUND, /* allowIo= */ true),
                 Build.IS_USERDEBUG || Build.IS_ENG, NORMAL_MONITORING_INTERVAL_MILLISECONDS,
-                DEBUG_MONITORING_INTERVAL_MILLISECONDS, LATEST_AVAILABILITY_DURATION_MILLISECONDS);
+                DEBUG_MONITORING_INTERVAL_MILLISECONDS, LATEST_AVAILABILITY_DURATION_MILLISECONDS,
+                STOP_PERIODIC_CPUSET_READING_DELAY_MILLISECONDS);
     }
 
     @VisibleForTesting
     CpuMonitorService(Context context, CpuInfoReader cpuInfoReader, HandlerThread handlerThread,
             boolean shouldDebugMonitor, long normalMonitoringIntervalMillis,
-            long debugMonitoringIntervalMillis, long latestAvailabilityDurationMillis) {
+            long debugMonitoringIntervalMillis, long latestAvailabilityDurationMillis,
+            long stopPeriodicCpusetReadingDelayMillis) {
         super(context);
         mContext = context;
         mHandlerThread = handlerThread;
@@ -167,6 +180,7 @@
         mNormalMonitoringIntervalMillis = normalMonitoringIntervalMillis;
         mDebugMonitoringIntervalMillis = debugMonitoringIntervalMillis;
         mLatestAvailabilityDurationMillis = latestAvailabilityDurationMillis;
+        mStopPeriodicCpusetReadingDelayMillis = stopPeriodicCpusetReadingDelayMillis;
         mCpuInfoReader = cpuInfoReader;
         mCpusetInfosByCpuset = new SparseArray<>(2);
         mCpusetInfosByCpuset.append(CPUSET_ALL, new CpusetInfo(CPUSET_ALL));
@@ -200,6 +214,16 @@
         }
     }
 
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase != PHASE_BOOT_COMPLETED) {
+            return;
+        }
+        Slogf.i(TAG, "Stopping periodic cpuset reading on boot complete");
+        mHandler.postDelayed(() -> mCpuInfoReader.stopPeriodicCpusetReading(),
+                mStopPeriodicCpusetReadingDelayMillis);
+    }
+
     @VisibleForTesting
     long getCurrentMonitoringIntervalMillis() {
         synchronized (mLock) {
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_2/background/cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_2/background/cpus
new file mode 100644
index 0000000..8b0fab8
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_2/background/cpus
@@ -0,0 +1 @@
+0-1
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_2/top-app/cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_2/top-app/cpus
new file mode 100644
index 0000000..40c7bb2
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_2/top-app/cpus
@@ -0,0 +1 @@
+0-3
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
index 2fbe8aa..3fe038a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
@@ -26,6 +26,7 @@
 
 import android.content.Context;
 import android.content.res.AssetManager;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -48,6 +49,7 @@
     private static final String TAG = CpuInfoReaderTest.class.getSimpleName();
     private static final String ROOT_DIR_NAME = "CpuInfoReaderTest";
     private static final String VALID_CPUSET_DIR = "valid_cpuset";
+    private static final String VALID_CPUSET_2_DIR = "valid_cpuset_2";
     private static final String VALID_CPUSET_WITH_EMPTY_CPUS = "valid_cpuset_with_empty_cpus";
     private static final String VALID_CPUFREQ_WITH_EMPTY_AFFECTED_CPUS =
             "valid_cpufreq_with_empty_affected_cpus";
@@ -88,54 +90,95 @@
     }
 
     @Test
+    public void testReadCpuInfoWithUpdatedCpuset() throws Exception {
+        CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+                getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(VALID_PROC_STAT));
+
+        SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos =
+                getFirstCpuInfosWithTimeInStateSnapshot();
+
+        compareCpuInfos("CPU infos first snapshot", expectedCpuInfos, actualCpuInfos);
+
+        cpuInfoReader.setCpusetDir(getCacheFile(VALID_CPUSET_2_DIR));
+        cpuInfoReader.setCpuFreqDir(getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_2_DIR));
+        cpuInfoReader.setProcStatFile(getCacheFile(VALID_PROC_STAT_2));
+
+        actualCpuInfos = cpuInfoReader.readCpuInfos();
+
+        IntArray cpusetCategories = new IntArray();
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP);
+        expectedCpuInfos = getSecondCpuInfosWithTimeInStateSnapshot(cpusetCategories);
+
+        compareCpuInfos("CPU infos second snapshot", expectedCpuInfos, actualCpuInfos);
+    }
+
+    @Test
+    public void testReadCpuInfoWithUpdatedCpusetBeforeStopSignal() throws Exception {
+        CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+                getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(VALID_PROC_STAT));
+
+        SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos =
+                getFirstCpuInfosWithTimeInStateSnapshot();
+
+        compareCpuInfos("CPU infos first snapshot", expectedCpuInfos, actualCpuInfos);
+
+        cpuInfoReader.setCpusetDir(getCacheFile(VALID_CPUSET_2_DIR));
+        // When stopping the periodic cpuset reading, the reader will create a new snapshot.
+        cpuInfoReader.stopPeriodicCpusetReading();
+        // Any cpuset update after the stop signal should be ignored by the reader.
+        cpuInfoReader.setCpusetDir(getCacheFile(VALID_CPUSET_DIR));
+        cpuInfoReader.setCpuFreqDir(getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_2_DIR));
+        cpuInfoReader.setProcStatFile(getCacheFile(VALID_PROC_STAT_2));
+
+        actualCpuInfos = cpuInfoReader.readCpuInfos();
+
+        IntArray cpusetCategories = new IntArray();
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP);
+        expectedCpuInfos = getSecondCpuInfosWithTimeInStateSnapshot(cpusetCategories);
+
+        compareCpuInfos("CPU infos second snapshot", expectedCpuInfos, actualCpuInfos);
+    }
+
+
+    @Test
+    public void testReadCpuInfoWithUpdatedCpusetAfterStopSignal() throws Exception {
+        CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+                getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(VALID_PROC_STAT));
+
+        SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos =
+                getFirstCpuInfosWithTimeInStateSnapshot();
+
+        compareCpuInfos("CPU infos first snapshot", expectedCpuInfos, actualCpuInfos);
+
+        cpuInfoReader.stopPeriodicCpusetReading();
+        // Any cpuset update after the stop signal should be ignored by the reader.
+        cpuInfoReader.setCpusetDir(getCacheFile(VALID_CPUSET_2_DIR));
+        cpuInfoReader.setCpuFreqDir(getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_2_DIR));
+        cpuInfoReader.setProcStatFile(getCacheFile(VALID_PROC_STAT_2));
+
+        actualCpuInfos = cpuInfoReader.readCpuInfos();
+
+        expectedCpuInfos = getSecondCpuInfosWithTimeInStateSnapshot();
+        compareCpuInfos("CPU infos second snapshot", expectedCpuInfos, actualCpuInfos);
+    }
+
+    @Test
     public void testReadCpuInfoWithTimeInState() throws Exception {
         CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
                 getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(VALID_PROC_STAT));
 
         SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
-        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
-        expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
-                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
-                /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095,
-                /* normalizedAvailableCpuFreqKHz= */ 2_402_267,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
-                        /* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
-                        /* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
-                        /* irqTimeMillis= */ 8_146_740, /* softirqTimeMillis= */ 428_970,
-                        /* stealTimeMillis= */ 81_950, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
-                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
-                /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
-                /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
-                        /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
-                        /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
-                        /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
-                        /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
-                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
-                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
-                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
-                /* normalizedAvailableCpuFreqKHz= */ 1_901_608,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
-                        /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
-                        /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
-                        /* irqTimeMillis= */ 14_796_940, /* softirqTimeMillis= */ 1_478_130,
-                        /* stealTimeMillis= */ 88_780, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
-                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
-                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
-                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
-                /* normalizedAvailableCpuFreqKHz= */ 1_907_125,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
-                        /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
-                        /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
-                        /* irqTimeMillis= */ 8_136_740, /* softirqTimeMillis= */ 438_970,
-                        /* stealTimeMillis= */ 71_950, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos =
+                getFirstCpuInfosWithTimeInStateSnapshot();
 
         compareCpuInfos("CPU infos first snapshot", expectedCpuInfos, actualCpuInfos);
 
@@ -144,49 +187,7 @@
 
         actualCpuInfos = cpuInfoReader.readCpuInfos();
 
-        expectedCpuInfos.clear();
-        expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
-                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
-                /* maxCpuFreqKHz= */ 2_600_000, /* avgTimeInStateCpuFreqKHz= */ 419_354,
-                /* normalizedAvailableCpuFreqKHz= */ 2_525_919,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
-                        /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
-                        /* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000,
-                        /* irqTimeMillis= */ 1_400_000, /* softirqTimeMillis= */ 80_000,
-                        /* stealTimeMillis= */ 21_000, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
-                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000,
-                /* maxCpuFreqKHz= */ 2_900_000, /* avgTimeInStateCpuFreqKHz= */ 429_032,
-                /* normalizedAvailableCpuFreqKHz= */ 2_503_009,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000,
-                        /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
-                        /* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000,
-                        /* irqTimeMillis= */ 200_000, /* softirqTimeMillis= */ 100_000,
-                        /* stealTimeMillis= */ 100_000, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
-                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
-                /* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
-                /* maxCpuFreqKHz= */ 2_100_000, /* avgTimeInStateCpuFreqKHz= */ 403_225,
-                /* normalizedAvailableCpuFreqKHz= */ 1_788_209,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
-                        /* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0,
-                        /* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000,
-                        /* irqTimeMillis= */ 20_000_000, /* softirqTimeMillis= */ 1_000_000,
-                        /* stealTimeMillis= */ 100_000, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
-                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
-                /* isOnline= */ false, /* curCpuFreqKHz= */ MISSING_FREQUENCY,
-                /* maxCpuFreqKHz= */ 2_100_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
-                /* normalizedAvailableCpuFreqKHz= */ MISSING_FREQUENCY,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000,
-                        /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000,
-                        /* idleTimeMillis= */ 100_000, /* iowaitTimeMillis= */ 100_000,
-                        /* irqTimeMillis= */ 100_000, /* softirqTimeMillis= */ 1_000_000,
-                        /* stealTimeMillis= */ 1_000, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
+        expectedCpuInfos = getSecondCpuInfosWithTimeInStateSnapshot();
 
         compareCpuInfos("CPU infos second snapshot", expectedCpuInfos, actualCpuInfos);
     }
@@ -592,4 +593,108 @@
         }
         return rootDir.delete();
     }
+
+    private SparseArray<CpuInfoReader.CpuInfo> getFirstCpuInfosWithTimeInStateSnapshot() {
+        SparseArray<CpuInfoReader.CpuInfo> cpuInfos = new SparseArray<>();
+        cpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0, FLAG_CPUSET_CATEGORY_TOP_APP,
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
+                /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095,
+                /* normalizedAvailableCpuFreqKHz= */ 2_402_267,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
+                        /* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
+                        /* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
+                        /* irqTimeMillis= */ 8_146_740, /* softirqTimeMillis= */ 428_970,
+                        /* stealTimeMillis= */ 81_950, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        cpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1, FLAG_CPUSET_CATEGORY_TOP_APP,
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
+                /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+                /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
+                        /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
+                        /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
+                        /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
+                        /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        cpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
+                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
+                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+                /* normalizedAvailableCpuFreqKHz= */ 1_901_608,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
+                        /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
+                        /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
+                        /* irqTimeMillis= */ 14_796_940, /* softirqTimeMillis= */ 1_478_130,
+                        /* stealTimeMillis= */ 88_780, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        cpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
+                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
+                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+                /* normalizedAvailableCpuFreqKHz= */ 1_907_125,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
+                        /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
+                        /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
+                        /* irqTimeMillis= */ 8_136_740, /* softirqTimeMillis= */ 438_970,
+                        /* stealTimeMillis= */ 71_950, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        return cpuInfos;
+    }
+
+    private SparseArray<CpuInfoReader.CpuInfo> getSecondCpuInfosWithTimeInStateSnapshot() {
+        IntArray cpusetCategories = new IntArray();
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND);
+        cpusetCategories.add(FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND);
+        return getSecondCpuInfosWithTimeInStateSnapshot(cpusetCategories);
+    }
+
+    private SparseArray<CpuInfoReader.CpuInfo> getSecondCpuInfosWithTimeInStateSnapshot(
+        IntArray cpusetCategories) {
+        SparseArray<CpuInfoReader.CpuInfo> cpuInfos = new SparseArray<>();
+        cpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0, cpusetCategories.get(0),
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
+                /* maxCpuFreqKHz= */ 2_600_000, /* avgTimeInStateCpuFreqKHz= */ 419_354,
+                /* normalizedAvailableCpuFreqKHz= */ 2_525_919,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
+                        /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
+                        /* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000,
+                        /* irqTimeMillis= */ 1_400_000, /* softirqTimeMillis= */ 80_000,
+                        /* stealTimeMillis= */ 21_000, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        cpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1, cpusetCategories.get(1),
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000,
+                /* maxCpuFreqKHz= */ 2_900_000, /* avgTimeInStateCpuFreqKHz= */ 429_032,
+                /* normalizedAvailableCpuFreqKHz= */ 2_503_009,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000,
+                        /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
+                        /* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000,
+                        /* irqTimeMillis= */ 200_000, /* softirqTimeMillis= */ 100_000,
+                        /* stealTimeMillis= */ 100_000, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        cpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2, cpusetCategories.get(2),
+                /* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
+                /* maxCpuFreqKHz= */ 2_100_000, /* avgTimeInStateCpuFreqKHz= */ 403_225,
+                /* normalizedAvailableCpuFreqKHz= */ 1_788_209,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
+                        /* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0,
+                        /* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000,
+                        /* irqTimeMillis= */ 20_000_000, /* softirqTimeMillis= */ 1_000_000,
+                        /* stealTimeMillis= */ 100_000, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        cpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3, cpusetCategories.get(3),
+                /* isOnline= */ false,
+                /* curCpuFreqKHz= */ MISSING_FREQUENCY, /* maxCpuFreqKHz= */ 2_100_000,
+                /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                /* normalizedAvailableCpuFreqKHz= */ MISSING_FREQUENCY,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000,
+                        /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000,
+                        /* idleTimeMillis= */ 100_000, /* iowaitTimeMillis= */ 100_000,
+                        /* irqTimeMillis= */ 100_000, /* softirqTimeMillis= */ 1_000_000,
+                        /* stealTimeMillis= */ 1_000, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        return cpuInfos;
+    }
+
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
index 994313f..d9e09d8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
@@ -26,6 +26,7 @@
 import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND;
 import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP;
 import static com.android.server.cpu.CpuMonitorService.DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -75,6 +76,7 @@
     private static final long TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS = 100;
     private static final long TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS = 150;
     private static final long TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS = 300;
+    private static final long TEST_STOP_PERIODIC_CPUSET_READING_DELAY_MILLISECONDS = 0;
     private static final CpuAvailabilityMonitoringConfig TEST_MONITORING_CONFIG_ALL_CPUSET =
             new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
                     .addThreshold(30).addThreshold(70).build();
@@ -119,7 +121,8 @@
         mService = new CpuMonitorService(mMockContext, mMockCpuInfoReader, mServiceHandlerThread,
                 /* shouldDebugMonitor= */ true, TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS,
                 TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS,
-                TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS);
+                TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS,
+                TEST_STOP_PERIODIC_CPUSET_READING_DELAY_MILLISECONDS);
 
         doNothing().when(() -> ServiceManager.addService(eq("cpu_monitor"), any(Binder.class),
                 anyBoolean(), anyInt()));
@@ -535,6 +538,18 @@
     }
 
     @Test
+    public void testBootCompleted() throws Exception {
+        mService.onBootPhase(PHASE_BOOT_COMPLETED);
+
+        // Message to stop periodic cpuset reading is posted on the service handler thread. Sync
+        // with this thread before proceeding.
+        syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
+
+        verify(mMockCpuInfoReader, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
+                .stopPeriodicCpusetReading();
+    }
+
+    @Test
     public void testHeavyCpuLoadMonitoring() throws Exception {
         // TODO(b/267500110): Once heavy CPU load detection logic is added, add unittest.
     }
@@ -567,7 +582,8 @@
                 mServiceHandlerThread, /* shouldDebugMonitor= */ false,
                 TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS,
                 TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS,
-                TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS);
+                TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS,
+                TEST_STOP_PERIODIC_CPUSET_READING_DELAY_MILLISECONDS);
 
         startService();
     }