blob: 776a70545df496095af41bf077cc9654ab15eb57 [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.internal.os;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Process;
import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
import java.util.List;
/**
* WiFi power calculator for when BatteryStats supports energy reporting
* from the WiFi controller.
*/
public class WifiPowerCalculator extends PowerCalculator {
private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
private static final String TAG = "WifiPowerCalculator";
private final UsageBasedPowerEstimator mIdlePowerEstimator;
private final UsageBasedPowerEstimator mTxPowerEstimator;
private final UsageBasedPowerEstimator mRxPowerEstimator;
private final UsageBasedPowerEstimator mPowerOnPowerEstimator;
private final UsageBasedPowerEstimator mScanPowerEstimator;
private final UsageBasedPowerEstimator mBatchScanPowerEstimator;
private final boolean mHasWifiPowerController;
private final double mWifiPowerPerPacket;
private static class PowerDurationAndTraffic {
public double powerMah;
public long durationMs;
public long wifiRxPackets;
public long wifiTxPackets;
public long wifiRxBytes;
public long wifiTxBytes;
}
public WifiPowerCalculator(PowerProfile profile) {
mPowerOnPowerEstimator = new UsageBasedPowerEstimator(
profile.getAveragePower(PowerProfile.POWER_WIFI_ON));
mScanPowerEstimator = new UsageBasedPowerEstimator(
profile.getAveragePower(PowerProfile.POWER_WIFI_SCAN));
mBatchScanPowerEstimator = new UsageBasedPowerEstimator(
profile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN));
mIdlePowerEstimator = new UsageBasedPowerEstimator(
profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE));
mTxPowerEstimator = new UsageBasedPowerEstimator(
profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX));
mRxPowerEstimator = new UsageBasedPowerEstimator(
profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX));
mWifiPowerPerPacket = getWifiPowerPerPacket(profile);
mHasWifiPowerController =
mIdlePowerEstimator.isSupported() && mTxPowerEstimator.isSupported()
&& mRxPowerEstimator.isSupported();
}
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
long totalAppDurationMs = 0;
double totalAppPowerMah = 0;
final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic();
final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
builder.getUidBatteryConsumerBuilders();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
final long consumptionUC =
app.getBatteryStatsUid().getWifiMeasuredBatteryConsumptionUC();
final int powerModel = getPowerModel(consumptionUC, query);
calculateApp(powerDurationAndTraffic, app.getBatteryStatsUid(), powerModel,
rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED,
batteryStats.hasWifiActivityReporting(), consumptionUC);
totalAppDurationMs += powerDurationAndTraffic.durationMs;
totalAppPowerMah += powerDurationAndTraffic.powerMah;
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI,
powerDurationAndTraffic.durationMs);
app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI,
powerDurationAndTraffic.powerMah, powerModel);
}
final long consumptionUC = batteryStats.getWifiMeasuredBatteryConsumptionUC();
final int powerModel = getPowerModel(consumptionUC, query);
calculateRemaining(powerDurationAndTraffic, powerModel, batteryStats, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED, batteryStats.hasWifiActivityReporting(),
totalAppDurationMs, totalAppPowerMah, consumptionUC);
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI,
powerDurationAndTraffic.durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI,
totalAppPowerMah + powerDurationAndTraffic.powerMah, powerModel);
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI,
totalAppPowerMah, powerModel);
}
/**
* We do per-app blaming of WiFi activity. If energy info is reported from the controller,
* then only the WiFi process gets blamed here since we normalize power calculations and
* assign all the power drain to apps. If energy info is not reported, we attribute the
* difference between total running time of WiFi for all apps and the actual running time
* of WiFi to the WiFi subsystem.
*/
@Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0);
long totalAppDurationMs = 0;
double totalAppPowerMah = 0;
final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic();
for (int i = sippers.size() - 1; i >= 0; i--) {
final BatterySipper app = sippers.get(i);
if (app.drainType == BatterySipper.DrainType.APP) {
final long consumptionUC =
app.uidObj.getWifiMeasuredBatteryConsumptionUC();
final int powerModel = getPowerModel(consumptionUC);
calculateApp(powerDurationAndTraffic, app.uidObj, powerModel, rawRealtimeUs,
statsType, batteryStats.hasWifiActivityReporting(), consumptionUC);
totalAppDurationMs += powerDurationAndTraffic.durationMs;
totalAppPowerMah += powerDurationAndTraffic.powerMah;
app.wifiPowerMah = powerDurationAndTraffic.powerMah;
app.wifiRunningTimeMs = powerDurationAndTraffic.durationMs;
app.wifiRxBytes = powerDurationAndTraffic.wifiRxBytes;
app.wifiRxPackets = powerDurationAndTraffic.wifiRxPackets;
app.wifiTxBytes = powerDurationAndTraffic.wifiTxBytes;
app.wifiTxPackets = powerDurationAndTraffic.wifiTxPackets;
if (app.getUid() == Process.WIFI_UID) {
if (DEBUG) Log.d(TAG, "WiFi adding sipper " + app + ": cpu=" + app.cpuTimeMs);
app.isAggregated = true;
bs.add(app);
}
}
}
final long consumptionUC = batteryStats.getWifiMeasuredBatteryConsumptionUC();
final int powerModel = getPowerModel(consumptionUC);
calculateRemaining(powerDurationAndTraffic, powerModel, batteryStats, rawRealtimeUs,
statsType, batteryStats.hasWifiActivityReporting(), totalAppDurationMs,
totalAppPowerMah, consumptionUC);
bs.wifiRunningTimeMs += powerDurationAndTraffic.durationMs;
bs.wifiPowerMah += powerDurationAndTraffic.powerMah;
if (bs.sumPower() > 0) {
sippers.add(bs);
}
}
private void calculateApp(PowerDurationAndTraffic powerDurationAndTraffic,
BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel,
long rawRealtimeUs, int statsType, boolean hasWifiActivityReporting,
long consumptionUC) {
powerDurationAndTraffic.wifiRxPackets = u.getNetworkActivityPackets(
BatteryStats.NETWORK_WIFI_RX_DATA,
statsType);
powerDurationAndTraffic.wifiTxPackets = u.getNetworkActivityPackets(
BatteryStats.NETWORK_WIFI_TX_DATA,
statsType);
powerDurationAndTraffic.wifiRxBytes = u.getNetworkActivityBytes(
BatteryStats.NETWORK_WIFI_RX_DATA,
statsType);
powerDurationAndTraffic.wifiTxBytes = u.getNetworkActivityBytes(
BatteryStats.NETWORK_WIFI_TX_DATA,
statsType);
if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
powerDurationAndTraffic.powerMah = uCtoMah(consumptionUC);
}
if (hasWifiActivityReporting && mHasWifiPowerController) {
final BatteryStats.ControllerActivityCounter counter = u.getWifiControllerActivity();
if (counter != null) {
final long idleTime = counter.getIdleTimeCounter().getCountLocked(statsType);
final long txTime = counter.getTxTimeCounters()[0].getCountLocked(statsType);
final long rxTime = counter.getRxTimeCounter().getCountLocked(statsType);
powerDurationAndTraffic.durationMs = idleTime + rxTime + txTime;
if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
powerDurationAndTraffic.powerMah
= calcPowerFromControllerDataMah(rxTime, txTime, idleTime);
}
if (DEBUG && powerDurationAndTraffic.powerMah != 0) {
Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime
+ "ms tx=" + txTime + "ms power=" + formatCharge(
powerDurationAndTraffic.powerMah));
}
}
} else {
final long wifiRunningTime = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000;
powerDurationAndTraffic.durationMs = wifiRunningTime;
if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000;
long batchTimeMs = 0;
for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
batchTimeMs += u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000;
}
powerDurationAndTraffic.powerMah = calcPowerWithoutControllerDataMah(
powerDurationAndTraffic.wifiRxPackets,
powerDurationAndTraffic.wifiTxPackets,
wifiRunningTime, wifiScanTimeMs, batchTimeMs);
}
if (DEBUG && powerDurationAndTraffic.powerMah != 0) {
Log.d(TAG, "UID " + u.getUid() + ": power=" + formatCharge(
powerDurationAndTraffic.powerMah));
}
}
}
private void calculateRemaining(PowerDurationAndTraffic powerDurationAndTraffic,
@BatteryConsumer.PowerModel int powerModel, BatteryStats stats, long rawRealtimeUs,
int statsType, boolean hasWifiActivityReporting, long totalAppDurationMs,
double totalAppPowerMah, long consumptionUC) {
long totalDurationMs;
double totalPowerMah = 0;
if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
totalPowerMah = uCtoMah(consumptionUC);
}
if (hasWifiActivityReporting && mHasWifiPowerController) {
final BatteryStats.ControllerActivityCounter counter =
stats.getWifiControllerActivity();
final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType);
final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType);
final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType);
totalDurationMs = idleTimeMs + rxTimeMs + txTimeMs;
if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
totalPowerMah = counter.getPowerCounter().getCountLocked(statsType)
/ (double) (1000 * 60 * 60);
if (totalPowerMah == 0) {
// Some controllers do not report power drain, so we can calculate it here.
totalPowerMah = calcPowerFromControllerDataMah(rxTimeMs, txTimeMs, idleTimeMs);
}
}
} else {
totalDurationMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType) / 1000;
if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
totalPowerMah = calcGlobalPowerWithoutControllerDataMah(totalDurationMs);
}
}
powerDurationAndTraffic.durationMs = Math.max(0, totalDurationMs - totalAppDurationMs);
powerDurationAndTraffic.powerMah = Math.max(0, totalPowerMah - totalAppPowerMah);
if (DEBUG) {
Log.d(TAG, "left over WiFi power: " + formatCharge(powerDurationAndTraffic.powerMah));
}
}
/** Returns (global or uid) estimated wifi power used using WifiControllerActivity data. */
public double calcPowerFromControllerDataMah(long rxTimeMs, long txTimeMs, long idleTimeMs) {
return mRxPowerEstimator.calculatePower(rxTimeMs)
+ mTxPowerEstimator.calculatePower(txTimeMs)
+ mIdlePowerEstimator.calculatePower(idleTimeMs);
}
/** Returns per-uid estimated wifi power used using non-WifiControllerActivity data. */
public double calcPowerWithoutControllerDataMah(long rxPackets, long txPackets,
long wifiRunningTimeMs, long wifiScanTimeMs, long wifiBatchScanTimeMs) {
return
(rxPackets + txPackets) * mWifiPowerPerPacket
+ mPowerOnPowerEstimator.calculatePower(wifiRunningTimeMs)
+ mScanPowerEstimator.calculatePower(wifiScanTimeMs)
+ mBatchScanPowerEstimator.calculatePower(wifiBatchScanTimeMs);
}
/** Returns global estimated wifi power used using non-WifiControllerActivity data. */
public double calcGlobalPowerWithoutControllerDataMah(long globalWifiRunningTimeMs) {
return mPowerOnPowerEstimator.calculatePower(globalWifiRunningTimeMs);
}
/**
* Return estimated power per Wi-Fi packet in mAh/packet where 1 packet = 2 KB.
*/
private static double getWifiPowerPerPacket(PowerProfile profile) {
// TODO(b/179392913): Extract average bit rates from system
final long wifiBps = 1000000;
final double averageWifiActivePower =
profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) / 3600;
return averageWifiActivePower / (((double) wifiBps) / 8 / 2048);
}
}