diff options
11 files changed, 449 insertions, 44 deletions
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index fa6472ee4a79..2e843479963b 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1004,6 +1004,24 @@ public abstract class BatteryStats implements Parcelable { public abstract long getCpuMeasuredBatteryConsumptionUC(); /** + * Returns the battery consumption (in microcoulombs) of the uid's GNSS usage, derived from + * on device power measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getGnssMeasuredBatteryConsumptionUC(); + + /** + * Returns the battery consumption (in microcoulombs) of the uid's radio usage, derived from + * on device power measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getMobileRadioMeasuredBatteryConsumptionUC(); + + /** * Returns the battery consumption (in microcoulombs) of the screen while on and uid active, * derived from on device power measurement data. * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. @@ -2548,6 +2566,24 @@ public abstract class BatteryStats implements Parcelable { public abstract long getCpuMeasuredBatteryConsumptionUC(); /** + * Returns the battery consumption (in microcoulombs) of the GNSS, derived from on device power + * measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getGnssMeasuredBatteryConsumptionUC(); + + /** + * Returns the battery consumption (in microcoulombs) of the radio, derived from on device power + * measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getMobileRadioMeasuredBatteryConsumptionUC(); + + /** * Returns the battery consumption (in microcoulombs) of the screen while on, derived from on * device power measurement data. * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 7f8788529714..1fedfd6dbea5 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -169,7 +169,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - static final int VERSION = 195; + static final int VERSION = 196; // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks @@ -1013,6 +1013,9 @@ public class BatteryStatsImpl extends BatteryStats { @Nullable BluetoothPowerCalculator mBluetoothPowerCalculator = null; /** Cpu Power calculator for attributing measured cpu charge consumption to uids */ @Nullable CpuPowerCalculator mCpuPowerCalculator = null; + /** Mobile Radio Power calculator for attributing measured radio charge consumption to uids */ + @Nullable + MobileRadioPowerCalculator mMobileRadioPowerCalculator = null; /** Wifi Power calculator for attributing measured wifi charge consumption to uids */ @Nullable WifiPowerCalculator mWifiPowerCalculator = null; @@ -6981,6 +6984,16 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public long getGnssMeasuredBatteryConsumptionUC() { + return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS); + } + + @Override + public long getMobileRadioMeasuredBatteryConsumptionUC() { + return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO); + } + + @Override public long getScreenOnMeasuredBatteryConsumptionUC() { return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON); } @@ -7840,6 +7853,16 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public long getGnssMeasuredBatteryConsumptionUC() { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS); + } + + @Override + public long getMobileRadioMeasuredBatteryConsumptionUC() { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO); + } + + @Override public long getScreenOnMeasuredBatteryConsumptionUC() { return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON); } @@ -7878,6 +7901,27 @@ public class BatteryStatsImpl extends BatteryStats { return (topTimeUs < fgTimeUs) ? topTimeUs : fgTimeUs; } + + /** + * Gets the uid's time spent using the GNSS since last marked. Also sets the mark time for + * the GNSS timer. + */ + private long markGnssTimeUs(long elapsedRealtimeMs) { + final Sensor sensor = mSensorStats.get(Sensor.GPS); + if (sensor == null) { + return 0; + } + + final StopwatchTimer timer = sensor.mTimer; + if (timer == null) { + return 0; + } + + final long gnssTimeUs = timer.getTimeSinceMarkLocked(elapsedRealtimeMs * 1000); + timer.setMark(elapsedRealtimeMs); + return gnssTimeUs; + } + public StopwatchTimer createAudioTurnedOnTimerLocked() { if (mAudioTurnedOnTimer == null) { mAudioTurnedOnTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, AUDIO_TURNED_ON, @@ -11821,7 +11865,7 @@ public class BatteryStatsImpl extends BatteryStats { * Distribute Cell radio energy info and network traffic to apps. */ public void noteModemControllerActivity(@Nullable final ModemActivityInfo activityInfo, - long elapsedRealtimeMs, long uptimeMs) { + final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) { if (DEBUG_ENERGY) { Slog.d(TAG, "Updating mobile radio stats with " + activityInfo); } @@ -11852,6 +11896,16 @@ public class BatteryStatsImpl extends BatteryStats { return; } + final SparseDoubleArray uidEstimatedConsumptionMah; + if (consumedChargeUC > 0 && mMobileRadioPowerCalculator != null + && mGlobalMeasuredEnergyStats != null) { + mGlobalMeasuredEnergyStats.updateStandardBucket( + MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, consumedChargeUC); + uidEstimatedConsumptionMah = new SparseDoubleArray(); + } else { + uidEstimatedConsumptionMah = null; + } + if (deltaInfo != null) { mHasModemReporting = true; mModemActivity.getIdleTimeCounter().addCountLocked( @@ -11896,7 +11950,7 @@ public class BatteryStatsImpl extends BatteryStats { mTmpRailStats.resetCellularTotalEnergyUsed(); } } - long radioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked( + long totalAppRadioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked( elapsedRealtimeMs * 1000); mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs); @@ -11956,12 +12010,21 @@ public class BatteryStatsImpl extends BatteryStats { // Distribute total radio active time in to this app. final long appPackets = entry.rxPackets + entry.txPackets; - final long appRadioTimeUs = (radioTimeUs * appPackets) / totalPackets; + final long appRadioTimeUs = + (totalAppRadioTimeUs * appPackets) / totalPackets; u.noteMobileRadioActiveTimeLocked(appRadioTimeUs); + // Distribute measured mobile radio charge consumption based on app radio + // active time + if (uidEstimatedConsumptionMah != null) { + uidEstimatedConsumptionMah.add(u.getUid(), + mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah( + appRadioTimeUs / 1000)); + } + // Remove this app from the totals, so that we don't lose any time // due to rounding. - radioTimeUs -= appRadioTimeUs; + totalAppRadioTimeUs -= appRadioTimeUs; totalPackets -= appPackets; if (deltaInfo != null) { @@ -11986,12 +12049,51 @@ public class BatteryStatsImpl extends BatteryStats { } } - if (radioTimeUs > 0) { + if (totalAppRadioTimeUs > 0) { // Whoops, there is some radio time we can't blame on an app! - mMobileRadioActiveUnknownTime.addCountLocked(radioTimeUs); + mMobileRadioActiveUnknownTime.addCountLocked(totalAppRadioTimeUs); mMobileRadioActiveUnknownCount.addCountLocked(1); } + + // Update the MeasuredEnergyStats information. + if (uidEstimatedConsumptionMah != null) { + double totalEstimatedConsumptionMah = 0.0; + + // Estimate total active radio power consumption since last mark. + final long totalRadioTimeMs = mMobileRadioActiveTimer.getTimeSinceMarkLocked( + elapsedRealtimeMs * 1000) / 1000; + mMobileRadioActiveTimer.setMark(elapsedRealtimeMs); + totalEstimatedConsumptionMah += + mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah( + totalRadioTimeMs); + + // Estimate idle power consumption at each signal strength level + final int numSignalStrengthLevels = mPhoneSignalStrengthsTimer.length; + for (int strengthLevel = 0; strengthLevel < numSignalStrengthLevels; + strengthLevel++) { + final long strengthLevelDurationMs = + mPhoneSignalStrengthsTimer[strengthLevel].getTimeSinceMarkLocked( + elapsedRealtimeMs * 1000) / 1000; + mPhoneSignalStrengthsTimer[strengthLevel].setMark(elapsedRealtimeMs); + + totalEstimatedConsumptionMah += + mMobileRadioPowerCalculator.calcIdlePowerAtSignalStrengthMah( + strengthLevelDurationMs, strengthLevel); + } + + // Estimate total active radio power consumption since last mark. + final long scanTimeMs = mPhoneSignalScanningTimer.getTimeSinceMarkLocked( + elapsedRealtimeMs * 1000) / 1000; + mPhoneSignalScanningTimer.setMark(elapsedRealtimeMs); + totalEstimatedConsumptionMah += + mMobileRadioPowerCalculator.calcScanTimePowerMah(scanTimeMs); + + distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, + consumedChargeUC, uidEstimatedConsumptionMah, + totalEstimatedConsumptionMah); + } + mNetworkStatsPool.release(delta); delta = null; } @@ -12451,7 +12553,7 @@ public class BatteryStatsImpl extends BatteryStats { // 'double counted' and will simply exceed the realtime that elapsed. // If multidisplay becomes a reality, this is probably more reasonable than pooling. - // On the first pass, collect total time since mark so that we can normalize power. + // Collect total time since mark so that we can normalize power. final SparseDoubleArray fgTimeUsArray = new SparseDoubleArray(); final long elapsedRealtimeUs = elapsedRealtimeMs * 1000; // TODO(b/175726779): Update and optimize the algorithm (e.g. avoid iterating over ALL uids) @@ -12466,6 +12568,50 @@ public class BatteryStatsImpl extends BatteryStats { } /** + * Accumulate GNSS charge consumption and distribute it to the correct state and the apps. + * + * @param chargeUC amount of charge (microcoulombs) used by GNSS since this was last called. + */ + @GuardedBy("this") + public void updateGnssMeasuredEnergyStatsLocked(long chargeUC, long elapsedRealtimeMs) { + if (DEBUG_ENERGY) Slog.d(TAG, "Updating gnss stats: " + chargeUC); + if (mGlobalMeasuredEnergyStats == null) { + return; + } + + if (!mOnBatteryInternal || chargeUC <= 0) { + // There's nothing further to update. + return; + } + if (mIgnoreNextExternalStats) { + // Although under ordinary resets we won't get here, and typically a new sync will + // happen right after the reset, strictly speaking we need to set all mark times to now. + final int uidStatsSize = mUidStats.size(); + for (int i = 0; i < uidStatsSize; i++) { + final Uid uid = mUidStats.valueAt(i); + uid.markGnssTimeUs(elapsedRealtimeMs); + } + return; + } + + mGlobalMeasuredEnergyStats.updateStandardBucket(MeasuredEnergyStats.POWER_BUCKET_GNSS, + chargeUC); + + // Collect the per uid time since mark so that we can normalize power. + final SparseDoubleArray gnssTimeUsArray = new SparseDoubleArray(); + // TODO(b/175726779): Update and optimize the algorithm (e.g. avoid iterating over ALL uids) + final int uidStatsSize = mUidStats.size(); + for (int i = 0; i < uidStatsSize; i++) { + final Uid uid = mUidStats.valueAt(i); + final long gnssTimeUs = uid.markGnssTimeUs(elapsedRealtimeMs); + if (gnssTimeUs == 0) continue; + gnssTimeUsArray.put(uid.getUid(), (double) gnssTimeUs); + } + distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_GNSS, chargeUC, + gnssTimeUsArray, 0); + } + + /** * Accumulate Custom power bucket charge, globally and for each app. * * @param totalChargeUC charge (microcoulombs) used for this bucket since this was last called. @@ -14467,6 +14613,9 @@ public class BatteryStatsImpl extends BatteryStats { if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU]) { mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile); } + if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO]) { + mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile); + } if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_WIFI]) { mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile); } diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java index df25cdaee17f..97c4fd8b7b7a 100644 --- a/core/java/com/android/internal/os/GnssPowerCalculator.java +++ b/core/java/com/android/internal/os/GnssPowerCalculator.java @@ -61,7 +61,17 @@ public class GnssPowerCalculator extends PowerCalculator { long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query, double averageGnssPowerMa) { final long durationMs = computeDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); - double powerMah = computePower(durationMs, averageGnssPowerMa); + + final long measuredChargeUC = u.getGnssMeasuredBatteryConsumptionUC(); + final boolean isMeasuredPowerAvailable = !query.shouldForceUsePowerProfileModel() + && measuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE; + + final double powerMah; + if (isMeasuredPowerAvailable) { + powerMah = uCtoMah(measuredChargeUC); + } else { + powerMah = computePower(durationMs, averageGnssPowerMa); + } app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS, durationMs) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS, powerMah); } @@ -73,15 +83,25 @@ public class GnssPowerCalculator extends PowerCalculator { for (int i = sippers.size() - 1; i >= 0; i--) { final BatterySipper app = sippers.get(i); if (app.drainType == BatterySipper.DrainType.APP) { - calculateApp(app, app.uidObj, rawRealtimeUs, statsType, averageGnssPowerMa); + calculateApp(app, app.uidObj, rawRealtimeUs, statsType, averageGnssPowerMa, false); } } } protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - int statsType, double averageGnssPowerMa) { + int statsType, double averageGnssPowerMa, boolean shouldForceUsePowerProfileModel) { final long durationMs = computeDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); - double powerMah = computePower(durationMs, averageGnssPowerMa); + + final long measuredChargeUC = u.getGnssMeasuredBatteryConsumptionUC(); + final boolean isMeasuredPowerAvailable = shouldForceUsePowerProfileModel + && measuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE; + + final double powerMah; + if (isMeasuredPowerAvailable) { + powerMah = uCtoMah(measuredChargeUC); + } else { + powerMah = computePower(durationMs, averageGnssPowerMa); + } app.gpsTimeMs = durationMs; app.gpsPowerMah = powerMah; diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java index 22001d4ffbb7..498e1f2931a0 100644 --- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java +++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java @@ -96,10 +96,12 @@ public class MobileRadioPowerCalculator extends PowerCalculator { for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); final BatteryStats.Uid uid = app.getBatteryStatsUid(); - calculateApp(app, uid, powerPerPacketMah, total); + calculateApp(app, uid, powerPerPacketMah, total, + query.shouldForceUsePowerProfileModel()); } - calculateRemaining(total, batteryStats, rawRealtimeUs); + calculateRemaining(total, batteryStats, rawRealtimeUs, + query.shouldForceUsePowerProfileModel()); if (total.powerMah != 0) { builder.getOrCreateSystemBatteryConsumerBuilder( @@ -111,11 +113,13 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, - double powerPerPacketMah, PowerAndDuration total) { + double powerPerPacketMah, PowerAndDuration total, + boolean shouldForceUsePowerProfileModel) { final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED); total.totalAppDurationMs += radioActiveDurationMs; - final double powerMah = calculatePower(u, powerPerPacketMah, radioActiveDurationMs); + final double powerMah = calculatePower(u, powerPerPacketMah, radioActiveDurationMs, + shouldForceUsePowerProfileModel); app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_MOBILE_RADIO, radioActiveDurationMs) @@ -132,12 +136,12 @@ public class MobileRadioPowerCalculator extends PowerCalculator { final BatterySipper app = sippers.get(i); if (app.drainType == BatterySipper.DrainType.APP) { final BatteryStats.Uid u = app.uidObj; - calculateApp(app, u, statsType, mobilePowerPerPacket, total); + calculateApp(app, u, statsType, mobilePowerPerPacket, total, false); } } BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0); - calculateRemaining(total, batteryStats, rawRealtimeUs); + calculateRemaining(total, batteryStats, rawRealtimeUs, false); if (total.powerMah != 0) { if (total.signalDurationMs != 0) { radio.noCoveragePercent = @@ -154,9 +158,12 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, - double powerPerPacketMah, PowerAndDuration total) { + double powerPerPacketMah, PowerAndDuration total, + boolean shouldForceUsePowerProfileModel) { app.mobileActive = calculateDuration(u, statsType); - app.mobileRadioPowerMah = calculatePower(u, powerPerPacketMah, app.mobileActive); + + app.mobileRadioPowerMah = calculatePower(u, powerPerPacketMah, app.mobileActive, + shouldForceUsePowerProfileModel); total.totalAppDurationMs += app.mobileActive; // Add cost of mobile traffic. @@ -183,11 +190,19 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } private double calculatePower(BatteryStats.Uid u, double powerPerPacketMah, - long radioActiveDurationMs) { + long radioActiveDurationMs, boolean shouldForceUsePowerProfileModel) { + + final long measuredChargeUC = u.getMobileRadioMeasuredBatteryConsumptionUC(); + final boolean isMeasuredPowerAvailable = !shouldForceUsePowerProfileModel + && measuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE; + if (isMeasuredPowerAvailable) { + return uCtoMah(measuredChargeUC); + } + if (radioActiveDurationMs > 0) { // We are tracking when the radio is up, so can use the active time to // determine power use. - return mActivePowerEstimator.calculatePower(radioActiveDurationMs); + return calcPowerFromRadioActiveDurationMah(radioActiveDurationMs); } else { // We are not tracking when the radio is up, so must approximate power use // based on the number of packets. @@ -202,18 +217,29 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } private void calculateRemaining(MobileRadioPowerCalculator.PowerAndDuration total, - BatteryStats batteryStats, long rawRealtimeUs) { + BatteryStats batteryStats, long rawRealtimeUs, + boolean shouldForceUsePowerProfileModel) { long signalTimeMs = 0; double powerMah = 0; + + final long measuredChargeUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC(); + final boolean isMeasuredPowerAvailable = !shouldForceUsePowerProfileModel + && measuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE; + if (isMeasuredPowerAvailable) { + powerMah = uCtoMah(measuredChargeUC); + } + for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) { long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED) / 1000; - final double p = mIdlePowerEstimators[i].calculatePower(strengthTimeMs); - if (DEBUG && p != 0) { - Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power=" - + formatCharge(p)); + if (!isMeasuredPowerAvailable) { + final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i); + if (DEBUG && p != 0) { + Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power=" + + formatCharge(p)); + } + powerMah += p; } - powerMah += p; signalTimeMs += strengthTimeMs; if (i == 0) { total.noCoverageDurationMs = strengthTimeMs; @@ -222,16 +248,21 @@ public class MobileRadioPowerCalculator extends PowerCalculator { final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED) / 1000; - final double p = mScanPowerEstimator.calculatePower(scanningTimeMs); - if (DEBUG && p != 0) { - Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + formatCharge(p)); - } - powerMah += p; long radioActiveTimeMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED) / 1000; long remainingActiveTimeMs = radioActiveTimeMs - total.totalAppDurationMs; - if (remainingActiveTimeMs > 0) { - powerMah += mActivePowerEstimator.calculatePower(remainingActiveTimeMs); + + if (!isMeasuredPowerAvailable) { + final double p = calcScanTimePowerMah(scanningTimeMs); + if (DEBUG && p != 0) { + Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + formatCharge( + p)); + } + powerMah += p; + + if (remainingActiveTimeMs > 0) { + powerMah += calcPowerFromRadioActiveDurationMah(remainingActiveTimeMs); + } } total.durationMs = radioActiveTimeMs; total.powerMah = powerMah; @@ -239,12 +270,35 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } /** + * Calculates active radio power consumption (in milliamp-hours) from active radio duration. + */ + public double calcPowerFromRadioActiveDurationMah(long radioActiveDurationMs) { + return mActivePowerEstimator.calculatePower(radioActiveDurationMs); + } + + /** + * Calculates idle radio power consumption (in milliamp-hours) for time spent at a cell signal + * strength level. + * see {@link CellSignalStrength#getNumSignalStrengthLevels()} + */ + public double calcIdlePowerAtSignalStrengthMah(long strengthTimeMs, int strengthLevel) { + return mIdlePowerEstimators[strengthLevel].calculatePower(strengthTimeMs); + } + + /** + * Calculates radio scan power consumption (in milliamp-hours) from scan time. + */ + public double calcScanTimePowerMah(long scanningTimeMs) { + return mScanPowerEstimator.calculatePower(scanningTimeMs); + } + + /** * Return estimated power (in mAh) of sending or receiving a packet with the mobile radio. */ private double getMobilePowerPerPacket(BatteryStats stats, long rawRealtimeUs, int statsType) { final long radioDataUptimeMs = stats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000; - final double mobilePower = mActivePowerEstimator.calculatePower(radioDataUptimeMs); + final double mobilePower = calcPowerFromRadioActiveDurationMah(radioDataUptimeMs); final long mobileRx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA, statsType); diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java index 845b3e501c08..00a0627d068c 100644 --- a/core/java/com/android/internal/power/MeasuredEnergyStats.java +++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java @@ -54,7 +54,9 @@ public class MeasuredEnergyStats { public static final int POWER_BUCKET_CPU = 3; public static final int POWER_BUCKET_WIFI = 4; public static final int POWER_BUCKET_BLUETOOTH = 5; - public static final int NUMBER_STANDARD_POWER_BUCKETS = 6; // Buckets above this are custom. + public static final int POWER_BUCKET_GNSS = 6; + public static final int POWER_BUCKET_MOBILE_RADIO = 7; + public static final int NUMBER_STANDARD_POWER_BUCKETS = 8; // Buckets above this are custom. @IntDef(prefix = {"POWER_BUCKET_"}, value = { POWER_BUCKET_UNKNOWN, @@ -64,6 +66,8 @@ public class MeasuredEnergyStats { POWER_BUCKET_CPU, POWER_BUCKET_WIFI, POWER_BUCKET_BLUETOOTH, + POWER_BUCKET_GNSS, + POWER_BUCKET_MOBILE_RADIO, }) @Retention(RetentionPolicy.SOURCE) public @interface StandardPowerBucket { diff --git a/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java index bd7f1e2c3410..eed61cb0afe7 100644 --- a/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java @@ -19,6 +19,7 @@ package com.android.internal.os; import static com.google.common.truth.Truth.assertThat; import android.os.BatteryConsumer; +import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.UidBatteryConsumer; @@ -35,12 +36,14 @@ public class GnssPowerCalculatorTest { private static final double PRECISION = 0.00001; private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; + private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 222; @Rule public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() .setAveragePower(PowerProfile.POWER_GPS_ON, 360.0) .setAveragePower(PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED, - new double[] {720.0, 1440.0, 1800.0}); + new double[] {720.0, 1440.0, 1800.0}) + .initMeasuredEnergyStatsLocked(0); @Test public void testTimerBasedModel() { @@ -51,7 +54,8 @@ public class GnssPowerCalculatorTest { GnssPowerCalculator calculator = new GnssPowerCalculator(mStatsRule.getPowerProfile()); - mStatsRule.apply(calculator); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(), + calculator); UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID); assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS)) @@ -59,4 +63,35 @@ public class GnssPowerCalculatorTest { assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS)) .isWithin(PRECISION).of(0.1); } + + @Test + public void testMeasuredEnergyBasedModel() { + BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(APP_UID); + uidStats.noteStartGps(1000); + uidStats.noteStopGps(2000); + + BatteryStatsImpl.Uid uidStats2 = mStatsRule.getUidStats(APP_UID2); + uidStats2.noteStartGps(3000); + uidStats2.noteStopGps(5000); + + BatteryStatsImpl stats = mStatsRule.getBatteryStats(); + stats.updateGnssMeasuredEnergyStatsLocked(30_000_000, 6000); + + GnssPowerCalculator calculator = + new GnssPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(calculator); + + UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS)) + .isEqualTo(1000); + assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS)) + .isWithin(PRECISION).of(2.77777); + + UidBatteryConsumer consumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2); + assertThat(consumer2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS)) + .isEqualTo(2000); + assertThat(consumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS)) + .isWithin(PRECISION).of(5.55555); + } } diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java index 66a8379177c2..ae59a546642d 100644 --- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java @@ -16,6 +16,8 @@ package com.android.internal.os; +import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; @@ -24,6 +26,7 @@ import static org.mockito.Mockito.when; import android.net.NetworkCapabilities; import android.net.NetworkStats; import android.os.BatteryConsumer; +import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.SystemBatteryConsumer; import android.os.UidBatteryConsumer; @@ -54,7 +57,8 @@ public class MobileRadioPowerCalculatorTest { .setAveragePower(PowerProfile.POWER_RADIO_SCANNING, 720.0) .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX, 1440.0) .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, - new double[] {720.0, 1080.0, 1440.0, 1800.0, 2160.0}); + new double[] {720.0, 1080.0, 1440.0, 1800.0, 2160.0}) + .initMeasuredEnergyStatsLocked(0); @Test public void testCounterBasedModel() { @@ -88,14 +92,15 @@ public class MobileRadioPowerCalculatorTest { ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, new int[] {100, 200, 300, 400, 500}, 600); - stats.noteModemControllerActivity(mai, 10000, 10000); + stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000); mStatsRule.setTime(12_000_000, 12_000_000); MobileRadioPowerCalculator calculator = new MobileRadioPowerCalculator(mStatsRule.getPowerProfile()); - mStatsRule.apply(calculator); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(), + calculator); SystemBatteryConsumer consumer = mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO); @@ -106,4 +111,57 @@ public class MobileRadioPowerCalculatorTest { assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) .isWithin(PRECISION).of(0.8); } + + @Test + public void testMeasuredEnergyBasedModel() { + BatteryStatsImpl stats = mStatsRule.getBatteryStats(); + + // Scan for a cell + stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE, + TelephonyManager.SIM_STATE_READY, + 2000, 2000); + + // Found a cell + stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY, + 5000, 5000); + + // Note cell signal strength + SignalStrength signalStrength = mock(SignalStrength.class); + when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE); + stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000); + + stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, + 8_000_000_000L, APP_UID, 8000, 8000); + + // Note established network + stats.noteNetworkInterfaceForTransports("cellular", + new int[] { NetworkCapabilities.TRANSPORT_CELLULAR }); + + // Note application network activity + NetworkStats networkStats = new NetworkStats(10000, 1) + .insertEntry("cellular", APP_UID, 0, 0, 1000, 100, 2000, 20, 100); + mStatsRule.setNetworkStats(networkStats); + + ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, + new int[] {100, 200, 300, 400, 500}, 600); + stats.noteModemControllerActivity(mai, 10_000_000, 10000, 10000); + + mStatsRule.setTime(12_000_000, 12_000_000); + + MobileRadioPowerCalculator calculator = + new MobileRadioPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(calculator); + + SystemBatteryConsumer consumer = + mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO); + + // 100000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s) = 2.77777 mAh + assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) + .isWithin(PRECISION).of(2.77777); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) + .isWithin(PRECISION).of(1.53934); + } } diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index e74c936af02d..0df47d6e50bb 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -658,6 +658,11 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC, screenState, elapsedRealtime); } + + final long gnssChargeUC = measuredEnergyDeltas.gnssChargeUC; + if (gnssChargeUC != MeasuredEnergySnapshot.UNAVAILABLE) { + mStats.updateGnssMeasuredEnergyStatsLocked(displayChargeUC, elapsedRealtime); + } } // Inform mStats about each applicable custom energy bucket. if (measuredEnergyDeltas != null @@ -698,7 +703,10 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } if (modemInfo != null) { - mStats.noteModemControllerActivity(modemInfo, elapsedRealtime, uptime); + final long mobileRadioChargeUC = measuredEnergyDeltas != null + ? measuredEnergyDeltas.mobileRadioChargeUC : MeasuredEnergySnapshot.UNAVAILABLE; + mStats.noteModemControllerActivity(modemInfo, mobileRadioChargeUC, elapsedRealtime, + uptime); } if (updateFlags == UPDATE_ALL) { @@ -830,6 +838,12 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { case EnergyConsumerType.CPU_CLUSTER: buckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true; break; + case EnergyConsumerType.GNSS: + buckets[MeasuredEnergyStats.POWER_BUCKET_GNSS] = true; + break; + case EnergyConsumerType.MOBILE_RADIO: + buckets[MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO] = true; + break; case EnergyConsumerType.DISPLAY: buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON] = true; buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_DOZE] = true; @@ -883,6 +897,9 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { if ((flags & UPDATE_DISPLAY) != 0) { addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY); } + if ((flags & UPDATE_RADIO) != 0) { + addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.MOBILE_RADIO); + } if ((flags & UPDATE_WIFI) != 0) { addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.WIFI); } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index c3f97adbd9c3..4228e3ef2df9 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1966,7 +1966,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub final long elapsedRealtime = SystemClock.elapsedRealtime(); final long uptime = SystemClock.uptimeMillis(); mHandler.post(() -> { - mStats.noteModemControllerActivity(info, elapsedRealtime, uptime); + mStats.noteModemControllerActivity(info, POWER_DATA_UNAVAILABLE, elapsedRealtime, + uptime); }); } } diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java index 4c9ab63a100b..8682027c8eb6 100644 --- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java +++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java @@ -118,6 +118,12 @@ public class MeasuredEnergySnapshot { /** The chargeUC for {@link EnergyConsumerType#DISPLAY}. */ public long displayChargeUC = UNAVAILABLE; + /** The chargeUC for {@link EnergyConsumerType#GNSS}. */ + public long gnssChargeUC = UNAVAILABLE; + + /** The chargeUC for {@link EnergyConsumerType#MOBILE_RADIO}. */ + public long mobileRadioChargeUC = UNAVAILABLE; + /** The chargeUC for {@link EnergyConsumerType#WIFI}. */ public long wifiChargeUC = UNAVAILABLE; @@ -217,6 +223,14 @@ public class MeasuredEnergySnapshot { output.displayChargeUC = deltaChargeUC; break; + case EnergyConsumerType.GNSS: + output.gnssChargeUC = deltaChargeUC; + break; + + case EnergyConsumerType.MOBILE_RADIO: + output.mobileRadioChargeUC = deltaChargeUC; + break; + case EnergyConsumerType.WIFI: output.wifiChargeUC = deltaChargeUC; break; diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java index 73a2febf72aa..4a67ec71fcaa 100644 --- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java @@ -20,6 +20,7 @@ import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_ import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_BT; import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU; import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY; +import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO; import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI; import static org.junit.Assert.assertArrayEquals; @@ -93,6 +94,16 @@ public class BatteryExternalStatsWorkerTest { tempAllIds.add(btId); mPowerStatsInternal.incrementEnergyConsumption(btId, 34567); + final int gnssId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.GNSS, 0, + "gnss"); + tempAllIds.add(gnssId); + mPowerStatsInternal.incrementEnergyConsumption(gnssId, 787878); + + final int mobileRadioId = mPowerStatsInternal.addEnergyConsumer( + EnergyConsumerType.MOBILE_RADIO, 0, "mobile_radio"); + tempAllIds.add(mobileRadioId); + mPowerStatsInternal.incrementEnergyConsumption(mobileRadioId, 62626); + final int[] cpuClusterIds = new int[numCpuClusters]; for (int i = 0; i < numCpuClusters; i++) { cpuClusterIds[i] = mPowerStatsInternal.addEnergyConsumer( @@ -135,6 +146,12 @@ public class BatteryExternalStatsWorkerTest { assertEquals(1, bluetoothResults.length); assertEquals(btId, bluetoothResults[0].id); + final EnergyConsumerResult[] mobileRadioResults = + mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_RADIO).getNow(null); + // Results should only have the mobile radio energy consumer + assertEquals(1, mobileRadioResults.length); + assertEquals(mobileRadioId, mobileRadioResults[0].id); + final EnergyConsumerResult[] cpuResults = mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_CPU).getNow(null); // Results should only have the cpu cluster energy consumers |