Add API SystemHealthManager.getPowerMonitorReadings

For now, the API is hidden.

Bug: 273308894
Test: atest FrameworksServicesTests:PowerStatsServiceTest
      atest FrameworksCoreTests:SystemHealthManagerTest

Change-Id: I99a14752ae7914375f9e8d51238a8c025e8ca34b
This commit is contained in:
Dmitri Plotnikov
2023-03-09 18:22:01 -08:00
parent 68ff9736f5
commit 77491b99bd
9 changed files with 936 additions and 74 deletions

View File

@@ -171,6 +171,7 @@ import android.os.IBinder;
import android.os.IDumpstate;
import android.os.IHardwarePropertiesManager;
import android.os.IPowerManager;
import android.os.IPowerStatsService;
import android.os.IRecoverySystem;
import android.os.ISystemUpdateManager;
import android.os.IThermalService;
@@ -1117,8 +1118,10 @@ public final class SystemServiceRegistry {
new CachedServiceFetcher<SystemHealthManager>() {
@Override
public SystemHealthManager createService(ContextImpl ctx) throws ServiceNotFoundException {
IBinder b = ServiceManager.getServiceOrThrow(BatteryStats.SERVICE_NAME);
return new SystemHealthManager(IBatteryStats.Stub.asInterface(b));
IBinder batteryStats = ServiceManager.getServiceOrThrow(BatteryStats.SERVICE_NAME);
IBinder powerStats = ServiceManager.getServiceOrThrow(Context.POWER_STATS_SERVICE);
return new SystemHealthManager(IBatteryStats.Stub.asInterface(batteryStats),
IPowerStatsService.Stub.asInterface(powerStats));
}});
registerService(Context.CONTEXTHUB_SERVICE, ContextHubManager.class,

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.os.ResultReceiver;
/** @hide */
interface IPowerStatsService {
/** @hide */
const String KEY_MONITORS = "monitors";
/** @hide */
const String KEY_ENERGY = "energy";
/** @hide */
const String KEY_TIMESTAMPS = "timestamps";
/** @hide */
const int RESULT_SUCCESS = 0;
/** @hide */
const int RESULT_UNSUPPORTED_POWER_MONITOR = 1;
/** {@hide} */
oneway void getSupportedPowerMonitors(in ResultReceiver resultReceiver);
/** {@hide} */
oneway void getPowerMonitorReadings(in int[] powerMonitorIndices,
in ResultReceiver resultReceiver);
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2023, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
/** @hide */
@JavaOnlyStableParcelable parcelable PowerMonitor;

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.annotation.IntDef;
import android.annotation.NonNull;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @hide
*/
public final class PowerMonitor implements Parcelable {
/**
* Power monitor corresponding to a subsystem. The energy value may be a direct pass-through
* power rail measurement, or modeled in some fashion. For example, an energy consumer may
* represent a combination of multiple rails or a portion of a rail shared between subsystems,
* e.g. WiFi and Bluetooth are often handled by the same chip, powered by a shared rail.
* Some consumer names are standardized (see android.hardware.power.stats.EnergyConsumerType),
* others are not.
*/
public static final int POWER_MONITOR_TYPE_CONSUMER = 0;
/**
* Power monitor corresponding to a directly measured power rail. Rails are device-specific:
* no assumptions can be made about the source of those measurements across different devices,
* even if they have the same name.
*/
public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1;
/** @hide */
@IntDef(flag = true, prefix = {"POWER_MONITOR_TYPE_"}, value = {
POWER_MONITOR_TYPE_CONSUMER,
POWER_MONITOR_TYPE_MEASUREMENT
})
@Retention(RetentionPolicy.SOURCE)
public @interface PowerMonitorType {
}
/**
* These indices are not guaranteed to be stable across reboots and should not
* be persisted.
*
* @hide
*/
public final int index;
@PowerMonitorType
public final int type;
@NonNull
public final String name;
/**
* @hide
*/
public PowerMonitor(int index, int type, @NonNull String name) {
this.index = index;
this.type = type;
this.name = name;
}
private PowerMonitor(Parcel in) {
index = in.readInt();
type = in.readInt();
name = in.readString();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(index);
dest.writeInt(type);
dest.writeString(name);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<PowerMonitor> CREATOR = new Creator<>() {
@Override
public PowerMonitor createFromParcel(@NonNull Parcel in) {
return new PowerMonitor(in);
}
@Override
public PowerMonitor[] newArray(int size) {
return new PowerMonitor[size];
}
};
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.annotation.NonNull;
import java.util.Arrays;
import java.util.Comparator;
/**
* A collection of energy measurements from Power Monitors.
*
* @hide
*/
public final class PowerMonitorReadings {
public static final int ENERGY_UNAVAILABLE = -1;
@NonNull
private final PowerMonitor[] mPowerMonitors;
@NonNull
private final long[] mEnergyUws;
@NonNull
private final long[] mTimestampsMs;
private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR =
Comparator.comparingInt(pm -> pm.index);
/**
* @param powerMonitors array of power monitor (ODPM) rails, sorted by PowerMonitor.index
* @hide
*/
public PowerMonitorReadings(PowerMonitor[] powerMonitors,
long[] energyUws, long[] timestampsMs) {
mPowerMonitors = powerMonitors;
mEnergyUws = energyUws;
mTimestampsMs = timestampsMs;
}
/**
* Returns energy consumed by the specified power monitor since boot in microwatt-seconds.
* Does not persist across reboots.
* Represents total energy: both on-battery and plugged-in.
*/
public long getConsumedEnergyUws(PowerMonitor powerMonitor) {
int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
if (offset >= 0) {
return mEnergyUws[offset];
}
return ENERGY_UNAVAILABLE;
}
/**
* Elapsed realtime when the snapshot was taken.
*/
public long getTimestampMs(PowerMonitor powerMonitor) {
int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
if (offset >= 0) {
return mTimestampsMs[offset];
}
return 0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(" monitors: [");
for (int i = 0; i < mPowerMonitors.length; i++) {
if (i != 0) {
sb.append(", ");
}
sb.append(mPowerMonitors[i].name)
.append(" = ").append(mEnergyUws[i])
.append(" (").append(mTimestampsMs[i]).append(')');
}
sb.append("]");
return sb.toString();
}
}

View File

@@ -16,17 +16,28 @@
package android.os.health;
import android.annotation.NonNull;
import android.annotation.SystemService;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.BatteryStats;
import android.os.Build;
import android.os.Bundle;
import android.os.IPowerStatsService;
import android.os.PowerMonitor;
import android.os.PowerMonitorReadings;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import com.android.internal.app.IBatteryStats;
import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* Provides access to data about how various system resources are used by applications.
* @more
@@ -47,19 +58,25 @@ import com.android.internal.app.IBatteryStats;
@SystemService(Context.SYSTEM_HEALTH_SERVICE)
public class SystemHealthManager {
private final IBatteryStats mBatteryStats;
private final IPowerStatsService mPowerStats;
private PowerMonitor[] mPowerMonitorsInfo;
/**
* Construct a new SystemHealthManager object.
*
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public SystemHealthManager() {
this(IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME)));
this(IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME)),
IPowerStatsService.Stub.asInterface(
ServiceManager.getService(Context.POWER_STATS_SERVICE)));
}
/** {@hide} */
public SystemHealthManager(IBatteryStats batteryStats) {
public SystemHealthManager(IBatteryStats batteryStats, IPowerStatsService powerStats) {
mBatteryStats = batteryStats;
mPowerStats = powerStats;
}
/**
@@ -69,22 +86,20 @@ public class SystemHealthManager {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static SystemHealthManager from(Context context) {
return (SystemHealthManager)context.getSystemService(Context.SYSTEM_HEALTH_SERVICE);
return (SystemHealthManager) context.getSystemService(Context.SYSTEM_HEALTH_SERVICE);
}
/**
* Return a {@link HealthStats} object containing a snapshot of system health
* metrics for the given uid (user-id, which in usually corresponds to application).
* @more
*
* An application must hold the {@link android.Manifest.permission#BATTERY_STATS
* android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats
* other than its own.
*
* @param uid User ID for a given application.
* @return A {@link HealthStats} object containing the metrics for the requested
* application. The keys for this HealthStats object will be from the {@link UidHealthStats}
* class.
* @more An application must hold the {@link android.Manifest.permission#BATTERY_STATS
* android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats
* other than its own.
* @see Process#myUid() Process.myUid()
*/
public HealthStats takeUidSnapshot(int uid) {
@@ -111,23 +126,21 @@ public class SystemHealthManager {
/**
* Return a {@link HealthStats} object containing a snapshot of system health
* metrics for the given uids (user-id, which in usually corresponds to application).
* @more
*
* An application must hold the {@link android.Manifest.permission#BATTERY_STATS
* android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats
* other than its own.
*
* @param uids An array of User IDs to retrieve.
* @return An array of {@link HealthStats} objects containing the metrics for each of
* the requested uids. The keys for this HealthStats object will be from the
* {@link UidHealthStats} class.
* @more An application must hold the {@link android.Manifest.permission#BATTERY_STATS
* android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats
* other than its own.
*/
public HealthStats[] takeUidSnapshots(int[] uids) {
try {
final HealthStatsParceler[] parcelers = mBatteryStats.takeUidSnapshots(uids);
final HealthStats[] results = new HealthStats[uids.length];
final int N = uids.length;
for (int i=0; i<N; i++) {
for (int i = 0; i < N; i++) {
results[i] = parcelers[i].getHealthStats();
}
return results;
@@ -136,5 +149,109 @@ public class SystemHealthManager {
}
}
}
/**
* Returns a list of supported power monitors, which include raw ODPM rails and
* modeled energy consumers. If ODPM is unsupported by PowerStats HAL, this method returns
* an empty array.
*
* @hide
*/
@NonNull
public PowerMonitor[] getSupportedPowerMonitors() {
synchronized (this) {
if (mPowerMonitorsInfo != null) {
return mPowerMonitorsInfo;
}
CompletableFuture<PowerMonitor[]> future = new CompletableFuture<>();
getSupportedPowerMonitors(future);
try {
return future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
}
/**
* Retrieves a list of supported power monitors, see {@link #getSupportedPowerMonitors()}
*
* @hide
*/
public void getSupportedPowerMonitors(@NonNull CompletableFuture<PowerMonitor[]> future) {
synchronized (this) {
if (mPowerMonitorsInfo != null) {
future.complete(mPowerMonitorsInfo);
return;
}
try {
mPowerStats.getSupportedPowerMonitors(new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
synchronized (this) {
mPowerMonitorsInfo = resultData.getParcelableArray(
IPowerStatsService.KEY_MONITORS, PowerMonitor.class);
}
future.complete(mPowerMonitorsInfo);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
/**
* Retrieves the accumulated power consumption reported by the specified power monitors.
*
* @param powerMonitors power monitors to be returned.
* @hide
*/
@NonNull
public PowerMonitorReadings getPowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors) {
CompletableFuture<PowerMonitorReadings> future = new CompletableFuture<>();
getPowerMonitorReadings(powerMonitors, future);
try {
return future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR =
Comparator.comparingInt(pm -> pm.index);
/**
* @param powerMonitors power monitors to be retrieved.
* @hide
*/
public void getPowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors,
@NonNull CompletableFuture<PowerMonitorReadings> future) {
Arrays.sort(powerMonitors, POWER_MONITOR_COMPARATOR);
int[] indices = new int[powerMonitors.length];
for (int i = 0; i < powerMonitors.length; i++) {
indices[i] = powerMonitors[i].index;
}
try {
mPowerStats.getPowerMonitorReadings(indices, new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == IPowerStatsService.RESULT_SUCCESS) {
future.complete(new PowerMonitorReadings(powerMonitors,
resultData.getLongArray(IPowerStatsService.KEY_ENERGY),
resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS)));
} else if (resultCode == IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR) {
future.completeExceptionally(
new IllegalArgumentException("Unsupported power monitor"));
} else {
future.completeExceptionally(
new IllegalStateException(
"Unrecognized result code " + resultCode));
}
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os.health;
import static androidx.test.InstrumentationRegistry.getContext;
import static com.google.common.truth.Truth.assertThat;
import android.os.PowerMonitor;
import android.os.PowerMonitorReadings;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class SystemHealthManagerTest {
@Test
public void getPowerMonitors() {
SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
PowerMonitor[] powerMonitorInfo = shm.getSupportedPowerMonitors();
assertThat(powerMonitorInfo).isNotNull();
if (powerMonitorInfo.length == 0) {
// This device does not support PowerStats HAL
return;
}
PowerMonitor consumerMonitor = null;
PowerMonitor measurementMonitor = null;
for (PowerMonitor pmi : powerMonitorInfo) {
if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
measurementMonitor = pmi;
} else {
consumerMonitor = pmi;
}
}
List<PowerMonitor> pmis = new ArrayList<>();
if (consumerMonitor != null) {
pmis.add(consumerMonitor);
}
if (measurementMonitor != null) {
pmis.add(measurementMonitor);
}
PowerMonitor[] selectedMonitors = pmis.toArray(new PowerMonitor[0]);
PowerMonitorReadings readings = shm.getPowerMonitorReadings(selectedMonitors);
for (PowerMonitor monitor : selectedMonitors) {
assertThat(readings.getConsumedEnergyUws(monitor)).isAtLeast(0);
assertThat(readings.getTimestampMs(monitor)).isGreaterThan(0);
}
}
}

View File

@@ -16,27 +16,34 @@
package com.android.server.powerstats;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.power.stats.Channel;
import android.hardware.power.stats.EnergyConsumer;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyConsumerType;
import android.hardware.power.stats.EnergyMeasurement;
import android.hardware.power.stats.PowerEntity;
import android.hardware.power.stats.StateResidencyResult;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IPowerStatsService;
import android.os.Looper;
import android.os.PowerMonitor;
import android.os.PowerMonitorReadings;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.power.PowerStatsInternal;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.Clock;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.SystemService;
import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
@@ -46,6 +53,8 @@ import com.android.server.powerstats.ProtoStreamUtils.PowerEntityUtils;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
@@ -65,8 +74,10 @@ public class PowerStatsService extends SystemService {
private static final String METER_CACHE_FILENAME = "meterCache";
private static final String MODEL_CACHE_FILENAME = "modelCache";
private static final String RESIDENCY_CACHE_FILENAME = "residencyCache";
private static final long MAX_POWER_MONITOR_AGE_MILLIS = 30_000;
private final Injector mInjector;
private final Clock mClock;
private File mDataStoragePath;
private Context mContext;
@@ -83,15 +94,23 @@ public class PowerStatsService extends SystemService {
@Nullable
@GuardedBy("this")
private Looper mLooper;
private Handler mHandler;
@Nullable
@GuardedBy("this")
private EnergyConsumer[] mEnergyConsumers = null;
@Nullable
@GuardedBy("this")
private Channel[] mEnergyMeters = null;
@VisibleForTesting
static class Injector {
@GuardedBy("this")
private IPowerStatsHALWrapper mPowerStatsHALWrapper;
Clock getClock() {
return Clock.SYSTEM_CLOCK;
}
File createDataStoragePath() {
return new File(Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM),
DATA_STORAGE_SUBDIR);
@@ -160,7 +179,18 @@ public class PowerStatsService extends SystemService {
}
}
private final class BinderService extends Binder {
private final IBinder mService = new IPowerStatsService.Stub() {
@Override
public void getSupportedPowerMonitors(ResultReceiver resultReceiver) {
getHandler().post(() -> getSupportedPowerMonitorsImpl(resultReceiver));
}
@Override
public void getPowerMonitorReadings(int[] powerMonitorIds, ResultReceiver resultReceiver) {
getHandler().post(() -> getPowerMonitorReadingsImpl(powerMonitorIds, resultReceiver));
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -191,7 +221,7 @@ public class PowerStatsService extends SystemService {
}
}
}
}
};
@Override
public void onBootPhase(int phase) {
@@ -208,7 +238,7 @@ public class PowerStatsService extends SystemService {
mPowerStatsInternal = new LocalService();
publishLocalService(PowerStatsInternal.class, mPowerStatsInternal);
}
publishBinderService(Context.POWER_STATS_SERVICE, new BinderService());
publishBinderService(Context.POWER_STATS_SERVICE, mService);
}
private void onSystemServicesReady() {
@@ -263,6 +293,15 @@ public class PowerStatsService extends SystemService {
}
}
private Handler getHandler() {
synchronized (this) {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
}
private EnergyConsumer[] getEnergyConsumerInfo() {
synchronized (this) {
if (mEnergyConsumers == null) {
@@ -272,6 +311,15 @@ public class PowerStatsService extends SystemService {
}
}
private Channel[] getEnergyMeterInfo() {
synchronized (this) {
if (mEnergyMeters == null) {
mEnergyMeters = getPowerStatsHal().getEnergyMeterInfo();
}
return mEnergyMeters;
}
}
public PowerStatsService(Context context) {
this(context, new Injector());
}
@@ -281,15 +329,10 @@ public class PowerStatsService extends SystemService {
super(context);
mContext = context;
mInjector = injector;
mClock = injector.getClock();
}
private final class LocalService extends PowerStatsInternal {
private final Handler mHandler;
LocalService() {
mHandler = new Handler(getLooper());
}
@Override
public EnergyConsumer[] getEnergyConsumerInfo() {
@@ -300,9 +343,8 @@ public class PowerStatsService extends SystemService {
public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync(
int[] energyConsumerIds) {
final CompletableFuture<EnergyConsumerResult[]> future = new CompletableFuture<>();
mHandler.sendMessage(
PooledLambda.obtainMessage(PowerStatsService.this::getEnergyConsumedAsync,
future, energyConsumerIds));
getHandler().post(
() -> PowerStatsService.this.getEnergyConsumedAsync(future, energyConsumerIds));
return future;
}
@@ -315,9 +357,8 @@ public class PowerStatsService extends SystemService {
public CompletableFuture<StateResidencyResult[]> getStateResidencyAsync(
int[] powerEntityIds) {
final CompletableFuture<StateResidencyResult[]> future = new CompletableFuture<>();
mHandler.sendMessage(
PooledLambda.obtainMessage(PowerStatsService.this::getStateResidencyAsync,
future, powerEntityIds));
getHandler().post(
() -> PowerStatsService.this.getStateResidencyAsync(future, powerEntityIds));
return future;
}
@@ -330,9 +371,8 @@ public class PowerStatsService extends SystemService {
public CompletableFuture<EnergyMeasurement[]> readEnergyMeterAsync(
int[] channelIds) {
final CompletableFuture<EnergyMeasurement[]> future = new CompletableFuture<>();
mHandler.sendMessage(
PooledLambda.obtainMessage(PowerStatsService.this::readEnergyMeterAsync,
future, channelIds));
getHandler().post(
() -> PowerStatsService.this.readEnergyMeterAsync(future, channelIds));
return future;
}
}
@@ -413,4 +453,237 @@ public class PowerStatsService extends SystemService {
int[] channelIds) {
future.complete(getPowerStatsHal().readEnergyMeter(channelIds));
}
private static class PowerMonitorState {
public final PowerMonitor powerMonitor;
public final int id;
public long timestampMs;
public long energyUws = PowerMonitorReadings.ENERGY_UNAVAILABLE;
private PowerMonitorState(PowerMonitor powerMonitor, int id) {
this.powerMonitor = powerMonitor;
this.id = id;
}
}
private volatile PowerMonitor[] mPowerMonitors;
private volatile PowerMonitorState[] mPowerMonitorStates;
private void ensurePowerMonitors() {
if (mPowerMonitors != null) {
return;
}
synchronized (this) {
if (mPowerMonitors != null) {
return;
}
List<PowerMonitor> monitors = new ArrayList<>();
List<PowerMonitorState> states = new ArrayList<>();
int index = 0;
Channel[] channels = getEnergyMeterInfo();
for (Channel channel : channels) {
PowerMonitor monitor = new PowerMonitor(index++,
PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT,
getChannelName(channel));
monitors.add(monitor);
states.add(new PowerMonitorState(monitor, channel.id));
}
EnergyConsumer[] energyConsumers = getEnergyConsumerInfo();
for (EnergyConsumer consumer : energyConsumers) {
PowerMonitor monitor = new PowerMonitor(index++,
PowerMonitor.POWER_MONITOR_TYPE_CONSUMER,
getEnergyConsumerName(consumer, energyConsumers));
monitors.add(monitor);
states.add(new PowerMonitorState(monitor, consumer.id));
}
mPowerMonitors = monitors.toArray(new PowerMonitor[monitors.size()]);
mPowerMonitorStates = states.toArray(new PowerMonitorState[monitors.size()]);
}
}
@NonNull
private String getChannelName(Channel c) {
StringBuilder sb = new StringBuilder();
sb.append('[').append(c.name).append("]:");
if (c.subsystem != null) {
sb.append(c.subsystem);
}
return sb.toString();
}
@NonNull
private String getEnergyConsumerName(EnergyConsumer consumer,
EnergyConsumer[] energyConsumers) {
if (consumer.type != EnergyConsumerType.OTHER) {
StringBuilder sb = new StringBuilder();
sb.append(energyConsumerTypeToString(consumer.type));
boolean hasOrdinal = consumer.ordinal != 0;
if (!hasOrdinal) {
// See if any other EnergyConsumer of the same type has an ordinal
for (EnergyConsumer aConsumer : energyConsumers) {
if (aConsumer.type == consumer.type && aConsumer.ordinal != 0) {
hasOrdinal = true;
break;
}
}
}
if (hasOrdinal) {
sb.append('/').append(consumer.ordinal);
}
return sb.toString();
} else {
return consumer.name;
}
}
private static String energyConsumerTypeToString(int type) {
switch(type) {
case EnergyConsumerType.BLUETOOTH: return "BLUETOOTH";
case EnergyConsumerType.CPU_CLUSTER: return "CPU";
case EnergyConsumerType.DISPLAY: return "DISPLAY";
case EnergyConsumerType.GNSS: return "GNSS";
case EnergyConsumerType.MOBILE_RADIO: return "MOBILE_RADIO";
case EnergyConsumerType.WIFI: return "WIFI";
case EnergyConsumerType.OTHER: return "";
default:
throw new IllegalStateException("Unrecognized EnergyConsumerType: " + type);
}
}
/**
* Returns names of supported power monitors, including Channels and EnergyConsumers.
*/
@VisibleForTesting
public void getSupportedPowerMonitorsImpl(ResultReceiver resultReceiver) {
ensurePowerMonitors();
Bundle result = new Bundle();
result.putParcelableArray(IPowerStatsService.KEY_MONITORS, mPowerMonitors);
resultReceiver.send(IPowerStatsService.RESULT_SUCCESS, result);
}
/**
* Returns the latest readings for the specified power monitors.
*/
@VisibleForTesting
public void getPowerMonitorReadingsImpl(@NonNull int[] powerMonitorIndices,
ResultReceiver resultReceiver) {
ensurePowerMonitors();
long earliestTimestamp = Long.MAX_VALUE;
PowerMonitorState[] powerMonitorStates = new PowerMonitorState[powerMonitorIndices.length];
for (int i = 0; i < powerMonitorIndices.length; i++) {
int index = powerMonitorIndices[i];
if (index < 0 || index >= mPowerMonitorStates.length) {
resultReceiver.send(IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR, null);
return;
}
powerMonitorStates[i] = mPowerMonitorStates[index];
if (mPowerMonitorStates[index] != null
&& mPowerMonitorStates[index].timestampMs < earliestTimestamp) {
earliestTimestamp = mPowerMonitorStates[index].timestampMs;
}
}
if (earliestTimestamp == 0
|| mClock.elapsedRealtime() - earliestTimestamp > MAX_POWER_MONITOR_AGE_MILLIS) {
updateEnergyConsumers(powerMonitorStates);
updateEnergyMeasurements(powerMonitorStates);
}
long[] energy = new long[powerMonitorStates.length];
long[] timestamps = new long[powerMonitorStates.length];
for (int i = 0; i < powerMonitorStates.length; i++) {
PowerMonitorState state = powerMonitorStates[i];
// TODO(273310268): add random noise
energy[i] = state.energyUws;
timestamps[i] = state.timestampMs;
}
Bundle result = new Bundle();
result.putLongArray(IPowerStatsService.KEY_ENERGY, energy);
result.putLongArray(IPowerStatsService.KEY_TIMESTAMPS, timestamps);
resultReceiver.send(IPowerStatsService.RESULT_SUCCESS, result);
}
private void updateEnergyConsumers(PowerMonitorState[] powerMonitorStates) {
int[] ids = collectIds(powerMonitorStates, PowerMonitor.POWER_MONITOR_TYPE_CONSUMER);
if (ids == null) {
return;
}
EnergyConsumerResult[] energyConsumerResults = getPowerStatsHal().getEnergyConsumed(ids);
if (energyConsumerResults == null) {
return;
}
for (PowerMonitorState powerMonitorState : powerMonitorStates) {
if (powerMonitorState.powerMonitor.type
== PowerMonitor.POWER_MONITOR_TYPE_CONSUMER) {
for (EnergyConsumerResult energyConsumerResult : energyConsumerResults) {
if (energyConsumerResult.id == powerMonitorState.id) {
powerMonitorState.energyUws = energyConsumerResult.energyUWs;
powerMonitorState.timestampMs = energyConsumerResult.timestampMs;
break;
}
}
}
}
}
private void updateEnergyMeasurements(PowerMonitorState[] powerMonitorStates) {
int[] ids = collectIds(powerMonitorStates, PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT);
if (ids == null) {
return;
}
EnergyMeasurement[] energyMeasurements = getPowerStatsHal().readEnergyMeter(ids);
if (energyMeasurements == null) {
return;
}
for (PowerMonitorState powerMonitorState : powerMonitorStates) {
if (powerMonitorState.powerMonitor.type
== PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
for (EnergyMeasurement energyMeasurement : energyMeasurements) {
if (energyMeasurement.id == powerMonitorState.id) {
powerMonitorState.energyUws = energyMeasurement.energyUWs;
powerMonitorState.timestampMs = energyMeasurement.timestampMs;
break;
}
}
}
}
}
@Nullable
private int[] collectIds(PowerMonitorState[] powerMonitorStates,
@PowerMonitor.PowerMonitorType int type) {
int count = 0;
for (PowerMonitorState monitorState : powerMonitorStates) {
if (monitorState.powerMonitor.type == type) {
count++;
}
}
if (count == 0) {
return null;
}
int[] ids = new int[count];
int index = 0;
for (PowerMonitorState monitorState : powerMonitorStates) {
if (monitorState.powerMonitor.type == type) {
ids[index++] = monitorState.id;
}
}
return ids;
}
}

View File

@@ -16,6 +16,8 @@
package com.android.server.powerstats;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -30,10 +32,15 @@ import android.hardware.power.stats.PowerEntity;
import android.hardware.power.stats.State;
import android.hardware.power.stats.StateResidency;
import android.hardware.power.stats.StateResidencyResult;
import android.os.Bundle;
import android.os.IPowerStatsService;
import android.os.Looper;
import android.os.PowerMonitor;
import android.os.ResultReceiver;
import androidx.test.InstrumentationRegistry;
import com.android.internal.os.Clock;
import com.android.server.SystemService;
import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
@@ -58,13 +65,15 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
/**
* Tests for {@link com.android.server.powerstats.PowerStatsService}.
*
* Build/Install/Run:
* atest FrameworksServicesTests:PowerStatsServiceTest
* atest FrameworksServicesTests:PowerStatsServiceTest
*/
public class PowerStatsServiceTest {
private static final String TAG = PowerStatsServiceTest.class.getSimpleName();
@@ -90,13 +99,29 @@ public class PowerStatsServiceTest {
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
private PowerStatsService mService;
private TestPowerStatsHALWrapper mPowerStatsHALWrapper = new TestPowerStatsHALWrapper();
private File mDataStorageDir;
private TimerTrigger mTimerTrigger;
private BatteryTrigger mBatteryTrigger;
private PowerStatsLogger mPowerStatsLogger;
private MockClock mMockClock = new MockClock();
private class MockClock extends Clock {
public long realtime;
@Override
public long elapsedRealtime() {
return realtime;
}
}
private final PowerStatsService.Injector mInjector = new PowerStatsService.Injector() {
private TestPowerStatsHALWrapper mTestPowerStatsHALWrapper = new TestPowerStatsHALWrapper();
@Override
Clock getClock() {
return mMockClock;
}
@Override
File createDataStoragePath() {
if (mDataStorageDir == null) {
@@ -142,7 +167,7 @@ public class PowerStatsServiceTest {
@Override
IPowerStatsHALWrapper getPowerStatsHALWrapperImpl() {
return mTestPowerStatsHALWrapper;
return mPowerStatsHALWrapper;
}
@Override
@@ -152,29 +177,32 @@ public class PowerStatsServiceTest {
String residencyFilename, String residencyCacheFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
mPowerStatsLogger = new PowerStatsLogger(context, looper, dataStoragePath,
meterFilename, meterCacheFilename,
modelFilename, modelCacheFilename,
residencyFilename, residencyCacheFilename,
powerStatsHALWrapper);
meterFilename, meterCacheFilename,
modelFilename, modelCacheFilename,
residencyFilename, residencyCacheFilename,
powerStatsHALWrapper);
return mPowerStatsLogger;
}
@Override
BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) {
mBatteryTrigger = new BatteryTrigger(context, powerStatsLogger,
false /* trigger enabled */);
false /* trigger enabled */);
return mBatteryTrigger;
}
@Override
TimerTrigger createTimerTrigger(Context context, PowerStatsLogger powerStatsLogger) {
mTimerTrigger = new TimerTrigger(context, powerStatsLogger,
false /* trigger enabled */);
false /* trigger enabled */);
return mTimerTrigger;
}
};
public static final class TestPowerStatsHALWrapper implements IPowerStatsHALWrapper {
public EnergyConsumerResult[] energyConsumerResults;
public EnergyMeasurement[] energyMeasurements;
@Override
public PowerEntity[] getPowerEntityInfo() {
PowerEntity[] powerEntityList = new PowerEntity[POWER_ENTITY_COUNT];
@@ -195,12 +223,12 @@ public class PowerStatsServiceTest {
@Override
public StateResidencyResult[] getStateResidency(int[] powerEntityIds) {
StateResidencyResult[] stateResidencyResultList =
new StateResidencyResult[POWER_ENTITY_COUNT];
new StateResidencyResult[POWER_ENTITY_COUNT];
for (int i = 0; i < stateResidencyResultList.length; i++) {
stateResidencyResultList[i] = new StateResidencyResult();
stateResidencyResultList[i].id = i;
stateResidencyResultList[i].stateResidencyData =
new StateResidency[STATE_RESIDENCY_COUNT];
new StateResidency[STATE_RESIDENCY_COUNT];
for (int j = 0; j < stateResidencyResultList[i].stateResidencyData.length; j++) {
stateResidencyResultList[i].stateResidencyData[j] = new StateResidency();
stateResidencyResultList[i].stateResidencyData[j].id = j;
@@ -226,24 +254,26 @@ public class PowerStatsServiceTest {
return energyConsumerList;
}
@Override
public EnergyConsumerResult[] getEnergyConsumed(int[] energyConsumerIds) {
EnergyConsumerResult[] energyConsumedList =
new EnergyConsumerResult[ENERGY_CONSUMER_COUNT];
for (int i = 0; i < energyConsumedList.length; i++) {
energyConsumedList[i] = new EnergyConsumerResult();
energyConsumedList[i].id = i;
energyConsumedList[i].timestampMs = i;
energyConsumedList[i].energyUWs = i;
energyConsumedList[i].attribution =
new EnergyConsumerAttribution[ENERGY_CONSUMER_ATTRIBUTION_COUNT];
for (int j = 0; j < energyConsumedList[i].attribution.length; j++) {
energyConsumedList[i].attribution[j] = new EnergyConsumerAttribution();
energyConsumedList[i].attribution[j].uid = j;
energyConsumedList[i].attribution[j].energyUWs = j;
private void buildEnergyConsumerResult() {
energyConsumerResults = new EnergyConsumerResult[ENERGY_CONSUMER_COUNT];
for (int i = 0; i < energyConsumerResults.length; i++) {
energyConsumerResults[i] = new EnergyConsumerResult();
energyConsumerResults[i].id = i;
energyConsumerResults[i].timestampMs = i;
energyConsumerResults[i].energyUWs = i;
energyConsumerResults[i].attribution =
new EnergyConsumerAttribution[ENERGY_CONSUMER_ATTRIBUTION_COUNT];
for (int j = 0; j < energyConsumerResults[i].attribution.length; j++) {
energyConsumerResults[i].attribution[j] = new EnergyConsumerAttribution();
energyConsumerResults[i].attribution[j].uid = j;
energyConsumerResults[i].attribution[j].energyUWs = j;
}
}
return energyConsumedList;
}
@Override
public EnergyConsumerResult[] getEnergyConsumed(int[] energyConsumerIds) {
return energyConsumerResults;
}
@Override
@@ -258,17 +288,20 @@ public class PowerStatsServiceTest {
return energyMeterList;
}
private void buildEnergyMeasurements() {
energyMeasurements = new EnergyMeasurement[ENERGY_METER_COUNT];
for (int i = 0; i < energyMeasurements.length; i++) {
energyMeasurements[i] = new EnergyMeasurement();
energyMeasurements[i].id = i;
energyMeasurements[i].timestampMs = i;
energyMeasurements[i].durationMs = i;
energyMeasurements[i].energyUWs = i;
}
}
@Override
public EnergyMeasurement[] readEnergyMeter(int[] channelIds) {
EnergyMeasurement[] energyMeasurementList = new EnergyMeasurement[ENERGY_METER_COUNT];
for (int i = 0; i < energyMeasurementList.length; i++) {
energyMeasurementList[i] = new EnergyMeasurement();
energyMeasurementList[i].id = i;
energyMeasurementList[i].timestampMs = i;
energyMeasurementList[i].durationMs = i;
energyMeasurementList[i].energyUWs = i;
}
return energyMeasurementList;
return energyMeasurements;
}
@Override
@@ -286,6 +319,7 @@ public class PowerStatsServiceTest {
public void testWrittenMeterDataMatchesReadIncidentReportData()
throws InterruptedException, IOException {
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mPowerStatsHALWrapper.buildEnergyMeasurements();
// Write data to on-device storage.
mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY);
@@ -331,6 +365,8 @@ public class PowerStatsServiceTest {
throws InterruptedException, IOException {
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mPowerStatsHALWrapper.buildEnergyConsumerResult();
// Write data to on-device storage.
mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY);
@@ -368,7 +404,7 @@ public class PowerStatsServiceTest {
== ENERGY_CONSUMER_ATTRIBUTION_COUNT);
for (int j = 0; j < pssProto.energyConsumerResult[i].attribution.length; j++) {
assertTrue(pssProto.energyConsumerResult[i].attribution[j].uid == j);
assertTrue(pssProto.energyConsumerResult[i].attribution[j].energyUws == j);
assertTrue(pssProto.energyConsumerResult[i].attribution[j].energyUws == j);
}
}
}
@@ -1008,4 +1044,111 @@ public class PowerStatsServiceTest {
assertTrue(modelFile.exists());
assertTrue(residencyFile.exists());
}
private static class GetSupportedPowerMonitorsResult extends ResultReceiver {
public PowerMonitor[] powerMonitors;
GetSupportedPowerMonitorsResult() {
super(null);
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
powerMonitors = resultData.getParcelableArray(IPowerStatsService.KEY_MONITORS,
PowerMonitor.class);
}
}
@Test
public void getSupportedPowerMonitors() {
GetSupportedPowerMonitorsResult result = new GetSupportedPowerMonitorsResult();
mService.getSupportedPowerMonitorsImpl(result);
assertThat(result.powerMonitors).isNotNull();
assertThat(Arrays.stream(result.powerMonitors).map(pm -> pm.name).toList())
.containsAtLeast(
"energyconsumer0",
"BLUETOOTH/1",
"[channelname0]:channelsubsystem0",
"[channelname1]:channelsubsystem1");
}
private static class GetPowerMonitorsResult extends ResultReceiver {
public long[] energyUws;
public long[] timestamps;
GetPowerMonitorsResult() {
super(null);
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
energyUws = resultData.getLongArray(IPowerStatsService.KEY_ENERGY);
timestamps = resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS);
}
}
@Test
public void getPowerMonitors() {
mMockClock.realtime = 10 * 60_000;
mPowerStatsHALWrapper.buildEnergyConsumerResult();
EnergyConsumerResult[] energyConsumerResults = mPowerStatsHALWrapper.energyConsumerResults;
for (int i = 0; i < energyConsumerResults.length; i++) {
energyConsumerResults[i].energyUWs = 42 + 100 * i;
energyConsumerResults[i].timestampMs = mMockClock.realtime + 100 * i;
}
mPowerStatsHALWrapper.buildEnergyMeasurements();
EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.energyMeasurements;
for (int i = 0; i < energyMeasurements.length; i++) {
energyMeasurements[i].energyUWs = 314 + 200 * i;
energyMeasurements[i].timestampMs = mMockClock.realtime + 200 * i;
}
GetSupportedPowerMonitorsResult supportedPowerMonitorsResult =
new GetSupportedPowerMonitorsResult();
mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
Map<String, PowerMonitor> map =
Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
.collect(Collectors.toMap(pm -> pm.name, pm -> pm));
PowerMonitor consumer1 = map.get("energyconsumer0");
PowerMonitor consumer2 = map.get("BLUETOOTH/1");
PowerMonitor measurement1 = map.get("[channelname0]:channelsubsystem0");
PowerMonitor measurement2 = map.get("[channelname1]:channelsubsystem1");
GetPowerMonitorsResult result = new GetPowerMonitorsResult();
mService.getPowerMonitorReadingsImpl(
new int[]{consumer1.index, consumer2.index, measurement1.index,
measurement2.index}, result);
assertThat(result.energyUws).isEqualTo(new long[]{42, 142, 314, 514});
assertThat(result.timestamps).isEqualTo(new long[]{600_000, 600_100, 600_000, 600_200});
// Test caching/throttling
mMockClock.realtime += 1;
for (EnergyConsumerResult energyConsumerResult : energyConsumerResults) {
energyConsumerResult.energyUWs = 300;
energyConsumerResult.timestampMs = mMockClock.realtime + 300;
}
for (EnergyMeasurement energyMeasurement : energyMeasurements) {
energyMeasurement.energyUWs = 400;
energyMeasurement.timestampMs = mMockClock.realtime + 400;
}
mService.getPowerMonitorReadingsImpl(new int[]{consumer1.index, measurement1.index},
result);
assertThat(result.energyUws).isEqualTo(new long[]{42, 314});
assertThat(result.timestamps).isEqualTo(new long[]{600_000, 600_000});
mMockClock.realtime += 10 * 60000;
mService.getPowerMonitorReadingsImpl(new int[]{consumer1.index, measurement1.index},
result);
assertThat(result.energyUws).isEqualTo(new long[]{300, 400});
assertThat(result.timestamps).isEqualTo(new long[]{600_301, 600_401});
}
}