1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.power.stats;
17 
18 import android.annotation.Nullable;
19 import android.os.BatteryConsumer;
20 import android.os.BatteryStats;
21 import android.os.BatteryUsageStats;
22 import android.os.BatteryUsageStatsQuery;
23 import android.os.UidBatteryConsumer;
24 import android.telephony.CellSignalStrength;
25 import android.telephony.ServiceState;
26 import android.util.Log;
27 import android.util.LongArrayQueue;
28 import android.util.SparseArray;
29 
30 import com.android.internal.os.PowerProfile;
31 import com.android.internal.power.ModemPowerProfile;
32 
33 import java.util.ArrayList;
34 
35 public class MobileRadioPowerCalculator extends PowerCalculator {
36     private static final String TAG = "MobRadioPowerCalculator";
37     private static final boolean DEBUG = PowerCalculator.DEBUG;
38 
39     private static final double MILLIS_IN_HOUR = 1000.0 * 60 * 60;
40 
41     private static final int NUM_SIGNAL_STRENGTH_LEVELS =
42             CellSignalStrength.getNumSignalStrengthLevels();
43 
44     private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0];
45     private static final int IGNORE = -1;
46 
47     private final UsageBasedPowerEstimator mActivePowerEstimator; // deprecated
48     private final UsageBasedPowerEstimator[] mIdlePowerEstimators =
49             new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS]; // deprecated
50     private final UsageBasedPowerEstimator mScanPowerEstimator; // deprecated
51 
52     @Nullable
53     private final UsageBasedPowerEstimator mSleepPowerEstimator;
54     @Nullable
55     private final UsageBasedPowerEstimator mIdlePowerEstimator;
56 
57     private final PowerProfile mPowerProfile;
58 
59     private static class PowerAndDuration {
60         public long remainingDurationMs;
61         public double remainingPowerMah;
62         public long totalAppDurationMs;
63         public double totalAppPowerMah;
64     }
65 
MobileRadioPowerCalculator(PowerProfile profile)66     public MobileRadioPowerCalculator(PowerProfile profile) {
67         mPowerProfile = profile;
68 
69         final double sleepDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
70                 PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
71                 Double.NaN);
72         if (Double.isNaN(sleepDrainRateMa)) {
73             mSleepPowerEstimator = null;
74         } else {
75             mSleepPowerEstimator = new UsageBasedPowerEstimator(sleepDrainRateMa);
76         }
77 
78         final double idleDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
79                 PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE,
80                 Double.NaN);
81         if (Double.isNaN(idleDrainRateMa)) {
82             mIdlePowerEstimator = null;
83         } else {
84             mIdlePowerEstimator = new UsageBasedPowerEstimator(idleDrainRateMa);
85         }
86 
87         // Instantiate legacy power estimators
88         double powerRadioActiveMa =
89                 profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, Double.NaN);
90         if (Double.isNaN(powerRadioActiveMa)) {
91             double sum = 0;
92             sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
93             for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
94                 sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
95             }
96             powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1);
97         }
98         mActivePowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa);
99 
100         if (!Double.isNaN(
101                 profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, Double.NaN))) {
102             for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
103                 mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(
104                         profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i));
105             }
106         } else {
107             double idle = profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE);
108 
109             // Magical calculations preserved for historical compatibility
110             mIdlePowerEstimators[0] = new UsageBasedPowerEstimator(idle * 25 / 180);
111             for (int i = 1; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
112                 mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(Math.max(1, idle / 256));
113             }
114         }
115 
116         mScanPowerEstimator = new UsageBasedPowerEstimator(
117                 profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_SCANNING, 0));
118     }
119 
120     @Override
isPowerComponentSupported(@atteryConsumer.PowerComponent int powerComponent)121     public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
122         return powerComponent == BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO;
123     }
124 
125     @Override
calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query)126     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
127             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
128 
129         PowerAndDuration total = new PowerAndDuration();
130 
131         final long totalConsumptionUC = batteryStats.getMobileRadioEnergyConsumptionUC();
132         final int powerModel = getPowerModel(totalConsumptionUC, query);
133 
134         final double totalActivePowerMah;
135         final ArrayList<UidBatteryConsumer.Builder> apps;
136         final LongArrayQueue appDurationsMs;
137         if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
138             // EnergyConsumer is available, don't bother calculating power.
139             totalActivePowerMah = Double.NaN;
140             apps = null;
141             appDurationsMs = null;
142         } else {
143             totalActivePowerMah = calculateActiveModemPowerMah(batteryStats, rawRealtimeUs);
144             apps = new ArrayList<>();
145             appDurationsMs = new LongArrayQueue();
146         }
147 
148         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
149                 builder.getUidBatteryConsumerBuilders();
150         BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
151 
152         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
153             final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
154             final BatteryStats.Uid uid = app.getBatteryStatsUid();
155             if (keys == UNINITIALIZED_KEYS) {
156                 if (query.isProcessStateDataNeeded()) {
157                     keys = app.getKeys(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
158                 } else {
159                     keys = null;
160                 }
161             }
162 
163             // Sum and populate each app's active radio duration.
164             final long radioActiveDurationMs = calculateDuration(uid,
165                     BatteryStats.STATS_SINCE_CHARGED);
166             if (!app.isVirtualUid()) {
167                 total.totalAppDurationMs += radioActiveDurationMs;
168             }
169             app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
170                     radioActiveDurationMs);
171 
172             if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
173                 // EnergyConsumer is available, populate the consumed power now.
174                 final long appConsumptionUC = uid.getMobileRadioEnergyConsumptionUC();
175                 if (appConsumptionUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
176                     final double appConsumptionMah = uCtoMah(appConsumptionUC);
177                     if (!app.isVirtualUid()) {
178                         total.totalAppPowerMah += appConsumptionMah;
179                     }
180                     app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
181                             appConsumptionMah, powerModel);
182 
183                     if (query.isProcessStateDataNeeded() && keys != null) {
184                         for (BatteryConsumer.Key key : keys) {
185                             final int processState = key.processState;
186                             if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
187                                 // Already populated with the total across all process states
188                                 continue;
189                             }
190                             final long consumptionInStateUc =
191                                     uid.getMobileRadioEnergyConsumptionUC(processState);
192                             final double powerInStateMah = uCtoMah(consumptionInStateUc);
193                             app.setConsumedPower(key, powerInStateMah, powerModel);
194                         }
195                     }
196                 }
197             } else {
198                 // Cache the app and its active duration for later calculations.
199                 apps.add(app);
200                 appDurationsMs.addLast(radioActiveDurationMs);
201             }
202         }
203 
204         long totalActiveDurationMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
205                 BatteryStats.STATS_SINCE_CHARGED) / 1000;
206         if (totalActiveDurationMs < total.totalAppDurationMs) {
207             totalActiveDurationMs = total.totalAppDurationMs;
208         }
209 
210         if (powerModel != BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
211             // Need to smear the calculated total active power across the apps based on app
212             // active durations.
213             final int appSize = apps.size();
214             for (int i = 0; i < appSize; i++) {
215                 final UidBatteryConsumer.Builder app = apps.get(i);
216                 final long activeDurationMs = appDurationsMs.get(i);
217 
218                 // Proportionally attribute radio power consumption based on active duration.
219                 final double appConsumptionMah;
220                 if (totalActiveDurationMs == 0.0) {
221                     appConsumptionMah = 0.0;
222                 } else {
223                     appConsumptionMah =
224                             (totalActivePowerMah * activeDurationMs) / totalActiveDurationMs;
225                 }
226 
227                 if (!app.isVirtualUid()) {
228                     total.totalAppPowerMah += appConsumptionMah;
229                 }
230                 app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
231                         appConsumptionMah, powerModel);
232 
233                 if (query.isProcessStateDataNeeded() && keys != null) {
234                     final BatteryStats.Uid uid = app.getBatteryStatsUid();
235                     for (BatteryConsumer.Key key : keys) {
236                         final int processState = key.processState;
237                         if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
238                             // Already populated with the total across all process states
239                             continue;
240                         }
241 
242                         final long durationInStateMs =
243                                 uid.getMobileRadioActiveTimeInProcessState(processState) / 1000;
244                         // Proportionally attribute per process state radio power consumption
245                         // based on time state duration.
246                         final double powerInStateMah;
247                         if (activeDurationMs == 0.0) {
248                             powerInStateMah = 0.0;
249                         } else {
250                             powerInStateMah =
251                                     (appConsumptionMah * durationInStateMs) / activeDurationMs;
252                         }
253                         app.setConsumedPower(key, powerInStateMah, powerModel);
254                     }
255                 }
256             }
257         }
258 
259         total.remainingDurationMs = totalActiveDurationMs - total.totalAppDurationMs;
260 
261         // Calculate remaining power consumption.
262         if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
263             total.remainingPowerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
264             if (total.remainingPowerMah < 0) total.remainingPowerMah = 0;
265         } else {
266             // Smear unattributed active time and add it to the remaining power consumption.
267             if (totalActiveDurationMs != 0) {
268                 total.remainingPowerMah +=
269                         (totalActivePowerMah * total.remainingDurationMs) / totalActiveDurationMs;
270             }
271 
272             // Calculate the inactive modem power consumption.
273             final BatteryStats.ControllerActivityCounter modemActivity =
274                     batteryStats.getModemControllerActivity();
275             double inactivePowerMah = Double.NaN;
276             if (modemActivity != null) {
277                 final long sleepDurationMs = modemActivity.getSleepTimeCounter().getCountLocked(
278                         BatteryStats.STATS_SINCE_CHARGED);
279                 final long idleDurationMs = modemActivity.getIdleTimeCounter().getCountLocked(
280                         BatteryStats.STATS_SINCE_CHARGED);
281                 inactivePowerMah = calcInactiveStatePowerMah(sleepDurationMs, idleDurationMs);
282             }
283             if (Double.isNaN(inactivePowerMah)) {
284                 // Modem activity counters unavailable. Use legacy calculations for inactive usage.
285                 final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
286                         BatteryStats.STATS_SINCE_CHARGED) / 1000;
287                 inactivePowerMah = calcScanTimePowerMah(scanningTimeMs);
288                 for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
289                     long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
290                             BatteryStats.STATS_SINCE_CHARGED) / 1000;
291                     inactivePowerMah += calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
292                 }
293             }
294             if (!Double.isNaN(inactivePowerMah)) {
295                 total.remainingPowerMah += inactivePowerMah;
296             }
297 
298         }
299 
300         if (total.remainingPowerMah != 0 || total.totalAppPowerMah != 0) {
301             builder.getAggregateBatteryConsumerBuilder(
302                             BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
303                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
304                             total.remainingDurationMs + total.totalAppDurationMs)
305                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
306                             total.remainingPowerMah + total.totalAppPowerMah, powerModel);
307 
308             builder.getAggregateBatteryConsumerBuilder(
309                             BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
310                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
311                             total.totalAppDurationMs)
312                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
313                             total.totalAppPowerMah, powerModel);
314         }
315     }
316 
calculateDuration(BatteryStats.Uid u, int statsType)317     private long calculateDuration(BatteryStats.Uid u, int statsType) {
318         return u.getMobileRadioActiveTime(statsType) / 1000;
319     }
320 
calculateActiveModemPowerMah(BatteryStats bs, long elapsedRealtimeUs)321     private double calculateActiveModemPowerMah(BatteryStats bs, long elapsedRealtimeUs) {
322         final long elapsedRealtimeMs = elapsedRealtimeUs / 1000;
323         final int txLvlCount = NUM_SIGNAL_STRENGTH_LEVELS;
324         double consumptionMah = 0.0;
325 
326         if (DEBUG) {
327             Log.d(TAG, "Calculating radio power consumption at elapased real timestamp : "
328                     + elapsedRealtimeMs + " ms");
329         }
330 
331         boolean hasConstants = false;
332 
333         for (int rat = 0; rat < BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
334             final int freqCount = rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR
335                     ? ServiceState.FREQUENCY_RANGE_COUNT : 1;
336             for (int freq = 0; freq < freqCount; freq++) {
337                 for (int txLvl = 0; txLvl < txLvlCount; txLvl++) {
338                     final long txDurationMs = bs.getActiveTxRadioDurationMs(rat, freq, txLvl,
339                             elapsedRealtimeMs);
340                     if (txDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
341                         continue;
342                     }
343                     final double txConsumptionMah = calcTxStatePowerMah(rat, freq, txLvl,
344                             txDurationMs);
345                     if (Double.isNaN(txConsumptionMah)) {
346                         continue;
347                     }
348                     hasConstants = true;
349                     consumptionMah += txConsumptionMah;
350                 }
351 
352                 final long rxDurationMs = bs.getActiveRxRadioDurationMs(rat, freq,
353                         elapsedRealtimeMs);
354                 if (rxDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
355                     continue;
356                 }
357                 final double rxConsumptionMah = calcRxStatePowerMah(rat, freq, rxDurationMs);
358                 if (Double.isNaN(rxConsumptionMah)) {
359                     continue;
360                 }
361                 hasConstants = true;
362                 consumptionMah += rxConsumptionMah;
363             }
364         }
365 
366         if (!hasConstants) {
367             final long radioActiveDurationMs = bs.getMobileRadioActiveTime(elapsedRealtimeUs,
368                     BatteryStats.STATS_SINCE_CHARGED) / 1000;
369             if (DEBUG) {
370                 Log.d(TAG,
371                         "Failed to calculate radio power consumption. Reattempted with legacy "
372                                 + "method. Radio active duration : "
373                                 + radioActiveDurationMs + " ms");
374             }
375             if (radioActiveDurationMs > 0) {
376                 consumptionMah = calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
377             } else {
378                 consumptionMah = 0.0;
379             }
380         }
381 
382         if (DEBUG) {
383             Log.d(TAG, "Total active radio power consumption calculated to be " + consumptionMah
384                     + " mAH.");
385         }
386 
387         return consumptionMah;
388     }
389 
390     /**
391      * Calculates active receive radio power consumption (in milliamp-hours) from the given state's
392      * duration.
393      */
calcRxStatePowerMah(@atteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange, long rxDurationMs)394     public double calcRxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
395             @ServiceState.FrequencyRange int freqRange, long rxDurationMs) {
396         final long rxKey = ModemPowerProfile.getAverageBatteryDrainKey(
397                 ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat, freqRange, IGNORE);
398         final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(rxKey,
399                 Double.NaN);
400         if (Double.isNaN(drainRateMa)) {
401             Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(rxKey));
402             return Double.NaN;
403         }
404 
405         final double consumptionMah = drainRateMa * rxDurationMs / MILLIS_IN_HOUR;
406         if (DEBUG) {
407             Log.d(TAG, "Calculated RX consumption " + consumptionMah + " mAH from a drain rate of "
408                     + drainRateMa + " mA and a duration of " + rxDurationMs + " ms for "
409                     + ModemPowerProfile.keyToString((int) rxKey));
410         }
411         return consumptionMah;
412     }
413 
414     /**
415      * Calculates active transmit radio power consumption (in milliamp-hours) from the given state's
416      * duration.
417      */
calcTxStatePowerMah(@atteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange, int txLevel, long txDurationMs)418     public double calcTxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
419             @ServiceState.FrequencyRange int freqRange, int txLevel, long txDurationMs) {
420         final long txKey = ModemPowerProfile.getAverageBatteryDrainKey(
421                 ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat, freqRange, txLevel);
422         final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(txKey,
423                 Double.NaN);
424         if (Double.isNaN(drainRateMa)) {
425             Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(txKey));
426             return Double.NaN;
427         }
428 
429         final double consumptionMah = drainRateMa * txDurationMs / MILLIS_IN_HOUR;
430         if (DEBUG) {
431             Log.d(TAG, "Calculated TX consumption " + consumptionMah + " mAH from a drain rate of "
432                     + drainRateMa + " mA and a duration of " + txDurationMs + " ms for "
433                     + ModemPowerProfile.keyToString((int) txKey));
434         }
435         return consumptionMah;
436     }
437 
438     /**
439      * Calculates active transmit radio power consumption (in milliamp-hours) from the given state's
440      * duration.
441      */
calcInactiveStatePowerMah(long sleepDurationMs, long idleDurationMs)442     public double calcInactiveStatePowerMah(long sleepDurationMs, long idleDurationMs) {
443         if (mSleepPowerEstimator == null || mIdlePowerEstimator == null) return Double.NaN;
444         final double sleepConsumptionMah = mSleepPowerEstimator.calculatePower(sleepDurationMs);
445         final double idleConsumptionMah = mIdlePowerEstimator.calculatePower(idleDurationMs);
446         if (DEBUG) {
447             Log.d(TAG, "Calculated sleep consumption " + sleepConsumptionMah
448                     + " mAH from a duration of " + sleepDurationMs + " ms and idle consumption "
449                     + idleConsumptionMah + " mAH from a duration of " + idleDurationMs);
450         }
451         return sleepConsumptionMah + idleConsumptionMah;
452     }
453 
454     /**
455      * Calculates active radio power consumption (in milliamp-hours) from active radio duration.
456      */
calcPowerFromRadioActiveDurationMah(long radioActiveDurationMs)457     public double calcPowerFromRadioActiveDurationMah(long radioActiveDurationMs) {
458         return mActivePowerEstimator.calculatePower(radioActiveDurationMs);
459     }
460 
461     /**
462      * Calculates idle radio power consumption (in milliamp-hours) for time spent at a cell signal
463      * strength level.
464      * see {@link CellSignalStrength#getNumSignalStrengthLevels()}
465      */
calcIdlePowerAtSignalStrengthMah(long strengthTimeMs, int strengthLevel)466     public double calcIdlePowerAtSignalStrengthMah(long strengthTimeMs, int strengthLevel) {
467         return mIdlePowerEstimators[strengthLevel].calculatePower(strengthTimeMs);
468     }
469 
470     /**
471      * Calculates radio scan power consumption (in milliamp-hours) from scan time.
472      */
calcScanTimePowerMah(long scanningTimeMs)473     public double calcScanTimePowerMah(long scanningTimeMs) {
474         return mScanPowerEstimator.calculatePower(scanningTimeMs);
475     }
476 }
477