• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.NonNull;
20 import android.annotation.Nullable;
21 import android.hardware.power.stats.EnergyConsumer;
22 import android.hardware.power.stats.EnergyConsumerAttribution;
23 import android.hardware.power.stats.EnergyConsumerResult;
24 import android.hardware.power.stats.EnergyConsumerType;
25 import android.os.ConditionVariable;
26 import android.os.Handler;
27 import android.power.PowerStatsInternal;
28 import android.util.IndentingPrintWriter;
29 import android.util.Slog;
30 import android.util.SparseLongArray;
31 
32 import com.android.internal.annotations.GuardedBy;
33 import com.android.internal.os.Clock;
34 import com.android.internal.os.PowerStats;
35 import com.android.server.power.stats.format.PowerStatsLayout;
36 
37 import java.io.PrintWriter;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Collections;
41 import java.util.Comparator;
42 import java.util.List;
43 import java.util.concurrent.CompletableFuture;
44 import java.util.concurrent.ExecutionException;
45 import java.util.concurrent.TimeUnit;
46 import java.util.concurrent.TimeoutException;
47 import java.util.function.Consumer;
48 import java.util.function.IntSupplier;
49 
50 /**
51  * Collects snapshots of power-related system statistics.
52  * <p>
53  * Instances of this class are intended to be used in a serialized fashion using
54  * the handler supplied in the constructor. Thus these objects are not thread-safe
55  * except where noted.
56  */
57 public abstract class PowerStatsCollector {
58     private static final String TAG = "PowerStatsCollector";
59     private static final int MILLIVOLTS_PER_VOLT = 1000;
60     private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
61     private static final long ENERGY_UNSPECIFIED = -1;
62 
63     private final Handler mHandler;
64     protected final PowerStatsUidResolver mUidResolver;
65     protected final Clock mClock;
66     private final long mThrottlePeriodMs;
67     private final Runnable mCollectAndDeliverStats = this::collectAndDeliverStats;
68     private boolean mEnabled;
69     private long mLastScheduledUpdateMs = -1;
70 
71     @GuardedBy("this")
72     private volatile List<Consumer<PowerStats>> mConsumerList = Collections.emptyList();
73 
PowerStatsCollector(Handler handler, long throttlePeriodMs, PowerStatsUidResolver uidResolver, Clock clock)74     public PowerStatsCollector(Handler handler, long throttlePeriodMs,
75             PowerStatsUidResolver uidResolver, Clock clock) {
76         mHandler = handler;
77         mThrottlePeriodMs = throttlePeriodMs;
78         mUidResolver = uidResolver;
79         mUidResolver.addListener(new PowerStatsUidResolver.Listener() {
80             @Override
81             public void onIsolatedUidAdded(int isolatedUid, int parentUid) {
82             }
83 
84             @Override
85             public void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) {
86             }
87 
88             @Override
89             public void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) {
90                 mHandler.post(()->onUidRemoved(isolatedUid));
91             }
92         });
93         mClock = clock;
94     }
95 
96     /**
97      * Adds a consumer that will receive a callback every time a snapshot of stats is collected.
98      * The method is thread safe.
99      */
addConsumer(Consumer<PowerStats> consumer)100     public void addConsumer(Consumer<PowerStats> consumer) {
101         synchronized (this) {
102             if (mConsumerList.contains(consumer)) {
103                 return;
104             }
105 
106             List<Consumer<PowerStats>> newList = new ArrayList<>(mConsumerList);
107             newList.add(consumer);
108             mConsumerList = Collections.unmodifiableList(newList);
109         }
110     }
111 
112     /**
113      * Removes a consumer.
114      * The method is thread safe.
115      */
removeConsumer(Consumer<PowerStats> consumer)116     public void removeConsumer(Consumer<PowerStats> consumer) {
117         synchronized (this) {
118             List<Consumer<PowerStats>> newList = new ArrayList<>(mConsumerList);
119             newList.remove(consumer);
120             mConsumerList = Collections.unmodifiableList(newList);
121         }
122     }
123 
124     /**
125      * Should be called at most once, before the first invocation of {@link #schedule} or
126      * {@link #forceSchedule}
127      */
setEnabled(boolean enabled)128     public void setEnabled(boolean enabled) {
129         mEnabled = enabled;
130     }
131 
132     /**
133      * Returns true if the collector is enabled.
134      */
isEnabled()135     public boolean isEnabled() {
136         return mEnabled;
137     }
138 
139     /**
140      * Schedules a stats snapshot collection, throttled in accordance with the
141      * {@link #mThrottlePeriodMs} parameter.
142      */
schedule()143     public boolean schedule() {
144         if (!mEnabled) {
145             return false;
146         }
147 
148         long uptimeMillis = mClock.uptimeMillis();
149         if (uptimeMillis - mLastScheduledUpdateMs < mThrottlePeriodMs
150                 && mLastScheduledUpdateMs >= 0) {
151             return false;
152         }
153         mLastScheduledUpdateMs = uptimeMillis;
154         mHandler.post(mCollectAndDeliverStats);
155         return true;
156     }
157 
158     /**
159      * Schedules an immediate snapshot collection, foregoing throttling.
160      */
forceSchedule()161     public boolean forceSchedule() {
162         if (!mEnabled) {
163             return false;
164         }
165 
166         mHandler.removeCallbacks(mCollectAndDeliverStats);
167         mHandler.postAtFrontOfQueue(mCollectAndDeliverStats);
168         return true;
169     }
170 
171     /**
172      * Performs a PowerStats collection pass and delivers the result to registered consumers.
173      */
174     @SuppressWarnings("GuardedBy")  // Field is volatile
collectAndDeliverStats()175     public void collectAndDeliverStats() {
176         deliverStats(collectStats());
177     }
178 
179     @Nullable
collectStats()180     protected PowerStats collectStats() {
181         return null;
182     }
183 
184     @SuppressWarnings("GuardedBy")  // Field is volatile
deliverStats(PowerStats stats)185     protected void deliverStats(PowerStats stats) {
186         if (stats == null) {
187             return;
188         }
189 
190         List<Consumer<PowerStats>> consumerList = mConsumerList;
191         for (int i = consumerList.size() - 1; i >= 0; i--) {
192             consumerList.get(i).accept(stats);
193         }
194     }
195 
196     /**
197      * Collects a fresh stats snapshot and prints it to the supplied printer.
198      */
collectAndDump(PrintWriter pw)199     public void collectAndDump(PrintWriter pw) {
200         if (Thread.currentThread() == mHandler.getLooper().getThread()) {
201             throw new RuntimeException(
202                     "Calling this method from the handler thread would cause a deadlock");
203         }
204 
205         IndentingPrintWriter out = new IndentingPrintWriter(pw);
206         if (!isEnabled()) {
207             out.print(getClass().getSimpleName());
208             out.println(": disabled");
209             return;
210         }
211 
212         ArrayList<PowerStats> collected = new ArrayList<>();
213         Consumer<PowerStats> consumer = collected::add;
214         addConsumer(consumer);
215 
216         try {
217             if (forceSchedule()) {
218                 awaitCompletion();
219             }
220         } finally {
221             removeConsumer(consumer);
222         }
223 
224         for (PowerStats stats : collected) {
225             stats.dump(out);
226         }
227     }
228 
awaitCompletion()229     private void awaitCompletion() {
230         ConditionVariable done = new ConditionVariable();
231         mHandler.post(done::open);
232         done.block();
233     }
234 
onUidRemoved(int uid)235     protected void onUidRemoved(int uid) {
236     }
237 
238     /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
uJtoUc(long deltaEnergyUj, int avgVoltageMv)239     protected static long uJtoUc(long deltaEnergyUj, int avgVoltageMv) {
240         // To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
241         // since the last snapshot. Round off to the nearest whole long.
242         return (deltaEnergyUj * MILLIVOLTS_PER_VOLT + (avgVoltageMv / 2)) / avgVoltageMv;
243     }
244 
245     public interface ConsumedEnergyRetriever {
246 
247         @NonNull
getEnergyConsumerIds(@nergyConsumerType int energyConsumerType)248         int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType);
249 
getEnergyConsumerName(int energyConsumerId)250         String getEnergyConsumerName(int energyConsumerId);
251 
252         @Nullable
getConsumedEnergy(int[] energyConsumerIds)253         EnergyConsumerResult[] getConsumedEnergy(int[] energyConsumerIds);
254 
255         /**
256          * Returns the last known battery/charger voltage in milli-volts.
257          */
getVoltageMv()258         int getVoltageMv();
259     }
260 
261     static class ConsumedEnergyRetrieverImpl implements ConsumedEnergyRetriever {
262         private final PowerStatsInternal mPowerStatsInternal;
263         private final IntSupplier mVoltageSupplier;
264         private EnergyConsumer[] mEnergyConsumers;
265 
ConsumedEnergyRetrieverImpl(PowerStatsInternal powerStatsInternal, IntSupplier voltageSupplier)266         ConsumedEnergyRetrieverImpl(PowerStatsInternal powerStatsInternal,
267                 IntSupplier voltageSupplier) {
268             mPowerStatsInternal = powerStatsInternal;
269             mVoltageSupplier = voltageSupplier;
270         }
271 
ensureEnergyConsumers()272         private void ensureEnergyConsumers() {
273             if (mEnergyConsumers != null) {
274                 return;
275             }
276 
277             if (mPowerStatsInternal == null) {
278                 mEnergyConsumers = new EnergyConsumer[0];
279                 return;
280             }
281 
282             mEnergyConsumers = mPowerStatsInternal.getEnergyConsumerInfo();
283             if (mEnergyConsumers == null) {
284                 mEnergyConsumers = new EnergyConsumer[0];
285             }
286         }
287 
288         @NonNull
289         @Override
getEnergyConsumerIds(int energyConsumerType)290         public int[] getEnergyConsumerIds(int energyConsumerType) {
291             ensureEnergyConsumers();
292 
293             if (mEnergyConsumers.length == 0) {
294                 return new int[0];
295             }
296 
297             List<EnergyConsumer> energyConsumers = new ArrayList<>();
298             for (EnergyConsumer energyConsumer : mEnergyConsumers) {
299                 if (energyConsumer.type == energyConsumerType) {
300                     energyConsumers.add(energyConsumer);
301                 }
302             }
303             if (energyConsumers.isEmpty()) {
304                 return new int[0];
305             }
306 
307             energyConsumers.sort(Comparator.comparing(c -> c.ordinal));
308 
309             int[] ids = new int[energyConsumers.size()];
310             for (int i = 0; i < ids.length; i++) {
311                 ids[i] = energyConsumers.get(i).id;
312             }
313             return ids;
314         }
315 
316         @Override
getConsumedEnergy(int[] energyConsumerIds)317         public EnergyConsumerResult[] getConsumedEnergy(int[] energyConsumerIds) {
318             CompletableFuture<EnergyConsumerResult[]> future =
319                     mPowerStatsInternal.getEnergyConsumedAsync(energyConsumerIds);
320             try {
321                 return future.get(POWER_STATS_ENERGY_CONSUMERS_TIMEOUT, TimeUnit.MILLISECONDS);
322             } catch (InterruptedException | ExecutionException | TimeoutException e) {
323                 Slog.e(TAG, "Could not obtain energy consumers from PowerStatsService", e);
324             }
325 
326             return null;
327         }
328 
329         @Override
getVoltageMv()330         public int getVoltageMv() {
331             return mVoltageSupplier.getAsInt();
332         }
333 
334         @Override
getEnergyConsumerName(int energyConsumerId)335         public String getEnergyConsumerName(int energyConsumerId) {
336             ensureEnergyConsumers();
337 
338             for (EnergyConsumer energyConsumer : mEnergyConsumers) {
339                 if (energyConsumer.id == energyConsumerId) {
340                     return sanitizeCustomPowerComponentName(energyConsumer);
341                 }
342             }
343 
344             Slog.e(TAG, "Unsupported energy consumer ID " + energyConsumerId);
345             return "unsupported";
346         }
347 
sanitizeCustomPowerComponentName(EnergyConsumer energyConsumer)348         private String sanitizeCustomPowerComponentName(EnergyConsumer energyConsumer) {
349             String name = energyConsumer.name;
350             if (name == null || name.isBlank()) {
351                 name = "CUSTOM_" + energyConsumer.id;
352             }
353             int length = name.length();
354             StringBuilder sb = new StringBuilder(length);
355             for (int i = 0; i < length; i++) {
356                 char c = name.charAt(i);
357                 if (Character.isWhitespace(c)) {
358                     sb.append(' ');
359                 } else if (Character.isISOControl(c)) {
360                     sb.append('_');
361                 } else {
362                     sb.append(c);
363                 }
364             }
365             return sb.toString();
366         }
367     }
368 
369     class ConsumedEnergyHelper implements PowerStatsUidResolver.Listener {
370         private final ConsumedEnergyRetriever mConsumedEnergyRetriever;
371         private final @EnergyConsumerType int mEnergyConsumerType;
372         private final boolean mPerUidAttributionSupported;
373 
374         private boolean mIsInitialized;
375         private boolean mFirstCollection = true;
376         private int[] mEnergyConsumerIds;
377         private long[] mLastConsumedEnergyUws;
378         private final SparseLongArray mLastConsumerEnergyPerUid;
379         private int mLastVoltageMv;
380 
ConsumedEnergyHelper(ConsumedEnergyRetriever consumedEnergyRetriever, @EnergyConsumerType int energyConsumerType)381         ConsumedEnergyHelper(ConsumedEnergyRetriever consumedEnergyRetriever,
382                 @EnergyConsumerType int energyConsumerType) {
383             mConsumedEnergyRetriever = consumedEnergyRetriever;
384             mEnergyConsumerType = energyConsumerType;
385             mPerUidAttributionSupported = false;
386             mLastConsumerEnergyPerUid = null;
387         }
388 
ConsumedEnergyHelper(ConsumedEnergyRetriever consumedEnergyRetriever, int energyConsumerId, boolean perUidAttributionSupported)389         ConsumedEnergyHelper(ConsumedEnergyRetriever consumedEnergyRetriever,
390                 int energyConsumerId, boolean perUidAttributionSupported) {
391             mConsumedEnergyRetriever = consumedEnergyRetriever;
392             mEnergyConsumerType = EnergyConsumerType.OTHER;
393             mEnergyConsumerIds = new int[]{energyConsumerId};
394             mPerUidAttributionSupported = perUidAttributionSupported;
395             mLastConsumerEnergyPerUid = mPerUidAttributionSupported ? new SparseLongArray() : null;
396         }
397 
ensureInitialized()398         private void ensureInitialized() {
399             if (!mIsInitialized) {
400                 if (mEnergyConsumerIds == null) {
401                     mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(
402                             mEnergyConsumerType);
403                 }
404                 mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length];
405                 Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
406                 mUidResolver.addListener(this);
407                 mIsInitialized = true;
408             }
409         }
410 
getEnergyConsumerCount()411         int getEnergyConsumerCount() {
412             ensureInitialized();
413             return mEnergyConsumerIds.length;
414         }
415 
collectConsumedEnergy(PowerStats powerStats, PowerStatsLayout layout)416         boolean collectConsumedEnergy(PowerStats powerStats, PowerStatsLayout layout) {
417             ensureInitialized();
418 
419             if (mEnergyConsumerIds.length == 0) {
420                 return false;
421             }
422 
423             int voltageMv = mConsumedEnergyRetriever.getVoltageMv();
424             int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
425             if (averageVoltage <= 0) {
426                 Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
427                         + " mV) when querying energy consumers");
428                 return false;
429             }
430 
431             mLastVoltageMv = voltageMv;
432 
433             EnergyConsumerResult[] energy =
434                     mConsumedEnergyRetriever.getConsumedEnergy(mEnergyConsumerIds);
435             if (energy == null) {
436                 return false;
437             }
438 
439             for (int i = 0; i < mEnergyConsumerIds.length; i++) {
440                 populatePowerStats(powerStats, layout, energy, i, averageVoltage);
441             }
442             mFirstCollection = false;
443             return true;
444         }
445 
populatePowerStats(PowerStats powerStats, PowerStatsLayout layout, @NonNull EnergyConsumerResult[] energy, int energyConsumerIndex, int averageVoltage)446         private void populatePowerStats(PowerStats powerStats, PowerStatsLayout layout,
447                 @NonNull EnergyConsumerResult[] energy, int energyConsumerIndex,
448                 int averageVoltage) {
449             long consumedEnergy = energy[energyConsumerIndex].energyUWs;
450             long energyDelta = mLastConsumedEnergyUws[energyConsumerIndex] != ENERGY_UNSPECIFIED
451                     ? consumedEnergy - mLastConsumedEnergyUws[energyConsumerIndex] : 0;
452             mLastConsumedEnergyUws[energyConsumerIndex] = consumedEnergy;
453             if (energyDelta < 0) {
454                 // Likely, restart of powerstats HAL
455                 energyDelta = 0;
456             }
457 
458             if (energyDelta == 0 && !mFirstCollection) {
459                 return;
460             }
461 
462             layout.setConsumedEnergy(powerStats.stats, energyConsumerIndex,
463                     uJtoUc(energyDelta, averageVoltage));
464 
465             if (!mPerUidAttributionSupported) {
466                 return;
467             }
468 
469             EnergyConsumerAttribution[] perUid = energy[energyConsumerIndex].attribution;
470             if (perUid == null) {
471                 return;
472             }
473 
474             for (EnergyConsumerAttribution attribution : perUid) {
475                 int uid = mUidResolver.mapUid(attribution.uid);
476                 long lastEnergy = mLastConsumerEnergyPerUid.get(uid, ENERGY_UNSPECIFIED);
477                 mLastConsumerEnergyPerUid.put(uid, attribution.energyUWs);
478                 if (lastEnergy == ENERGY_UNSPECIFIED) {
479                     continue;
480                 }
481                 long deltaEnergy = attribution.energyUWs - lastEnergy;
482                 if (deltaEnergy <= 0) {
483                     continue;
484                 }
485 
486                 long[] uidStats = powerStats.uidStats.get(uid);
487                 if (uidStats == null) {
488                     uidStats = new long[layout.getUidStatsArrayLength()];
489                     powerStats.uidStats.put(uid, uidStats);
490                 }
491 
492                 layout.setUidConsumedEnergy(uidStats, energyConsumerIndex,
493                         layout.getUidConsumedEnergy(uidStats, energyConsumerIndex)
494                                 + uJtoUc(deltaEnergy, averageVoltage));
495             }
496         }
497 
498         @Override
onAfterIsolatedUidRemoved(int isolatedUid, int parentUid)499         public void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) {
500             if (mLastConsumerEnergyPerUid != null) {
501                 mHandler.post(() -> mLastConsumerEnergyPerUid.delete(isolatedUid));
502             }
503         }
504 
505         @Override
onIsolatedUidAdded(int isolatedUid, int parentUid)506         public void onIsolatedUidAdded(int isolatedUid, int parentUid) {
507         }
508 
509         @Override
onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid)510         public void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) {
511         }
512     }
513 }
514