1 /*
2  * Copyright (C) 2023 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 
17 package com.android.server.power.stats;
18 
19 import android.annotation.Nullable;
20 import android.hardware.power.stats.EnergyConsumer;
21 import android.hardware.power.stats.EnergyConsumerResult;
22 import android.hardware.power.stats.EnergyConsumerType;
23 import android.os.ConditionVariable;
24 import android.os.Handler;
25 import android.power.PowerStatsInternal;
26 import android.util.IndentingPrintWriter;
27 import android.util.Slog;
28 
29 import com.android.internal.annotations.GuardedBy;
30 import com.android.internal.os.Clock;
31 import com.android.internal.os.PowerStats;
32 
33 import java.io.PrintWriter;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.Comparator;
37 import java.util.List;
38 import java.util.concurrent.CompletableFuture;
39 import java.util.concurrent.ExecutionException;
40 import java.util.concurrent.TimeUnit;
41 import java.util.concurrent.TimeoutException;
42 import java.util.function.Consumer;
43 
44 /**
45  * Collects snapshots of power-related system statistics.
46  * <p>
47  * Instances of this class are intended to be used in a serialized fashion using
48  * the handler supplied in the constructor. Thus these objects are not thread-safe
49  * except where noted.
50  */
51 public abstract class PowerStatsCollector {
52     private static final String TAG = "PowerStatsCollector";
53     private static final int MILLIVOLTS_PER_VOLT = 1000;
54     private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
55     private final Handler mHandler;
56     protected final PowerStatsUidResolver mUidResolver;
57     protected final Clock mClock;
58     private final long mThrottlePeriodMs;
59     private final Runnable mCollectAndDeliverStats = this::collectAndDeliverStats;
60     private boolean mEnabled;
61     private long mLastScheduledUpdateMs = -1;
62 
63     @GuardedBy("this")
64     @SuppressWarnings("unchecked")
65     private volatile List<Consumer<PowerStats>> mConsumerList = Collections.emptyList();
66 
PowerStatsCollector(Handler handler, long throttlePeriodMs, PowerStatsUidResolver uidResolver, Clock clock)67     public PowerStatsCollector(Handler handler, long throttlePeriodMs,
68             PowerStatsUidResolver uidResolver, Clock clock) {
69         mHandler = handler;
70         mThrottlePeriodMs = throttlePeriodMs;
71         mUidResolver = uidResolver;
72         mUidResolver.addListener(new PowerStatsUidResolver.Listener() {
73             @Override
74             public void onIsolatedUidAdded(int isolatedUid, int parentUid) {
75             }
76 
77             @Override
78             public void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) {
79             }
80 
81             @Override
82             public void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) {
83                 mHandler.post(()->onUidRemoved(isolatedUid));
84             }
85         });
86         mClock = clock;
87     }
88 
89     /**
90      * Adds a consumer that will receive a callback every time a snapshot of stats is collected.
91      * The method is thread safe.
92      */
93     @SuppressWarnings("unchecked")
addConsumer(Consumer<PowerStats> consumer)94     public void addConsumer(Consumer<PowerStats> consumer) {
95         synchronized (this) {
96             if (mConsumerList.contains(consumer)) {
97                 return;
98             }
99 
100             List<Consumer<PowerStats>> newList = new ArrayList<>(mConsumerList);
101             newList.add(consumer);
102             mConsumerList = Collections.unmodifiableList(newList);
103         }
104     }
105 
106     /**
107      * Removes a consumer.
108      * The method is thread safe.
109      */
110     @SuppressWarnings("unchecked")
removeConsumer(Consumer<PowerStats> consumer)111     public void removeConsumer(Consumer<PowerStats> consumer) {
112         synchronized (this) {
113             List<Consumer<PowerStats>> newList = new ArrayList<>(mConsumerList);
114             newList.remove(consumer);
115             mConsumerList = Collections.unmodifiableList(newList);
116         }
117     }
118 
119     /**
120      * Should be called at most once, before the first invocation of {@link #schedule} or
121      * {@link #forceSchedule}
122      */
setEnabled(boolean enabled)123     public void setEnabled(boolean enabled) {
124         mEnabled = enabled;
125     }
126 
127     /**
128      * Returns true if the collector is enabled.
129      */
isEnabled()130     public boolean isEnabled() {
131         return mEnabled;
132     }
133 
134     @SuppressWarnings("GuardedBy")  // Field is volatile
collectAndDeliverStats()135     public void collectAndDeliverStats() {
136         PowerStats stats = collectStats();
137         if (stats == null) {
138             return;
139         }
140         List<Consumer<PowerStats>> consumerList = mConsumerList;
141         for (int i = consumerList.size() - 1; i >= 0; i--) {
142             consumerList.get(i).accept(stats);
143         }
144     }
145 
146     /**
147      * Schedules a stats snapshot collection, throttled in accordance with the
148      * {@link #mThrottlePeriodMs} parameter.
149      */
schedule()150     public boolean schedule() {
151         if (!mEnabled) {
152             return false;
153         }
154 
155         long uptimeMillis = mClock.uptimeMillis();
156         if (uptimeMillis - mLastScheduledUpdateMs < mThrottlePeriodMs
157                 && mLastScheduledUpdateMs >= 0) {
158             return false;
159         }
160         mLastScheduledUpdateMs = uptimeMillis;
161         mHandler.post(mCollectAndDeliverStats);
162         return true;
163     }
164 
165     /**
166      * Schedules an immediate snapshot collection, foregoing throttling.
167      */
forceSchedule()168     public boolean forceSchedule() {
169         if (!mEnabled) {
170             return false;
171         }
172 
173         mHandler.removeCallbacks(mCollectAndDeliverStats);
174         mHandler.postAtFrontOfQueue(mCollectAndDeliverStats);
175         return true;
176     }
177 
178     @Nullable
collectStats()179     protected abstract PowerStats collectStats();
180 
181     /**
182      * Collects a fresh stats snapshot and prints it to the supplied printer.
183      */
collectAndDump(PrintWriter pw)184     public void collectAndDump(PrintWriter pw) {
185         if (Thread.currentThread() == mHandler.getLooper().getThread()) {
186             throw new RuntimeException(
187                     "Calling this method from the handler thread would cause a deadlock");
188         }
189 
190         IndentingPrintWriter out = new IndentingPrintWriter(pw);
191         out.print(getClass().getSimpleName());
192         if (!isEnabled()) {
193             out.println(": disabled");
194             return;
195         }
196         out.println();
197 
198         ArrayList<PowerStats> collected = new ArrayList<>();
199         Consumer<PowerStats> consumer = collected::add;
200         addConsumer(consumer);
201 
202         try {
203             if (forceSchedule()) {
204                 awaitCompletion();
205             }
206         } finally {
207             removeConsumer(consumer);
208         }
209 
210         out.increaseIndent();
211         for (PowerStats stats : collected) {
212             stats.dump(out);
213         }
214         out.decreaseIndent();
215     }
216 
awaitCompletion()217     private void awaitCompletion() {
218         ConditionVariable done = new ConditionVariable();
219         mHandler.post(done::open);
220         done.block();
221     }
222 
onUidRemoved(int uid)223     protected void onUidRemoved(int uid) {
224     }
225 
226     /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
uJtoUc(long deltaEnergyUj, int avgVoltageMv)227     protected static long uJtoUc(long deltaEnergyUj, int avgVoltageMv) {
228         // To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
229         // since the last snapshot. Round off to the nearest whole long.
230         return (deltaEnergyUj * MILLIVOLTS_PER_VOLT + (avgVoltageMv / 2)) / avgVoltageMv;
231     }
232 
233     interface ConsumedEnergyRetriever {
getEnergyConsumerIds(@nergyConsumerType int energyConsumerType)234         int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType);
235 
236         @Nullable
getConsumedEnergyUws(int[] energyConsumerIds)237         long[] getConsumedEnergyUws(int[] energyConsumerIds);
238     }
239 
240     static class ConsumedEnergyRetrieverImpl implements ConsumedEnergyRetriever {
241         private final PowerStatsInternal mPowerStatsInternal;
242 
ConsumedEnergyRetrieverImpl(PowerStatsInternal powerStatsInternal)243         ConsumedEnergyRetrieverImpl(PowerStatsInternal powerStatsInternal) {
244             mPowerStatsInternal = powerStatsInternal;
245         }
246 
247         @Override
getEnergyConsumerIds(int energyConsumerType)248         public int[] getEnergyConsumerIds(int energyConsumerType) {
249             if (mPowerStatsInternal == null) {
250                 return new int[0];
251             }
252 
253             EnergyConsumer[] energyConsumerInfo = mPowerStatsInternal.getEnergyConsumerInfo();
254             if (energyConsumerInfo == null) {
255                 return new int[0];
256             }
257 
258             List<EnergyConsumer> energyConsumers = new ArrayList<>();
259             for (EnergyConsumer energyConsumer : energyConsumerInfo) {
260                 if (energyConsumer.type == energyConsumerType) {
261                     energyConsumers.add(energyConsumer);
262                 }
263             }
264             if (energyConsumers.isEmpty()) {
265                 return new int[0];
266             }
267 
268             energyConsumers.sort(Comparator.comparing(c -> c.ordinal));
269 
270             int[] ids = new int[energyConsumers.size()];
271             for (int i = 0; i < ids.length; i++) {
272                 ids[i] = energyConsumers.get(i).id;
273             }
274             return ids;
275         }
276 
277         @Override
getConsumedEnergyUws(int[] energyConsumerIds)278         public long[] getConsumedEnergyUws(int[] energyConsumerIds) {
279             CompletableFuture<EnergyConsumerResult[]> future =
280                     mPowerStatsInternal.getEnergyConsumedAsync(energyConsumerIds);
281             EnergyConsumerResult[] results = null;
282             try {
283                 results = future.get(
284                         POWER_STATS_ENERGY_CONSUMERS_TIMEOUT, TimeUnit.MILLISECONDS);
285             } catch (InterruptedException | ExecutionException | TimeoutException e) {
286                 Slog.e(TAG, "Could not obtain energy consumers from PowerStatsService", e);
287             }
288 
289             if (results == null) {
290                 return null;
291             }
292 
293             long[] energy = new long[energyConsumerIds.length];
294             for (int i = 0; i < energyConsumerIds.length; i++) {
295                 int id = energyConsumerIds[i];
296                 for (EnergyConsumerResult result : results) {
297                     if (result.id == id) {
298                         energy[i] = result.energyUWs;
299                         break;
300                     }
301                 }
302             }
303             return energy;
304         }
305     }
306 }
307