Implement EnergyConsumer type to ids map for targeted querying

The EnergyConsumer type to id map is built at boot, based on the
EnergyConsumer info query. Using the map, BatteryExernalStatsWorker can
selectively choose which EnergyConsumers to query based on EnergyConsumer
type.

Also, collect the EnergyConsumerResults asynchronously with a
CompletableFuture.

Fixes: 180029015
Bug: 180079165
Test: atest FrameworksServicesTests:BatteryExternalStatsWorkerTest
Change-Id: I8d87c1da9170897065f866e7e1e14f72e0c73543
This commit is contained in:
Michael Wachenschwanz
2021-02-12 03:10:02 -08:00
parent 0726d59bc5
commit 580d86680d
2 changed files with 168 additions and 42 deletions

View File

@@ -50,8 +50,6 @@ import com.android.server.LocalServices;
import libcore.util.EmptyArray;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@@ -149,7 +147,6 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
* Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s,
* unless it is of {@link EnergyConsumer#type}=={@link EnergyConsumerType#OTHER}
*/
// TODO(b/180029015): Hook this up (it isn't used yet)
@GuardedBy("mWorkerLock")
private @Nullable SparseArray<int[]> mEnergyConsumerTypeToIdMap = null;
@@ -209,11 +206,22 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
= populateEnergyConsumerSubsystemMapsLocked();
if (idToConsumer != null) {
mMeasuredEnergySnapshot = new MeasuredEnergySnapshot(idToConsumer);
final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData();
// According to spec, initialEcrs will include 0s for consumers that haven't
// used any energy yet, as long as they are supported; however, attributed uid
// energies will be absent if their energy is 0.
mMeasuredEnergySnapshot.updateAndGetDelta(initialEcrs);
try {
final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData().get(
EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
// According to spec, initialEcrs will include 0s for consumers that haven't
// used any energy yet, as long as they are supported; however,
// attributed uid energies will be absent if their energy is 0.
mMeasuredEnergySnapshot.updateAndGetDelta(initialEcrs);
} catch (TimeoutException | InterruptedException e) {
Slog.w(TAG, "timeout or interrupt reading initial getEnergyConsumedAsync: "
+ e);
// Continue running, later attempts to query may be successful.
} catch (ExecutionException e) {
Slog.wtf(TAG, "exception reading initial getEnergyConsumedAsync: "
+ e.getCause());
// Continue running, later attempts to query may be successful.
}
numCustomBuckets = mMeasuredEnergySnapshot.getNumOtherOrdinals();
supportedStdBuckets = getSupportedEnergyBuckets(idToConsumer);
}
@@ -498,6 +506,8 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
CompletableFuture<ModemActivityInfo> modemFuture = CompletableFuture.completedFuture(null);
boolean railUpdated = false;
CompletableFuture<EnergyConsumerResult[]> futureECRs = getMeasuredEnergyLocked(updateFlags);
if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
// We were asked to fetch WiFi data.
// Only fetch WiFi power data if it is supported.
@@ -574,9 +584,23 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
Slog.w(TAG, "exception reading modem stats: " + e.getCause());
}
final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas =
mMeasuredEnergySnapshot == null ? null :
mMeasuredEnergySnapshot.updateAndGetDelta(getMeasuredEnergyLocked(updateFlags));
final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas;
if (mMeasuredEnergySnapshot == null || futureECRs == null) {
measuredEnergyDeltas = null;
} else {
EnergyConsumerResult[] ecrs;
try {
ecrs = futureECRs.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
} catch (TimeoutException | InterruptedException e) {
// TODO (b/180519623): Invalidate the MeasuredEnergy derived data until next reset.
Slog.w(TAG, "timeout or interrupt reading getEnergyConsumedAsync: " + e);
ecrs = null;
} catch (ExecutionException e) {
Slog.wtf(TAG, "exception reading getEnergyConsumedAsync: " + e.getCause());
ecrs = null;
}
measuredEnergyDeltas = mMeasuredEnergySnapshot.updateAndGetDelta(ecrs);
}
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -786,22 +810,29 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
return buckets;
}
/** Get {@link EnergyConsumerResult}s with the latest energy usage since boot. */
/** Get all {@link EnergyConsumerResult}s with the latest energy usage since boot. */
@GuardedBy("mWorkerLock")
private @Nullable EnergyConsumerResult[] getEnergyConsumptionData() {
try {
return mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
} catch (Exception e) {
Slog.e(TAG, "Failed to getEnergyConsumedAsync", e);
return null;
}
@Nullable
private CompletableFuture<EnergyConsumerResult[]> getEnergyConsumptionData() {
return getEnergyConsumptionData(new int[0]);
}
/**
* Get {@link EnergyConsumerResult}s of the specified {@link EnergyConsumer} ids with the latest
* energy usage since boot.
*/
@GuardedBy("mWorkerLock")
@Nullable
private CompletableFuture<EnergyConsumerResult[]> getEnergyConsumptionData(int[] consumerIds) {
return mPowerStatsInternal.getEnergyConsumedAsync(consumerIds);
}
/** Fetch EnergyConsumerResult[] for supported subsystems based on the given updateFlags. */
@VisibleForTesting
@GuardedBy("mWorkerLock")
private @Nullable EnergyConsumerResult[] getMeasuredEnergyLocked(@ExternalUpdateFlag int flags)
{
@Nullable
public CompletableFuture<EnergyConsumerResult[]> getMeasuredEnergyLocked(
@ExternalUpdateFlag int flags) {
if (mMeasuredEnergySnapshot == null || mPowerStatsInternal == null) return null;
if (flags == UPDATE_ALL) {
@@ -809,24 +840,27 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
return getEnergyConsumptionData();
}
final List<Integer> energyConsumerIds = new ArrayList<>();
final IntArray energyConsumerIds = new IntArray();
if ((flags & UPDATE_CPU) != 0) {
addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.CPU_CLUSTER);
}
if ((flags & UPDATE_DISPLAY) != 0) {
addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY);
}
// TODO: Wifi, Bluetooth, etc., go here
if (energyConsumerIds.isEmpty()) {
if (energyConsumerIds.size() == 0) {
return null;
}
// TODO(b/180029015): Query specific subsystems from HAL based on energyConsumerIds.toArray
return getEnergyConsumptionData();
return getEnergyConsumptionData(energyConsumerIds.toArray());
}
@GuardedBy("mWorkerLock")
private void addEnergyConsumerIdLocked(
List<Integer> energyConsumerIds, @EnergyConsumerType int type) {
final int consumerId = 0; // TODO(b/180029015): Use mEnergyConsumerTypeToIdMap to get this
energyConsumerIds.add(consumerId);
IntArray energyConsumerIds, @EnergyConsumerType int type) {
final int[] consumerIds = mEnergyConsumerTypeToIdMap.get(type);
if (consumerIds == null) return;
energyConsumerIds.addAll(consumerIds);
}
/** Populates the cached type->ids map, and returns the (inverse) id->EnergyConsumer map. */
@@ -840,12 +874,10 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
return null;
}
// TODO(b/180029015): Initialize typeToIds
// Maps type -> {ids} (1:n map, since multiple ids might have the same type)
// final SparseArray<SparseIntArray> typeToIds = new SparseArray<>();
// Maps id -> EnergyConsumer (1:1 map)
final SparseArray<EnergyConsumer> idToConsumer = new SparseArray<>(energyConsumers.length);
// Maps type -> {ids} (1:n map, since multiple ids might have the same type)
final SparseArray<IntArray> tempTypeToId = new SparseArray<>();
// Add all expected EnergyConsumers to the maps
for (final EnergyConsumer consumer : energyConsumers) {
@@ -862,9 +894,23 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
}
}
idToConsumer.put(consumer.id, consumer);
// TODO(b/180029015): Also populate typeToIds map
IntArray ids = tempTypeToId.get(consumer.type);
if (ids == null) {
ids = new IntArray();
tempTypeToId.put(consumer.type, ids);
}
ids.add(consumer.id);
}
mEnergyConsumerTypeToIdMap = new SparseArray<>(tempTypeToId.size());
// Populate mEnergyConsumerTypeToIdMap with EnergyConsumer type to ids mappings
final int size = tempTypeToId.size();
for (int i = 0; i < size; i++) {
final int consumerType = tempTypeToId.keyAt(i);
final int[] consumerIds = tempTypeToId.valueAt(i).toArray();
mEnergyConsumerTypeToIdMap.put(consumerType, consumerIds);
}
// TODO(b/180029015): Store typeToIds in mEnergyConsumerTypeToIdMap.
return idToConsumer;
}
}

View File

@@ -16,6 +16,12 @@
package com.android.server.am;
import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL;
import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import android.content.Context;
@@ -27,6 +33,7 @@ import android.hardware.power.stats.EnergyMeasurement;
import android.hardware.power.stats.PowerEntity;
import android.hardware.power.stats.StateResidencyResult;
import android.power.PowerStatsInternal;
import android.util.IntArray;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -34,7 +41,9 @@ import androidx.test.InstrumentationRegistry;
import com.android.internal.os.BatteryStatsImpl;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
/**
@@ -58,6 +67,69 @@ public class BatteryExternalStatsWorkerTest {
mBatteryStatsImpl);
}
@Test
public void testTargetedEnergyConsumerQuerying() {
final int numCpuClusters = 4;
final int numOther = 3;
final IntArray tempAllIds = new IntArray();
// Add some energy consumers used by BatteryExternalStatsWorker.
final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
"display");
tempAllIds.add(displayId);
mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
final int[] cpuClusterIds = new int[numCpuClusters];
for (int i = 0; i < numCpuClusters; i++) {
cpuClusterIds[i] = mPowerStatsInternal.addEnergyConsumer(
EnergyConsumerType.CPU_CLUSTER, i, "cpu_cluster" + i);
tempAllIds.add(cpuClusterIds[i]);
mPowerStatsInternal.incrementEnergyConsumption(cpuClusterIds[i], 1111 + i);
}
Arrays.sort(cpuClusterIds);
final int[] otherIds = new int[numOther];
for (int i = 0; i < numOther; i++) {
otherIds[i] = mPowerStatsInternal.addEnergyConsumer(
EnergyConsumerType.OTHER, i, "other" + i);
tempAllIds.add(otherIds[i]);
mPowerStatsInternal.incrementEnergyConsumption(otherIds[i], 3000 + i);
}
Arrays.sort(otherIds);
final int[] allIds = tempAllIds.toArray();
Arrays.sort(allIds);
// Inform BESW that PowerStatsInternal is ready to query
mBatteryExternalStatsWorker.systemServicesReady();
final EnergyConsumerResult[] displayResults =
mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_DISPLAY).getNow(null);
// Results should only have the display energy consumer
assertEquals(1, displayResults.length);
assertEquals(displayId, displayResults[0].id);
final EnergyConsumerResult[] cpuResults =
mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_CPU).getNow(null);
// Results should only have the cpu cluster energy consumers
final int[] receivedCpuIds = new int[cpuResults.length];
for (int i = 0; i < cpuResults.length; i++) {
receivedCpuIds[i] = cpuResults[i].id;
}
Arrays.sort(receivedCpuIds);
assertArrayEquals(cpuClusterIds, receivedCpuIds);
final EnergyConsumerResult[] allResults =
mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_ALL).getNow(null);
// All energy consumer results should be available
final int[] receivedAllIds = new int[allResults.length];
for (int i = 0; i < allResults.length; i++) {
receivedAllIds[i] = allResults[i].id;
}
Arrays.sort(receivedAllIds);
assertArrayEquals(allIds, receivedAllIds);
}
public class TestInjector extends BatteryExternalStatsWorker.Injector {
public TestInjector(Context context) {
super(context);
@@ -79,9 +151,8 @@ public class BatteryExternalStatsWorkerTest {
}
public class TestPowerStatsInternal extends PowerStatsInternal {
private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray<>();
private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults =
new SparseArray<>();
private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray();
private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults = new SparseArray();
private final int mTimeSinceBoot = 0;
@Override
@@ -98,10 +169,19 @@ public class BatteryExternalStatsWorkerTest {
public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync(
int[] energyConsumerIds) {
final CompletableFuture<EnergyConsumerResult[]> future = new CompletableFuture();
final int size = mEnergyConsumerResults.size();
final EnergyConsumerResult[] results = new EnergyConsumerResult[size];
for (int i = 0; i < size; i++) {
results[i] = mEnergyConsumerResults.valueAt(i);
final EnergyConsumerResult[] results;
final int length = energyConsumerIds.length;
if (length == 0) {
final int size = mEnergyConsumerResults.size();
results = new EnergyConsumerResult[size];
for (int i = 0; i < size; i++) {
results[i] = mEnergyConsumerResults.valueAt(i);
}
} else {
results = new EnergyConsumerResult[length];
for (int i = 0; i < length; i++) {
results[i] = mEnergyConsumerResults.get(energyConsumerIds[i]);
}
}
future.complete(results);
return future;