Periodically read cpuset categories until boot complete.

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. After boot complete, an external service may still cause
changes, which take a few seconds to propagate. Therefore, a delayed
task is initiated on boot completion to stop periodic reading and
ensure synchronization with the latest cpuset category updates.

Flag: EXEMPT fixing a minor bug
Change-Id: I260aab57c68fb1fa59bc1174613ce314d75baf7d
Test: atest CpuInfoReaderTest CpuMonitorServiceTest
Bug: 355099583
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();
     }