Loading core/java/android/os/BatteryStats.java +36 −0 Original line number Diff line number Diff line Loading @@ -1003,6 +1003,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. Loading Loading @@ -2547,6 +2565,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. Loading core/java/com/android/internal/os/BatteryStatsImpl.java +157 −8 Original line number Diff line number Diff line Loading @@ -170,7 +170,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 Loading Loading @@ -1014,6 +1014,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; Loading Loading @@ -6982,6 +6985,16 @@ public class BatteryStatsImpl extends BatteryStats { return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU); } @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); Loading Loading @@ -7841,6 +7854,16 @@ public class BatteryStatsImpl extends BatteryStats { return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU); } @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); Loading Loading @@ -7880,6 +7903,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, Loading Loading @@ -11823,7 +11867,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); } Loading Loading @@ -11854,6 +11898,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( Loading Loading @@ -11898,7 +11952,7 @@ public class BatteryStatsImpl extends BatteryStats { mTmpRailStats.resetCellularTotalEnergyUsed(); } } long radioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked( long totalAppRadioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked( elapsedRealtimeMs * 1000); mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs); Loading Loading @@ -11958,12 +12012,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) { Loading @@ -11988,12 +12051,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; } Loading Loading @@ -12453,7 +12555,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) Loading @@ -12467,6 +12569,50 @@ public class BatteryStatsImpl extends BatteryStats { distributeEnergyToUidsLocked(powerBucket, chargeUC, fgTimeUsArray, 0); } /** * 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. * Loading Loading @@ -14394,6 +14540,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); } core/java/com/android/internal/os/GnssPowerCalculator.java +24 −4 Original line number Diff line number Diff line Loading @@ -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); } Loading @@ -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; Loading core/java/com/android/internal/os/MobileRadioPowerCalculator.java +78 −24 Original line number Diff line number Diff line Loading @@ -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( Loading @@ -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) Loading @@ -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 = Loading @@ -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. Loading @@ -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. Loading @@ -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 (!isMeasuredPowerAvailable) { final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i); if (DEBUG && p != 0) { Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power=" + formatCharge(p)); } powerMah += p; } signalTimeMs += strengthTimeMs; if (i == 0) { total.noCoverageDurationMs = strengthTimeMs; Loading @@ -222,29 +248,57 @@ 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 (!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 += mActivePowerEstimator.calculatePower(remainingActiveTimeMs); powerMah += calcPowerFromRadioActiveDurationMah(remainingActiveTimeMs); } } total.durationMs = radioActiveTimeMs; total.powerMah = powerMah; total.signalDurationMs = signalTimeMs; } /** * 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); Loading core/java/com/android/internal/power/MeasuredEnergyStats.java +5 −1 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/os/BatteryStats.java +36 −0 Original line number Diff line number Diff line Loading @@ -1003,6 +1003,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. Loading Loading @@ -2547,6 +2565,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. Loading
core/java/com/android/internal/os/BatteryStatsImpl.java +157 −8 Original line number Diff line number Diff line Loading @@ -170,7 +170,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 Loading Loading @@ -1014,6 +1014,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; Loading Loading @@ -6982,6 +6985,16 @@ public class BatteryStatsImpl extends BatteryStats { return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU); } @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); Loading Loading @@ -7841,6 +7854,16 @@ public class BatteryStatsImpl extends BatteryStats { return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU); } @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); Loading Loading @@ -7880,6 +7903,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, Loading Loading @@ -11823,7 +11867,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); } Loading Loading @@ -11854,6 +11898,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( Loading Loading @@ -11898,7 +11952,7 @@ public class BatteryStatsImpl extends BatteryStats { mTmpRailStats.resetCellularTotalEnergyUsed(); } } long radioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked( long totalAppRadioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked( elapsedRealtimeMs * 1000); mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs); Loading Loading @@ -11958,12 +12012,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) { Loading @@ -11988,12 +12051,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; } Loading Loading @@ -12453,7 +12555,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) Loading @@ -12467,6 +12569,50 @@ public class BatteryStatsImpl extends BatteryStats { distributeEnergyToUidsLocked(powerBucket, chargeUC, fgTimeUsArray, 0); } /** * 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. * Loading Loading @@ -14394,6 +14540,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); }
core/java/com/android/internal/os/GnssPowerCalculator.java +24 −4 Original line number Diff line number Diff line Loading @@ -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); } Loading @@ -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; Loading
core/java/com/android/internal/os/MobileRadioPowerCalculator.java +78 −24 Original line number Diff line number Diff line Loading @@ -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( Loading @@ -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) Loading @@ -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 = Loading @@ -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. Loading @@ -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. Loading @@ -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 (!isMeasuredPowerAvailable) { final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i); if (DEBUG && p != 0) { Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power=" + formatCharge(p)); } powerMah += p; } signalTimeMs += strengthTimeMs; if (i == 0) { total.noCoverageDurationMs = strengthTimeMs; Loading @@ -222,29 +248,57 @@ 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 (!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 += mActivePowerEstimator.calculatePower(remainingActiveTimeMs); powerMah += calcPowerFromRadioActiveDurationMah(remainingActiveTimeMs); } } total.durationMs = radioActiveTimeMs; total.powerMah = powerMah; total.signalDurationMs = signalTimeMs; } /** * 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); Loading
core/java/com/android/internal/power/MeasuredEnergyStats.java +5 −1 File changed.Preview size limit exceeded, changes collapsed. Show changes