1 /*
2  * Copyright (C) 2024 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.content.pm.PackageManager;
19 import android.hardware.power.stats.EnergyConsumerType;
20 import android.net.NetworkStats;
21 import android.net.wifi.WifiManager;
22 import android.os.BatteryConsumer;
23 import android.os.Handler;
24 import android.os.PersistableBundle;
25 import android.os.connectivity.WifiActivityEnergyInfo;
26 import android.util.Slog;
27 import android.util.SparseArray;
28 
29 import com.android.internal.os.Clock;
30 import com.android.internal.os.PowerStats;
31 
32 import java.util.Arrays;
33 import java.util.List;
34 import java.util.concurrent.CompletableFuture;
35 import java.util.concurrent.TimeUnit;
36 import java.util.function.IntSupplier;
37 import java.util.function.Supplier;
38 
39 public class WifiPowerStatsCollector extends PowerStatsCollector {
40     private static final String TAG = "WifiPowerStatsCollector";
41 
42     private static final long WIFI_ACTIVITY_REQUEST_TIMEOUT = 20000;
43 
44     private static final long ENERGY_UNSPECIFIED = -1;
45 
46     interface WifiStatsRetriever {
47         interface Callback {
onWifiScanTime(int uid, long scanTimeMs, long batchScanTimeMs)48             void onWifiScanTime(int uid, long scanTimeMs, long batchScanTimeMs);
49         }
50 
retrieveWifiScanTimes(Callback callback)51         void retrieveWifiScanTimes(Callback callback);
getWifiActiveDuration()52         long getWifiActiveDuration();
53     }
54 
55     interface Injector {
getHandler()56         Handler getHandler();
getClock()57         Clock getClock();
getUidResolver()58         PowerStatsUidResolver getUidResolver();
getPowerStatsCollectionThrottlePeriod(String powerComponentName)59         long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
getPackageManager()60         PackageManager getPackageManager();
getConsumedEnergyRetriever()61         ConsumedEnergyRetriever getConsumedEnergyRetriever();
getVoltageSupplier()62         IntSupplier getVoltageSupplier();
getWifiNetworkStatsSupplier()63         Supplier<NetworkStats> getWifiNetworkStatsSupplier();
getWifiManager()64         WifiManager getWifiManager();
getWifiStatsRetriever()65         WifiStatsRetriever getWifiStatsRetriever();
66     }
67 
68     private final Injector mInjector;
69 
70     private WifiPowerStatsLayout mLayout;
71     private boolean mIsInitialized;
72     private boolean mPowerReportingSupported;
73 
74     private PowerStats mPowerStats;
75     private long[] mDeviceStats;
76     private volatile WifiManager mWifiManager;
77     private volatile Supplier<NetworkStats> mNetworkStatsSupplier;
78     private volatile WifiStatsRetriever mWifiStatsRetriever;
79     private ConsumedEnergyRetriever mConsumedEnergyRetriever;
80     private IntSupplier mVoltageSupplier;
81     private int[] mEnergyConsumerIds = new int[0];
82     private WifiActivityEnergyInfo mLastWifiActivityInfo =
83             new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
84     private NetworkStats mLastNetworkStats;
85     private long[] mLastConsumedEnergyUws;
86     private int mLastVoltageMv;
87 
88     private static class WifiScanTimes {
89         public long basicScanTimeMs;
90         public long batchedScanTimeMs;
91     }
92     private final WifiScanTimes mScanTimes = new WifiScanTimes();
93     private final SparseArray<WifiScanTimes> mLastScanTimes = new SparseArray<>();
94     private long mLastWifiActiveDuration;
95 
WifiPowerStatsCollector(Injector injector)96     WifiPowerStatsCollector(Injector injector) {
97         super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
98                         BatteryConsumer.powerComponentIdToString(
99                                 BatteryConsumer.POWER_COMPONENT_WIFI)),
100                 injector.getUidResolver(), injector.getClock());
101         mInjector = injector;
102     }
103 
104     @Override
setEnabled(boolean enabled)105     public void setEnabled(boolean enabled) {
106         if (enabled) {
107             PackageManager packageManager = mInjector.getPackageManager();
108             super.setEnabled(packageManager != null
109                     && packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI));
110         } else {
111             super.setEnabled(false);
112         }
113     }
114 
ensureInitialized()115     private boolean ensureInitialized() {
116         if (mIsInitialized) {
117             return true;
118         }
119 
120         if (!isEnabled()) {
121             return false;
122         }
123 
124         mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
125         mVoltageSupplier = mInjector.getVoltageSupplier();
126         mWifiManager = mInjector.getWifiManager();
127         mNetworkStatsSupplier = mInjector.getWifiNetworkStatsSupplier();
128         mWifiStatsRetriever = mInjector.getWifiStatsRetriever();
129         mPowerReportingSupported =
130                 mWifiManager != null && mWifiManager.isEnhancedPowerReportingSupported();
131 
132         mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI);
133         mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length];
134         Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
135 
136         mLayout = new WifiPowerStatsLayout();
137         mLayout.addDeviceWifiActivity(mPowerReportingSupported);
138         mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length);
139         mLayout.addUidNetworkStats();
140         mLayout.addDeviceSectionUsageDuration();
141         mLayout.addDeviceSectionPowerEstimate();
142         mLayout.addUidSectionPowerEstimate();
143 
144         PersistableBundle extras = new PersistableBundle();
145         mLayout.toExtras(extras);
146         PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor(
147                 BatteryConsumer.POWER_COMPONENT_WIFI, mLayout.getDeviceStatsArrayLength(),
148                 null, 0, mLayout.getUidStatsArrayLength(),
149                 extras);
150         mPowerStats = new PowerStats(powerStatsDescriptor);
151         mDeviceStats = mPowerStats.stats;
152 
153         mIsInitialized = true;
154         return true;
155     }
156 
157     @Override
collectStats()158     protected PowerStats collectStats() {
159         if (!ensureInitialized()) {
160             return null;
161         }
162 
163         if (mPowerReportingSupported) {
164             collectWifiActivityInfo();
165         } else {
166             collectWifiActivityStats();
167         }
168         collectNetworkStats();
169         collectWifiScanTime();
170 
171         if (mEnergyConsumerIds.length != 0) {
172             collectEnergyConsumers();
173         }
174 
175         return mPowerStats;
176     }
177 
collectWifiActivityInfo()178     private void collectWifiActivityInfo() {
179         CompletableFuture<WifiActivityEnergyInfo> immediateFuture = new CompletableFuture<>();
180         mWifiManager.getWifiActivityEnergyInfoAsync(Runnable::run,
181                 immediateFuture::complete);
182 
183         WifiActivityEnergyInfo activityInfo;
184         try {
185             activityInfo = immediateFuture.get(WIFI_ACTIVITY_REQUEST_TIMEOUT,
186                     TimeUnit.MILLISECONDS);
187         } catch (Exception e) {
188             Slog.e(TAG, "Cannot acquire WifiActivityEnergyInfo", e);
189             activityInfo = null;
190         }
191 
192         if (activityInfo == null) {
193             return;
194         }
195 
196         long rxDuration = activityInfo.getControllerRxDurationMillis()
197                 - mLastWifiActivityInfo.getControllerRxDurationMillis();
198         long txDuration = activityInfo.getControllerTxDurationMillis()
199                 - mLastWifiActivityInfo.getControllerTxDurationMillis();
200         long scanDuration = activityInfo.getControllerScanDurationMillis()
201                 - mLastWifiActivityInfo.getControllerScanDurationMillis();
202         long idleDuration = activityInfo.getControllerIdleDurationMillis()
203                 - mLastWifiActivityInfo.getControllerIdleDurationMillis();
204 
205         mLayout.setDeviceRxTime(mDeviceStats, rxDuration);
206         mLayout.setDeviceTxTime(mDeviceStats, txDuration);
207         mLayout.setDeviceScanTime(mDeviceStats, scanDuration);
208         mLayout.setDeviceIdleTime(mDeviceStats, idleDuration);
209 
210         mPowerStats.durationMs = rxDuration + txDuration + scanDuration + idleDuration;
211 
212         mLastWifiActivityInfo = activityInfo;
213     }
214 
collectWifiActivityStats()215     private void collectWifiActivityStats() {
216         long duration = mWifiStatsRetriever.getWifiActiveDuration();
217         mLayout.setDeviceActiveTime(mDeviceStats, Math.max(0, duration - mLastWifiActiveDuration));
218         mLastWifiActiveDuration = duration;
219         mPowerStats.durationMs = duration;
220     }
221 
collectNetworkStats()222     private void collectNetworkStats() {
223         mPowerStats.uidStats.clear();
224 
225         NetworkStats networkStats = mNetworkStatsSupplier.get();
226         if (networkStats == null) {
227             return;
228         }
229 
230         List<BatteryStatsImpl.NetworkStatsDelta> delta =
231                 BatteryStatsImpl.computeDelta(networkStats, mLastNetworkStats);
232         mLastNetworkStats = networkStats;
233         for (int i = delta.size() - 1; i >= 0; i--) {
234             BatteryStatsImpl.NetworkStatsDelta uidDelta = delta.get(i);
235             long rxBytes = uidDelta.getRxBytes();
236             long txBytes = uidDelta.getTxBytes();
237             long rxPackets = uidDelta.getRxPackets();
238             long txPackets = uidDelta.getTxPackets();
239             if (rxBytes == 0 && txBytes == 0 && rxPackets == 0 && txPackets == 0) {
240                 continue;
241             }
242 
243             int uid = mUidResolver.mapUid(uidDelta.getUid());
244             long[] stats = mPowerStats.uidStats.get(uid);
245             if (stats == null) {
246                 stats = new long[mLayout.getUidStatsArrayLength()];
247                 mPowerStats.uidStats.put(uid, stats);
248                 mLayout.setUidRxBytes(stats, rxBytes);
249                 mLayout.setUidTxBytes(stats, txBytes);
250                 mLayout.setUidRxPackets(stats, rxPackets);
251                 mLayout.setUidTxPackets(stats, txPackets);
252             } else {
253                 mLayout.setUidRxBytes(stats, mLayout.getUidRxBytes(stats) + rxBytes);
254                 mLayout.setUidTxBytes(stats, mLayout.getUidTxBytes(stats) + txBytes);
255                 mLayout.setUidRxPackets(stats, mLayout.getUidRxPackets(stats) + rxPackets);
256                 mLayout.setUidTxPackets(stats, mLayout.getUidTxPackets(stats) + txPackets);
257             }
258         }
259     }
260 
collectWifiScanTime()261     private void collectWifiScanTime() {
262         mScanTimes.basicScanTimeMs = 0;
263         mScanTimes.batchedScanTimeMs = 0;
264         mWifiStatsRetriever.retrieveWifiScanTimes((uid, scanTimeMs, batchScanTimeMs) -> {
265             WifiScanTimes lastScanTimes = mLastScanTimes.get(uid);
266             if (lastScanTimes == null) {
267                 lastScanTimes = new WifiScanTimes();
268                 mLastScanTimes.put(uid, lastScanTimes);
269             }
270 
271             long scanTimeDelta = Math.max(0, scanTimeMs - lastScanTimes.basicScanTimeMs);
272             long batchScanTimeDelta = Math.max(0,
273                     batchScanTimeMs - lastScanTimes.batchedScanTimeMs);
274             if (scanTimeDelta != 0 || batchScanTimeDelta != 0) {
275                 mScanTimes.basicScanTimeMs += scanTimeDelta;
276                 mScanTimes.batchedScanTimeMs += batchScanTimeDelta;
277                 uid = mUidResolver.mapUid(uid);
278                 long[] stats = mPowerStats.uidStats.get(uid);
279                 if (stats == null) {
280                     stats = new long[mLayout.getUidStatsArrayLength()];
281                     mPowerStats.uidStats.put(uid, stats);
282                     mLayout.setUidScanTime(stats, scanTimeDelta);
283                     mLayout.setUidBatchScanTime(stats, batchScanTimeDelta);
284                 } else {
285                     mLayout.setUidScanTime(stats, mLayout.getUidScanTime(stats) + scanTimeDelta);
286                     mLayout.setUidBatchScanTime(stats,
287                             mLayout.getUidBatchedScanTime(stats) + batchScanTimeDelta);
288                 }
289             }
290             lastScanTimes.basicScanTimeMs = scanTimeMs;
291             lastScanTimes.batchedScanTimeMs = batchScanTimeMs;
292         });
293 
294         mLayout.setDeviceBasicScanTime(mDeviceStats, mScanTimes.basicScanTimeMs);
295         mLayout.setDeviceBatchedScanTime(mDeviceStats, mScanTimes.batchedScanTimeMs);
296     }
297 
collectEnergyConsumers()298     private void collectEnergyConsumers() {
299         int voltageMv = mVoltageSupplier.getAsInt();
300         if (voltageMv <= 0) {
301             Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
302                     + " mV) when querying energy consumers");
303             return;
304         }
305 
306         int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
307         mLastVoltageMv = voltageMv;
308 
309         long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds);
310         if (energyUws == null) {
311             return;
312         }
313 
314         for (int i = energyUws.length - 1; i >= 0; i--) {
315             long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
316                     ? energyUws[i] - mLastConsumedEnergyUws[i] : 0;
317             if (energyDelta < 0) {
318                 // Likely, restart of powerstats HAL
319                 energyDelta = 0;
320             }
321             mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage));
322             mLastConsumedEnergyUws[i] = energyUws[i];
323         }
324     }
325 
326     @Override
onUidRemoved(int uid)327     protected void onUidRemoved(int uid) {
328         super.onUidRemoved(uid);
329         mLastScanTimes.remove(uid);
330     }
331 }
332