From 9c841db321f63b39d129d80685890b8bf217524e Mon Sep 17 00:00:00 2001 From: Michael Wachenschwanz Date: Tue, 18 Oct 2022 13:49:35 -0700 Subject: [PATCH] Rate limit BatteryStats' ModemActivityInfo querying Frequent mobile radio power state changes results in frequent ModemActivityInfo queries. To limit the unnecessary churn, a Modem update due to mobile radio power state change will only occur some amount of time after the previous update. Modem updates due to other signals (plug/unplug events, battery percent change, etc) will be unaffected. Fixes: 239715259 Test: atest BatteryStatsNoteTest Merged-In: Ie0af28dbc2ad4836ef6437c0656dc20eec2285ea Change-Id: Ib3ee18113ac381b8912bb58974b5d41ceefb7131 --- .../android/internal/os/BatteryStatsImpl.java | 16 +++ .../internal/os/BatteryStatsNoteTest.java | 111 ++++++++++++++++++ .../internal/os/MockBatteryStatsImpl.java | 4 + 3 files changed, 131 insertions(+) diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 8b96597767d35..6fed26c4a81d0 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -1326,6 +1326,13 @@ public class BatteryStatsImpl extends BatteryStats { LongSamplingCounter mMobileRadioActiveUnknownTime; LongSamplingCounter mMobileRadioActiveUnknownCount; + /** + * The soonest the Mobile Radio stats can be updated due to a mobile radio power state change + * after it was last updated. + */ + @VisibleForTesting + protected static final long MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS = 1000 * 60 * 10; + int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; @GuardedBy("this") @@ -6260,6 +6267,15 @@ public class BatteryStatsImpl extends BatteryStats { } else { mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs); mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs); + + if (mLastModemActivityInfo != null) { + if (elapsedRealtimeMs < mLastModemActivityInfo.getTimestampMillis() + + MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS) { + // Modem Activity info has been collected recently, don't bother + // triggering another update. + return false; + } + } // Tell the caller to collect radio network/power stats. return true; } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java index d19f9f5ea58f9..52feac5a585a7 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java @@ -47,6 +47,7 @@ import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; import android.telephony.ServiceState; import android.telephony.TelephonyManager; +import android.util.Log; import android.util.MutableInt; import android.util.SparseIntArray; import android.util.SparseLongArray; @@ -82,6 +83,7 @@ import java.util.function.IntConsumer; * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner */ public class BatteryStatsNoteTest extends TestCase { + private static final String TAG = BatteryStatsNoteTest.class.getSimpleName(); private static final int UID = 10500; private static final int ISOLATED_APP_ID = Process.FIRST_ISOLATED_UID + 23; @@ -2031,6 +2033,115 @@ public class BatteryStatsNoteTest extends TestCase { noRadioProcFlags, lastProcStateChangeFlags.value); } + + + @SmallTest + public void testNoteMobileRadioPowerStateLocked() { + long curr; + boolean update; + final MockClock clocks = new MockClock(); // holds realtime and uptime in ms + final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setOnBatteryInternal(true); + + // Note mobile radio is on. + curr = 1000L * (clocks.realtime = clocks.uptime = 1001); + bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr, + UID); + + // Note mobile radio is still on. + curr = 1000L * (clocks.realtime = clocks.uptime = 2001); + update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, + curr, UID); + assertFalse( + "noteMobileRadioPowerStateLocked should not request an update when the power " + + "state does not change from HIGH.", + update); + + // Note mobile radio is off. + curr = 1000L * (clocks.realtime = clocks.uptime = 3001); + update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + curr, UID); + assertTrue( + "noteMobileRadioPowerStateLocked should request an update when the power state " + + "changes from HIGH to LOW.", + update); + + // Note mobile radio is still off. + curr = 1000L * (clocks.realtime = clocks.uptime = 4001); + update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + curr, UID); + assertFalse( + "noteMobileRadioPowerStateLocked should not request an update when the power " + + "state does not change from LOW.", + update); + + // Note mobile radio is on. + curr = 1000L * (clocks.realtime = clocks.uptime = 5001); + update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, + curr, UID); + assertFalse( + "noteMobileRadioPowerStateLocked should not request an update when the power " + + "state changes from LOW to HIGH.", + update); + } + + @SmallTest + public void testNoteMobileRadioPowerStateLocked_rateLimited() { + long curr; + boolean update; + final MockClock clocks = new MockClock(); // holds realtime and uptime in ms + final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setPowerProfile(mock(PowerProfile.class)); + + final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels(); + final ModemActivityInfo mai = new ModemActivityInfo(0L, 0L, 0L, new int[txLevelCount], 0L); + + final long rateLimit = bi.getMobileRadioPowerStateUpdateRateLimit(); + if (rateLimit < 0) { + Log.w(TAG, "Skipping testNoteMobileRadioPowerStateLocked_rateLimited, rateLimit = " + + rateLimit); + return; + } + bi.setOnBatteryInternal(true); + + // Note mobile radio is on. + curr = 1000L * (clocks.realtime = clocks.uptime = 1001); + bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr, + UID); + + clocks.realtime = clocks.uptime = 2001; + mai.setTimestamp(clocks.realtime); + bi.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, + clocks.realtime, clocks.uptime, mNetworkStatsManager); + + // Note mobile radio is off within the rate limit duration. + clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7); + curr = 1000L * clocks.realtime; + update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + curr, UID); + assertFalse( + "noteMobileRadioPowerStateLocked should not request an update when the power " + + "state so soon after a noteModemControllerActivity", + update); + + // Note mobile radio is on. + clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7); + curr = 1000L * clocks.realtime; + bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr, + UID); + + // Note mobile radio is off much later + clocks.realtime = clocks.uptime = clocks.realtime + rateLimit; + curr = 1000L * clocks.realtime; + update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + curr, UID); + assertTrue( + "noteMobileRadioPowerStateLocked should request an update when the power state " + + "changes from HIGH to LOW much later after a " + + "noteModemControllerActivity.", + update); + } + private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) { // Note that noteUidProcessStateLocked uses ActivityManager process states. if (fgOn) { diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java index edeb5e9f4834e..5ea4f069f0261 100644 --- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java +++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java @@ -114,6 +114,10 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { return getUidStatsLocked(uid).mOnBatteryScreenOffBackgroundTimeBase; } + public long getMobileRadioPowerStateUpdateRateLimit() { + return MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS; + } + public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) { mNetworkStats = networkStats; return this;