From be40990e582a0ef43bb8d22abeed4a0a70d6cd9c Mon Sep 17 00:00:00 2001 From: Michael Wachenschwanz Date: Wed, 5 Oct 2022 18:30:51 -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. Bug: 239715259 Test: atest BatteryStatsNoteTest Change-Id: Ie0af28dbc2ad4836ef6437c0656dc20eec2285ea --- .../server/power/stats/BatteryStatsImpl.java | 16 +++ .../power/stats/BatteryStatsNoteTest.java | 109 ++++++++++++++++++ .../power/stats/MockBatteryStatsImpl.java | 4 + 3 files changed, 129 insertions(+) diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index c6128f9f6cef3..250a05ad2e098 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -1306,6 +1306,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") @@ -5506,6 +5513,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/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java index 0a1e3c7032edd..e9c5b01dbe3a1 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/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; @@ -83,6 +84,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; @@ -2030,6 +2032,113 @@ 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/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index 570b2ee617f56..df4b8962630fb 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -113,6 +113,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;