blob: aba8e5fa95077a7921dd73f9a128271aae149503 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.power.stats;
import android.annotation.Nullable;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
import android.telephony.CellSignalStrength;
import android.telephony.ServiceState;
import android.util.Log;
import android.util.LongArrayQueue;
import android.util.SparseArray;
import com.android.internal.os.PowerProfile;
import com.android.internal.power.ModemPowerProfile;
import java.util.ArrayList;
public class MobileRadioPowerCalculator extends PowerCalculator {
private static final String TAG = "MobRadioPowerCalculator";
private static final boolean DEBUG = PowerCalculator.DEBUG;
private static final double MILLIS_IN_HOUR = 1000.0 * 60 * 60;
private static final int NUM_SIGNAL_STRENGTH_LEVELS =
CellSignalStrength.getNumSignalStrengthLevels();
private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0];
private static final int IGNORE = -1;
private final UsageBasedPowerEstimator mActivePowerEstimator; // deprecated
private final UsageBasedPowerEstimator[] mIdlePowerEstimators =
new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS]; // deprecated
private final UsageBasedPowerEstimator mScanPowerEstimator; // deprecated
@Nullable
private final UsageBasedPowerEstimator mSleepPowerEstimator;
@Nullable
private final UsageBasedPowerEstimator mIdlePowerEstimator;
private final PowerProfile mPowerProfile;
private static class PowerAndDuration {
public long remainingDurationMs;
public double remainingPowerMah;
public long totalAppDurationMs;
public double totalAppPowerMah;
}
public MobileRadioPowerCalculator(PowerProfile profile) {
mPowerProfile = profile;
final double sleepDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
Double.NaN);
if (Double.isNaN(sleepDrainRateMa)) {
mSleepPowerEstimator = null;
} else {
mSleepPowerEstimator = new UsageBasedPowerEstimator(sleepDrainRateMa);
}
final double idleDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE,
Double.NaN);
if (Double.isNaN(idleDrainRateMa)) {
mIdlePowerEstimator = null;
} else {
mIdlePowerEstimator = new UsageBasedPowerEstimator(idleDrainRateMa);
}
// Instantiate legacy power estimators
double powerRadioActiveMa =
profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, Double.NaN);
if (Double.isNaN(powerRadioActiveMa)) {
double sum = 0;
sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
}
powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1);
}
mActivePowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa);
if (!Double.isNaN(
profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, Double.NaN))) {
for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(
profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i));
}
} else {
double idle = profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE);
// Magical calculations preserved for historical compatibility
mIdlePowerEstimators[0] = new UsageBasedPowerEstimator(idle * 25 / 180);
for (int i = 1; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(Math.max(1, idle / 256));
}
}
mScanPowerEstimator = new UsageBasedPowerEstimator(
profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_SCANNING, 0));
}
@Override
public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
return powerComponent == BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO;
}
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
PowerAndDuration total = new PowerAndDuration();
final long totalConsumptionUC = batteryStats.getMobileRadioEnergyConsumptionUC();
final int powerModel = getPowerModel(totalConsumptionUC, query);
final double totalActivePowerMah;
final ArrayList<UidBatteryConsumer.Builder> apps;
final LongArrayQueue appDurationsMs;
if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
// EnergyConsumer is available, don't bother calculating power.
totalActivePowerMah = Double.NaN;
apps = null;
appDurationsMs = null;
} else {
totalActivePowerMah = calculateActiveModemPowerMah(batteryStats, rawRealtimeUs);
apps = new ArrayList<>();
appDurationsMs = new LongArrayQueue();
}
final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
builder.getUidBatteryConsumerBuilders();
BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
final BatteryStats.Uid uid = app.getBatteryStatsUid();
if (keys == UNINITIALIZED_KEYS) {
if (query.isProcessStateDataNeeded()) {
keys = app.getKeys(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
} else {
keys = null;
}
}
// Sum and populate each app's active radio duration.
final long radioActiveDurationMs = calculateDuration(uid,
BatteryStats.STATS_SINCE_CHARGED);
if (!app.isVirtualUid()) {
total.totalAppDurationMs += radioActiveDurationMs;
}
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
radioActiveDurationMs);
if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
// EnergyConsumer is available, populate the consumed power now.
final long appConsumptionUC = uid.getMobileRadioEnergyConsumptionUC();
if (appConsumptionUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
final double appConsumptionMah = uCtoMah(appConsumptionUC);
if (!app.isVirtualUid()) {
total.totalAppPowerMah += appConsumptionMah;
}
app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
appConsumptionMah, powerModel);
if (query.isProcessStateDataNeeded() && keys != null) {
for (BatteryConsumer.Key key : keys) {
final int processState = key.processState;
if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
// Already populated with the total across all process states
continue;
}
final long consumptionInStateUc =
uid.getMobileRadioEnergyConsumptionUC(processState);
final double powerInStateMah = uCtoMah(consumptionInStateUc);
app.setConsumedPower(key, powerInStateMah, powerModel);
}
}
}
} else {
// Cache the app and its active duration for later calculations.
apps.add(app);
appDurationsMs.addLast(radioActiveDurationMs);
}
}
long totalActiveDurationMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
if (totalActiveDurationMs < total.totalAppDurationMs) {
totalActiveDurationMs = total.totalAppDurationMs;
}
if (powerModel != BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
// Need to smear the calculated total active power across the apps based on app
// active durations.
final int appSize = apps.size();
for (int i = 0; i < appSize; i++) {
final UidBatteryConsumer.Builder app = apps.get(i);
final long activeDurationMs = appDurationsMs.get(i);
// Proportionally attribute radio power consumption based on active duration.
final double appConsumptionMah;
if (totalActiveDurationMs == 0.0) {
appConsumptionMah = 0.0;
} else {
appConsumptionMah =
(totalActivePowerMah * activeDurationMs) / totalActiveDurationMs;
}
if (!app.isVirtualUid()) {
total.totalAppPowerMah += appConsumptionMah;
}
app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
appConsumptionMah, powerModel);
if (query.isProcessStateDataNeeded() && keys != null) {
final BatteryStats.Uid uid = app.getBatteryStatsUid();
for (BatteryConsumer.Key key : keys) {
final int processState = key.processState;
if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
// Already populated with the total across all process states
continue;
}
final long durationInStateMs =
uid.getMobileRadioActiveTimeInProcessState(processState) / 1000;
// Proportionally attribute per process state radio power consumption
// based on time state duration.
final double powerInStateMah;
if (activeDurationMs == 0.0) {
powerInStateMah = 0.0;
} else {
powerInStateMah =
(appConsumptionMah * durationInStateMs) / activeDurationMs;
}
app.setConsumedPower(key, powerInStateMah, powerModel);
}
}
}
}
total.remainingDurationMs = totalActiveDurationMs - total.totalAppDurationMs;
// Calculate remaining power consumption.
if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
total.remainingPowerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
if (total.remainingPowerMah < 0) total.remainingPowerMah = 0;
} else {
// Smear unattributed active time and add it to the remaining power consumption.
if (totalActiveDurationMs != 0) {
total.remainingPowerMah +=
(totalActivePowerMah * total.remainingDurationMs) / totalActiveDurationMs;
}
// Calculate the inactive modem power consumption.
final BatteryStats.ControllerActivityCounter modemActivity =
batteryStats.getModemControllerActivity();
double inactivePowerMah = Double.NaN;
if (modemActivity != null) {
final long sleepDurationMs = modemActivity.getSleepTimeCounter().getCountLocked(
BatteryStats.STATS_SINCE_CHARGED);
final long idleDurationMs = modemActivity.getIdleTimeCounter().getCountLocked(
BatteryStats.STATS_SINCE_CHARGED);
inactivePowerMah = calcInactiveStatePowerMah(sleepDurationMs, idleDurationMs);
}
if (Double.isNaN(inactivePowerMah)) {
// Modem activity counters unavailable. Use legacy calculations for inactive usage.
final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
inactivePowerMah = calcScanTimePowerMah(scanningTimeMs);
for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
inactivePowerMah += calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
}
}
if (!Double.isNaN(inactivePowerMah)) {
total.remainingPowerMah += inactivePowerMah;
}
}
if (total.remainingPowerMah != 0 || total.totalAppPowerMah != 0) {
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
total.remainingDurationMs + total.totalAppDurationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
total.remainingPowerMah + total.totalAppPowerMah, powerModel);
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
total.totalAppDurationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
total.totalAppPowerMah, powerModel);
}
}
private long calculateDuration(BatteryStats.Uid u, int statsType) {
return u.getMobileRadioActiveTime(statsType) / 1000;
}
private double calculateActiveModemPowerMah(BatteryStats bs, long elapsedRealtimeUs) {
final long elapsedRealtimeMs = elapsedRealtimeUs / 1000;
final int txLvlCount = CellSignalStrength.getNumSignalStrengthLevels();
double consumptionMah = 0.0;
if (DEBUG) {
Log.d(TAG, "Calculating radio power consumption at elapased real timestamp : "
+ elapsedRealtimeMs + " ms");
}
boolean hasConstants = false;
for (int rat = 0; rat < BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
final int freqCount = rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR
? ServiceState.FREQUENCY_RANGE_COUNT : 1;
for (int freq = 0; freq < freqCount; freq++) {
for (int txLvl = 0; txLvl < txLvlCount; txLvl++) {
final long txDurationMs = bs.getActiveTxRadioDurationMs(rat, freq, txLvl,
elapsedRealtimeMs);
if (txDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
continue;
}
final double txConsumptionMah = calcTxStatePowerMah(rat, freq, txLvl,
txDurationMs);
if (Double.isNaN(txConsumptionMah)) {
continue;
}
hasConstants = true;
consumptionMah += txConsumptionMah;
}
final long rxDurationMs = bs.getActiveRxRadioDurationMs(rat, freq,
elapsedRealtimeMs);
if (rxDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
continue;
}
final double rxConsumptionMah = calcRxStatePowerMah(rat, freq, rxDurationMs);
if (Double.isNaN(rxConsumptionMah)) {
continue;
}
hasConstants = true;
consumptionMah += rxConsumptionMah;
}
}
if (!hasConstants) {
final long radioActiveDurationMs = bs.getMobileRadioActiveTime(elapsedRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
if (DEBUG) {
Log.d(TAG,
"Failed to calculate radio power consumption. Reattempted with legacy "
+ "method. Radio active duration : "
+ radioActiveDurationMs + " ms");
}
if (radioActiveDurationMs > 0) {
consumptionMah = calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
} else {
consumptionMah = 0.0;
}
}
if (DEBUG) {
Log.d(TAG, "Total active radio power consumption calculated to be " + consumptionMah
+ " mAH.");
}
return consumptionMah;
}
private static long buildModemPowerProfileKey(@ModemPowerProfile.ModemDrainType int drainType,
@BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange,
int txLevel) {
long key = PowerProfile.SUBSYSTEM_MODEM;
// Attach Modem drain type to the key if specified.
if (drainType != IGNORE) {
key |= drainType;
}
// Attach RadioAccessTechnology to the key if specified.
switch (rat) {
case IGNORE:
// do nothing
break;
case BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER:
key |= ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT;
break;
case BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE:
key |= ModemPowerProfile.MODEM_RAT_TYPE_LTE;
break;
case BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR:
key |= ModemPowerProfile.MODEM_RAT_TYPE_NR;
break;
default:
Log.w(TAG, "Unexpected RadioAccessTechnology : " + rat);
}
// Attach NR Frequency Range to the key if specified.
switch (freqRange) {
case IGNORE:
// do nothing
break;
case ServiceState.FREQUENCY_RANGE_UNKNOWN:
key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT;
break;
case ServiceState.FREQUENCY_RANGE_LOW:
key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW;
break;
case ServiceState.FREQUENCY_RANGE_MID:
key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID;
break;
case ServiceState.FREQUENCY_RANGE_HIGH:
key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH;
break;
case ServiceState.FREQUENCY_RANGE_MMWAVE:
key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE;
break;
default:
Log.w(TAG, "Unexpected NR frequency range : " + freqRange);
}
// Attach transmission level to the key if specified.
switch (txLevel) {
case IGNORE:
// do nothing
break;
case 0:
key |= ModemPowerProfile.MODEM_TX_LEVEL_0;
break;
case 1:
key |= ModemPowerProfile.MODEM_TX_LEVEL_1;
break;
case 2:
key |= ModemPowerProfile.MODEM_TX_LEVEL_2;
break;
case 3:
key |= ModemPowerProfile.MODEM_TX_LEVEL_3;
break;
case 4:
key |= ModemPowerProfile.MODEM_TX_LEVEL_4;
break;
default:
Log.w(TAG, "Unexpected transmission level : " + txLevel);
}
return key;
}
/**
* Calculates active receive radio power consumption (in milliamp-hours) from the given state's
* duration.
*/
public double calcRxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
@ServiceState.FrequencyRange int freqRange, long rxDurationMs) {
final long rxKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat,
freqRange, IGNORE);
final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(rxKey,
Double.NaN);
if (Double.isNaN(drainRateMa)) {
Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(rxKey));
return Double.NaN;
}
final double consumptionMah = drainRateMa * rxDurationMs / MILLIS_IN_HOUR;
if (DEBUG) {
Log.d(TAG, "Calculated RX consumption " + consumptionMah + " mAH from a drain rate of "
+ drainRateMa + " mA and a duration of " + rxDurationMs + " ms for "
+ ModemPowerProfile.keyToString((int) rxKey));
}
return consumptionMah;
}
/**
* Calculates active transmit radio power consumption (in milliamp-hours) from the given state's
* duration.
*/
public double calcTxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
@ServiceState.FrequencyRange int freqRange, int txLevel, long txDurationMs) {
final long txKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat,
freqRange, txLevel);
final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(txKey,
Double.NaN);
if (Double.isNaN(drainRateMa)) {
Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(txKey));
return Double.NaN;
}
final double consumptionMah = drainRateMa * txDurationMs / MILLIS_IN_HOUR;
if (DEBUG) {
Log.d(TAG, "Calculated TX consumption " + consumptionMah + " mAH from a drain rate of "
+ drainRateMa + " mA and a duration of " + txDurationMs + " ms for "
+ ModemPowerProfile.keyToString((int) txKey));
}
return consumptionMah;
}
/**
* Calculates active transmit radio power consumption (in milliamp-hours) from the given state's
* duration.
*/
public double calcInactiveStatePowerMah(long sleepDurationMs, long idleDurationMs) {
if (mSleepPowerEstimator == null || mIdlePowerEstimator == null) return Double.NaN;
final double sleepConsumptionMah = mSleepPowerEstimator.calculatePower(sleepDurationMs);
final double idleConsumptionMah = mIdlePowerEstimator.calculatePower(idleDurationMs);
if (DEBUG) {
Log.d(TAG, "Calculated sleep consumption " + sleepConsumptionMah
+ " mAH from a duration of " + sleepDurationMs + " ms and idle consumption "
+ idleConsumptionMah + " mAH from a duration of " + idleDurationMs);
}
return sleepConsumptionMah + idleConsumptionMah;
}
/**
* 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);
}
}