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