Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5c19b897 authored by Sudheer Shanka's avatar Sudheer Shanka
Browse files

Add a global setting to turn on/off the proc state cpu times tracking.

Bug: 66953194
Test: atest core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
Test: atest hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
Test: atest core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
Change-Id: Id26476ad77c95994f358d8bd59b6c2e6513c4c54
parent 65e919ae
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -9781,6 +9781,22 @@ public final class Settings {
         */
        public static final String TEXT_CLASSIFIER_CONSTANTS = "text_classifier_constants";

        /**
         * BatteryStats specific settings.
         * This is encoded as a key=value list, separated by commas. Ex: "foo=1,bar=true"
         *
         * The following keys are supported:
         * <pre>
         * track_cpu_times_by_proc_state (boolean)
         * </pre>
         *
         * <p>
         * Type: string
         * @hide
         * see also com.android.internal.os.BatteryStatsImpl.Constants
         */
        public static final String BATTERY_STATS_CONSTANTS = "battery_stats_constants";

        /**
         * Whether or not App Standby feature is enabled. This controls throttling of apps
         * based on usage patterns and predictions.
+105 −1
Original line number Diff line number Diff line
@@ -21,10 +21,13 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.bluetooth.BluetoothActivityEnergyInfo;
import android.bluetooth.UidTraffic;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.NetworkStats;
import android.net.Uri;
import android.net.wifi.WifiActivityEnergyInfo;
import android.net.wifi.WifiManager;
import android.os.BatteryManager;
@@ -46,6 +49,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
import android.provider.Settings;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
import android.telephony.ServiceState;
@@ -54,6 +58,7 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.IntArray;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.LogWriter;
import android.util.LongSparseArray;
@@ -292,9 +297,18 @@ public class BatteryStatsImpl extends BatteryStats {
    public void updateProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
        final SparseIntArray uidStates;
        synchronized (BatteryStatsImpl.this) {
            if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
                return;
            }
            if(!initKernelSingleUidTimeReaderLocked()) {
                return;
            }
            // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
            // compute deltas since it might result in mis-attributing cpu times to wrong states.
            if (mKernelSingleUidTimeReader.hasStaleData()) {
                mPendingUids.clear();
                return;
            }
            if (mPendingUids.size() == 0) {
                return;
@@ -355,12 +369,23 @@ public class BatteryStatsImpl extends BatteryStats {
     */
    public void copyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
        synchronized (BatteryStatsImpl.this) {
            if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
                return;
            }
            if(!initKernelSingleUidTimeReaderLocked()) {
                return;
            }
            final SparseArray<long[]> allUidCpuFreqTimesMs =
                    mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs();
            // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
            // compute deltas since it might result in mis-attributing cpu times to wrong states.
            if (mKernelSingleUidTimeReader.hasStaleData()) {
                mKernelSingleUidTimeReader.setAllUidsCpuTimesMs(allUidCpuFreqTimesMs);
                mKernelSingleUidTimeReader.markDataAsStale(false);
                mPendingUids.clear();
                return;
            }
            for (int i = allUidCpuFreqTimesMs.size() - 1; i >= 0; --i) {
                final int uid = allUidCpuFreqTimesMs.keyAt(i);
                final Uid u = getAvailableUidStatsLocked(mapUid(uid));
@@ -450,6 +475,7 @@ public class BatteryStatsImpl extends BatteryStats {
        Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
        Future<?> scheduleReadProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff);
        Future<?> scheduleCopyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff);
        Future<?> scheduleCpuSyncDueToSettingChange();
    }
    public Handler mHandler;
@@ -812,6 +838,9 @@ public class BatteryStatsImpl extends BatteryStats {
    @VisibleForTesting
    protected PowerProfile mPowerProfile;
    @GuardedBy("this")
    private final Constants mConstants;
    /*
     * Holds a SamplingTimer associated with each Resource Power Manager state and voter,
     * recording their times when on-battery (regardless of screen state).
@@ -900,6 +929,7 @@ public class BatteryStatsImpl extends BatteryStats {
        mHandler = null;
        mPlatformIdleStateCallback = null;
        mUserInfoProvider = null;
        mConstants = new Constants(mHandler);
        clearHistoryLocked();
    }
@@ -9411,7 +9441,7 @@ public class BatteryStatsImpl extends BatteryStats {
                if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
                    mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
                    if (mBsi.mPerProcStateCpuTimesAvailable) {
                    if (mBsi.trackPerProcStateCpuTimes()) {
                        if (mBsi.mPendingUids.size() == 0) {
                            mBsi.mExternalSync.scheduleReadProcStateCpuTimes(
                                    mBsi.mOnBatteryTimeBase.isRunning(),
@@ -9765,6 +9795,7 @@ public class BatteryStatsImpl extends BatteryStats {
        mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
        mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
        mHandler = new MyHandler(handler.getLooper());
        mConstants = new Constants(mHandler);
        mStartCount++;
        mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
        mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
@@ -9860,6 +9891,7 @@ public class BatteryStatsImpl extends BatteryStats {
        mDailyFile = null;
        mHandler = null;
        mExternalSync = null;
        mConstants = new Constants(mHandler);
        clearHistoryLocked();
        readFromParcel(p);
        mPlatformIdleStateCallback = null;
@@ -12613,6 +12645,78 @@ public class BatteryStatsImpl extends BatteryStats {
        mShuttingDown = true;
    }
    public boolean trackPerProcStateCpuTimes() {
        return mConstants.TRACK_CPU_TIMES_BY_PROC_STATE && mPerProcStateCpuTimesAvailable;
    }
    public void systemServicesReady(Context context) {
        mConstants.startObserving(context.getContentResolver());
    }
    @VisibleForTesting
    public final class Constants extends ContentObserver {
        public static final String KEY_TRACK_CPU_TIMES_BY_PROC_STATE
                = "track_cpu_times_by_proc_state";
        private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = true;
        public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE;
        private ContentResolver mResolver;
        private final KeyValueListParser mParser = new KeyValueListParser(',');
        public Constants(Handler handler) {
            super(handler);
        }
        public void startObserving(ContentResolver resolver) {
            mResolver = resolver;
            mResolver.registerContentObserver(
                    Settings.Global.getUriFor(Settings.Global.BATTERY_STATS_CONSTANTS),
                    false /* notifyForDescendants */, this);
            updateConstants();
        }
        @Override
        public void onChange(boolean selfChange, Uri uri) {
            updateConstants();
        }
        private void updateConstants() {
            synchronized (BatteryStatsImpl.this) {
                try {
                    mParser.setString(Settings.Global.getString(mResolver,
                            Settings.Global.BATTERY_STATS_CONSTANTS));
                } catch (IllegalArgumentException e) {
                    // Failed to parse the settings string, log this and move on
                    // with defaults.
                    Slog.e(TAG, "Bad batterystats settings", e);
                }
                updateTrackCpuTimesByProcStateLocked(TRACK_CPU_TIMES_BY_PROC_STATE,
                        mParser.getBoolean(KEY_TRACK_CPU_TIMES_BY_PROC_STATE,
                                DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE));
            }
        }
        private void updateTrackCpuTimesByProcStateLocked(boolean wasEnabled, boolean isEnabled) {
            TRACK_CPU_TIMES_BY_PROC_STATE = isEnabled;
            if (isEnabled && !wasEnabled) {
                mKernelSingleUidTimeReader.markDataAsStale(true);
                mExternalSync.scheduleCpuSyncDueToSettingChange();
            }
        }
        public void dumpLocked(PrintWriter pw) {
            pw.print(KEY_TRACK_CPU_TIMES_BY_PROC_STATE); pw.print("=");
            pw.println(TRACK_CPU_TIMES_BY_PROC_STATE);
        }
    }
    public void dumpConstantsLocked(PrintWriter pw) {
        mConstants.dumpLocked(pw);
    }
    Parcel mPendingWrite = null;
    final ReentrantLock mWriteLock = new ReentrantLock();
+27 −1
Original line number Diff line number Diff line
@@ -46,12 +46,14 @@ public class KernelSingleUidTimeReader {
    private final int mCpuFreqsCount;

    @GuardedBy("this")
    private final SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
    private SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();

    @GuardedBy("this")
    private int mReadErrorCounter;
    @GuardedBy("this")
    private boolean mSingleUidCpuTimesAvailable = true;
    @GuardedBy("this")
    private boolean mHasStaleData;

    private final Injector mInjector;

@@ -166,6 +168,30 @@ public class KernelSingleUidTimeReader {
        return deltaTimesMs;
    }

    public void markDataAsStale(boolean hasStaleData) {
        synchronized (this) {
            mHasStaleData = hasStaleData;
        }
    }

    public boolean hasStaleData() {
        synchronized (this) {
            return mHasStaleData;
        }
    }

    public void setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs) {
        synchronized (this) {
            mLastUidCpuTimeMs.clear();
            for (int i = allUidsCpuTimesMs.size() - 1; i >= 0; --i) {
                final long[] cpuTimesMs = allUidsCpuTimesMs.valueAt(i);
                if (cpuTimesMs != null) {
                    mLastUidCpuTimeMs.put(allUidsCpuTimesMs.keyAt(i), cpuTimesMs.clone());
                }
            }
        }
    }

    public void removeUid(int uid) {
        synchronized (this) {
            mLastUidCpuTimeMs.delete(uid);
+1 −0
Original line number Diff line number Diff line
@@ -112,6 +112,7 @@ public class SettingsBackupTest {
                    Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
                    Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
                    Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
                    Settings.Global.BATTERY_STATS_CONSTANTS,
                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
                    Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX,
                    Settings.Global.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX,
+167 −3
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING;
import static android.os.BatteryStats.Uid.UID_PROCESS_TYPES;

import static com.android.internal.os.BatteryStatsImpl.Constants.KEY_TRACK_CPU_TIMES_BY_PROC_STATE;

import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
@@ -48,15 +50,19 @@ import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
import android.os.SystemClock;
import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
import android.text.TextUtils;
import android.util.DebugUtils;
import android.util.Log;

import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;

import java.util.Arrays;
@@ -86,6 +92,9 @@ public class BstatsCpuTimesValidationTest {
    private static final int START_SERVICE_TIMEOUT_MS = 2000;
    private static final int START_ISOLATED_SERVICE_TIMEOUT_MS = 2000;

    private static final int SETTING_UPDATE_TIMEOUT_MS = 2000;
    private static final int SETTING_UPDATE_CHECK_INTERVAL_MS = 200;

    private static final int GENERAL_TIMEOUT_MS = 4000;
    private static final int GENERAL_INTERVAL_MS = 200;

@@ -97,6 +106,8 @@ public class BstatsCpuTimesValidationTest {
    private static boolean sCpuFreqTimesAvailable;
    private static boolean sPerProcStateTimesAvailable;

    @Rule public TestName testName = new TestName();

    @BeforeClass
    public static void setupOnce() throws Exception {
        sContext = InstrumentationRegistry.getContext();
@@ -123,6 +134,9 @@ public class BstatsCpuTimesValidationTest {
    @Test
    public void testCpuFreqTimes() throws Exception {
        if (!sCpuFreqTimesAvailable) {
            Log.w(TAG, "Skipping " + testName.getMethodName()
                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
            return;
        }

@@ -148,6 +162,9 @@ public class BstatsCpuTimesValidationTest {
    @Test
    public void testCpuFreqTimes_screenOff() throws Exception {
        if (!sCpuFreqTimesAvailable) {
            Log.w(TAG, "Skipping " + testName.getMethodName()
                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
            return;
        }

@@ -179,6 +196,9 @@ public class BstatsCpuTimesValidationTest {
    @Test
    public void testCpuFreqTimes_isolatedProcess() throws Exception {
        if (!sCpuFreqTimesAvailable) {
            Log.w(TAG, "Skipping " + testName.getMethodName()
                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
            return;
        }

@@ -204,6 +224,9 @@ public class BstatsCpuTimesValidationTest {
    @Test
    public void testCpuFreqTimes_stateTop() throws Exception {
        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
            Log.w(TAG, "Skipping " + testName.getMethodName()
                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
            return;
        }

@@ -234,6 +257,9 @@ public class BstatsCpuTimesValidationTest {
    @Test
    public void testIsolatedCpuFreqTimes_stateService() throws Exception {
        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
            Log.w(TAG, "Skipping " + testName.getMethodName()
                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
            return;
        }

@@ -272,6 +298,9 @@ public class BstatsCpuTimesValidationTest {
    @Test
    public void testCpuFreqTimes_stateTopSleeping() throws Exception {
        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
            Log.w(TAG, "Skipping " + testName.getMethodName()
                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
            return;
        }

@@ -302,6 +331,9 @@ public class BstatsCpuTimesValidationTest {
    @Test
    public void testCpuFreqTimes_stateFgService() throws Exception {
        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
            Log.w(TAG, "Skipping " + testName.getMethodName()
                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
            return;
        }

@@ -332,6 +364,9 @@ public class BstatsCpuTimesValidationTest {
    @Test
    public void testCpuFreqTimes_stateFg() throws Exception {
        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
            Log.w(TAG, "Skipping " + testName.getMethodName()
                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
            return;
        }

@@ -362,6 +397,9 @@ public class BstatsCpuTimesValidationTest {
    @Test
    public void testCpuFreqTimes_stateBg() throws Exception {
        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
            Log.w(TAG, "Skipping " + testName.getMethodName()
                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
            return;
        }

@@ -392,6 +430,9 @@ public class BstatsCpuTimesValidationTest {
    @Test
    public void testCpuFreqTimes_stateCached() throws Exception {
        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
            Log.w(TAG, "Skipping " + testName.getMethodName()
                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
            return;
        }

@@ -419,6 +460,124 @@ public class BstatsCpuTimesValidationTest {
        batteryOffScreenOn();
    }

    @Test
    public void testCpuFreqTimes_trackingDisabled() throws Exception {
        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
            Log.w(TAG, "Skipping " + testName.getMethodName()
                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
            return;
        }

        final String bstatsConstants = Settings.Global.getString(sContext.getContentResolver(),
                Settings.Global.BATTERY_STATS_CONSTANTS);
        try {
            batteryOnScreenOn();
            forceStop();
            resetBatteryStats();
            final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
            assertNull("Initial snapshot should be null, initial="
                    + Arrays.toString(initialSnapshot), initialSnapshot);
            assertNull("Initial top state snapshot should be null",
                    getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));

            doSomeWork(PROCESS_STATE_TOP);
            forceStop();

            final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
            final String msgCpuTimes = getAllCpuTimesMsg();
            assertCpuTimesValid(cpuTimesMs);
            long actualCpuTimeMs = 0;
            for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
                actualCpuTimeMs += cpuTimesMs[i];
            }
            assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
                    WORK_DURATION_MS, actualCpuTimeMs);

            updateTrackPerProcStateCpuTimesSetting(bstatsConstants, false);

            doSomeWork(PROCESS_STATE_TOP);
            forceStop();

            final long[] cpuTimesMs2 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
            assertCpuTimesValid(cpuTimesMs2);
            assertCpuTimesEqual(cpuTimesMs2, cpuTimesMs, 20,
                    "Unexpected cpu times with tracking off");

            updateTrackPerProcStateCpuTimesSetting(bstatsConstants, true);

            final long[] cpuTimesMs3 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
            assertCpuTimesValid(cpuTimesMs3);
            assertCpuTimesEqual(cpuTimesMs3, cpuTimesMs, 20,
                    "Unexpected cpu times after turning on tracking");

            doSomeWork(PROCESS_STATE_TOP);
            forceStop();

            final long[] cpuTimesMs4 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
            assertCpuTimesValid(cpuTimesMs4);
            actualCpuTimeMs = 0;
            for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
                actualCpuTimeMs += cpuTimesMs[i];
            }
            assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
                    2 * WORK_DURATION_MS, actualCpuTimeMs);

            batteryOffScreenOn();
        } finally {
            Settings.Global.putString(sContext.getContentResolver(),
                    Settings.Global.BATTERY_STATS_CONSTANTS, bstatsConstants);
        }
    }

    private void assertCpuTimesEqual(long[] actual, long[] expected, long delta, String errMsg) {
        for (int i = actual.length - 1; i >= 0; --i) {
            if (actual[i] > expected[i] + delta || actual[i] < expected[i]) {
                fail(errMsg + ", actual=" + actual + ", expected=" + expected + ", delta=" + delta);
            }
        }
    }

    private void updateTrackPerProcStateCpuTimesSetting(String originalConstants, boolean enabled)
            throws Exception {
        final String newConstants;
        final String setting = KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=" + enabled;
        if (originalConstants == null || "null".equals(originalConstants)) {
            newConstants = setting;
        } else if (originalConstants.contains(KEY_TRACK_CPU_TIMES_BY_PROC_STATE)) {
            newConstants = originalConstants.replaceAll(
                    KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=(true|false)", setting);
        } else {
            newConstants = originalConstants + "," + setting;
        }
        Settings.Global.putString(sContext.getContentResolver(),
                Settings.Global.BATTERY_STATS_CONSTANTS, newConstants);
        assertTrackPerProcStateCpuTimesSetting(enabled);
    }

    private void assertTrackPerProcStateCpuTimesSetting(boolean enabled) throws Exception {
        final String expectedValue = Boolean.toString(enabled);
        assertDelayedCondition("Unexpected value for " + KEY_TRACK_CPU_TIMES_BY_PROC_STATE, () -> {
            final String actualValue = getSettingValueFromDump(KEY_TRACK_CPU_TIMES_BY_PROC_STATE);
            return expectedValue.equals(actualValue)
                    ? null : "expected=" + expectedValue + ", actual=" + actualValue;
        }, SETTING_UPDATE_TIMEOUT_MS, SETTING_UPDATE_CHECK_INTERVAL_MS);
    }

    private String getSettingValueFromDump(String key) throws Exception {
        final String settingsDump = executeCmdSilent("dumpsys batterystats --settings");
        final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
        splitter.setString(settingsDump);
        String next;
        while (splitter.hasNext()) {
            next = splitter.next();
            if (next.startsWith(key)) {
                return next.split("=")[1];
            }
        }
        return null;
    }

    private void assertCpuTimesValid(long[] cpuTimes) {
        assertNotNull(cpuTimes);
        for (int i = 0; i < cpuTimes.length; ++i) {
@@ -751,12 +910,17 @@ public class BstatsCpuTimesValidationTest {

    private void assertDelayedCondition(String errMsgPrefix, ExpectedCondition condition)
        throws Exception {
        final long endTime = SystemClock.uptimeMillis() + GENERAL_TIMEOUT_MS;
        assertDelayedCondition(errMsgPrefix, condition, GENERAL_TIMEOUT_MS, GENERAL_INTERVAL_MS);
    }

    private void assertDelayedCondition(String errMsgPrefix, ExpectedCondition condition,
            long timeoutMs, long checkIntervalMs) throws Exception {
        final long endTime = SystemClock.uptimeMillis() + timeoutMs;
        while (SystemClock.uptimeMillis() <= endTime) {
            if (condition.getErrIfNotTrue() == null) {
                return;
            }
            SystemClock.sleep(GENERAL_INTERVAL_MS);
            SystemClock.sleep(checkIntervalMs);
        }
        final String errMsg = condition.getErrIfNotTrue();
        if (errMsg != null) {
Loading