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