Merge changes from topic "odpm_api"
* changes: Add random noise to power monitor readings Add random noise generator for Power Monitor API Add feature flag for PowerMonitor API Add API SystemHealthManager.getPowerMonitorReadings
This commit is contained in:
committed by
Android (Google) Code Review
commit
c75113a73d
@@ -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,
|
||||
|
||||
39
core/java/android/os/IPowerStatsService.aidl
Normal file
39
core/java/android/os/IPowerStatsService.aidl
Normal 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);
|
||||
}
|
||||
20
core/java/android/os/PowerMonitor.aidl
Normal file
20
core/java/android/os/PowerMonitor.aidl
Normal 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;
|
||||
106
core/java/android/os/PowerMonitor.java
Normal file
106
core/java/android/os/PowerMonitor.java
Normal 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
92
core/java/android/os/PowerMonitorReadings.java
Normal file
92
core/java/android/os/PowerMonitorReadings.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,6 +186,7 @@ java_library_static {
|
||||
"com.android.sysprop.watchdog",
|
||||
"ImmutabilityAnnotation",
|
||||
"securebox",
|
||||
"apache-commons-math",
|
||||
],
|
||||
javac_shard_size: 50,
|
||||
javacflags: [
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 com.android.server.powerstats;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import org.apache.commons.math.MathException;
|
||||
import org.apache.commons.math.distribution.AbstractContinuousDistribution;
|
||||
import org.apache.commons.math.distribution.BetaDistributionImpl;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Adds random noise to provided value, keeping it within the limits of a specified range.
|
||||
* @hide
|
||||
*/
|
||||
public class IntervalRandomNoiseGenerator {
|
||||
private static final int DISTRIBUTION_SAMPLE_SIZE = 17;
|
||||
|
||||
private final AbstractContinuousDistribution mDistribution;
|
||||
private final double[] mSamples = new double[DISTRIBUTION_SAMPLE_SIZE];
|
||||
|
||||
private static final double UNINITIALIZED = -1;
|
||||
|
||||
/**
|
||||
* Higher alpha makes the distribution more asymmetrical, tightening it
|
||||
* closer to the high bound. A value of alpha should be > 1 to ensure
|
||||
* that the samples closer to 1 appear more frequently t those closer
|
||||
* to 0.
|
||||
*/
|
||||
IntervalRandomNoiseGenerator(double alpha) {
|
||||
if (alpha <= 1) {
|
||||
throw new IllegalArgumentException("alpha should be > 1");
|
||||
}
|
||||
mDistribution = new BetaDistributionImpl(alpha, 1 /* beta */);
|
||||
refresh();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void reseed(long seed) {
|
||||
mDistribution.reseedRandomGenerator(seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random value between the specified bounds, statistically closer to the
|
||||
* highProbabilityBound.
|
||||
*
|
||||
* The same value is returned for a given stickyKey until {@link #refresh()} is called.
|
||||
*/
|
||||
long addNoise(long lowProbabilityBound, long highProbabilityBound, int stickyKey) {
|
||||
double sample = mSamples[stickyKey % DISTRIBUTION_SAMPLE_SIZE];
|
||||
if (sample < 0) { // UNINITIALIZED
|
||||
try {
|
||||
sample = mDistribution.sample();
|
||||
} catch (MathException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
mSamples[stickyKey % DISTRIBUTION_SAMPLE_SIZE] = sample;
|
||||
}
|
||||
return lowProbabilityBound + (long) ((highProbabilityBound - lowProbabilityBound) * sample);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the cache of random samples.
|
||||
*/
|
||||
void refresh() {
|
||||
Arrays.fill(mSamples, UNINITIALIZED);
|
||||
}
|
||||
}
|
||||
@@ -16,27 +16,39 @@
|
||||
|
||||
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.HandlerExecutor;
|
||||
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.provider.DeviceConfig;
|
||||
import android.provider.DeviceConfigInterface;
|
||||
import android.util.Log;
|
||||
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,7 +58,11 @@ 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.Locale;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* This class provides a system service that estimates system power usage
|
||||
@@ -65,8 +81,19 @@ 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;
|
||||
|
||||
static final String KEY_POWER_MONITOR_API_ENABLED = "power_monitor_api_enabled";
|
||||
|
||||
// The alpha parameter of the Beta distribution used by the random noise generator.
|
||||
// The higher this value, the smaller the amount of added noise.
|
||||
private static final double INTERVAL_RANDOM_NOISE_GENERATION_ALPHA = 50;
|
||||
private static final long MAX_RANDOM_NOISE_UWS = 10_000_000;
|
||||
|
||||
private final Injector mInjector;
|
||||
private final Clock mClock;
|
||||
private final DeviceConfigInterface mDeviceConfig;
|
||||
private final DeviceConfigListener mDeviceConfigListener = new DeviceConfigListener();
|
||||
private File mDataStoragePath;
|
||||
|
||||
private Context mContext;
|
||||
@@ -83,15 +110,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);
|
||||
@@ -158,9 +193,30 @@ public class PowerStatsService extends SystemService {
|
||||
PowerStatsInternal powerStatsInternal) {
|
||||
return new StatsPullAtomCallbackImpl(context, powerStatsInternal);
|
||||
}
|
||||
|
||||
DeviceConfigInterface getDeviceConfig() {
|
||||
return DeviceConfigInterface.REAL;
|
||||
}
|
||||
|
||||
IntervalRandomNoiseGenerator createIntervalRandomNoiseGenerator() {
|
||||
return new IntervalRandomNoiseGenerator(INTERVAL_RANDOM_NOISE_GENERATION_ALPHA);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
int callingUid = Binder.getCallingUid();
|
||||
getHandler().post(() ->
|
||||
getPowerMonitorReadingsImpl(powerMonitorIds, resultReceiver, callingUid));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
|
||||
@@ -191,6 +247,20 @@ public class PowerStatsService extends SystemService {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
|
||||
public Executor mExecutor = new HandlerExecutor(getHandler());
|
||||
|
||||
void startListening() {
|
||||
mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BATTERY_STATS,
|
||||
mExecutor, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertiesChanged(DeviceConfig.Properties properties) {
|
||||
refreshFlags();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -208,11 +278,13 @@ 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() {
|
||||
mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal);
|
||||
mDeviceConfigListener.startListening();
|
||||
refreshFlags();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -263,6 +335,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 +353,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 +371,16 @@ public class PowerStatsService extends SystemService {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mInjector = injector;
|
||||
mClock = injector.getClock();
|
||||
mDeviceConfig = injector.getDeviceConfig();
|
||||
}
|
||||
|
||||
void refreshFlags() {
|
||||
setPowerMonitorApiEnabled(mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BATTERY_STATS,
|
||||
KEY_POWER_MONITOR_API_ENABLED, true));
|
||||
}
|
||||
|
||||
private final class LocalService extends PowerStatsInternal {
|
||||
private final Handler mHandler;
|
||||
|
||||
LocalService() {
|
||||
mHandler = new Handler(getLooper());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public EnergyConsumer[] getEnergyConsumerInfo() {
|
||||
@@ -300,9 +391,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 +405,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 +419,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 +501,280 @@ 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;
|
||||
public long prevEnergyUws;
|
||||
|
||||
private PowerMonitorState(PowerMonitor powerMonitor, int id) {
|
||||
this.powerMonitor = powerMonitor;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean mPowerMonitorApiEnabled = true;
|
||||
private volatile PowerMonitor[] mPowerMonitors;
|
||||
private PowerMonitorState[] mPowerMonitorStates;
|
||||
private IntervalRandomNoiseGenerator mIntervalRandomNoiseGenerator;
|
||||
|
||||
private void setPowerMonitorApiEnabled(boolean powerMonitorApiEnabled) {
|
||||
if (powerMonitorApiEnabled != mPowerMonitorApiEnabled) {
|
||||
mPowerMonitorApiEnabled = powerMonitorApiEnabled;
|
||||
mPowerMonitors = null;
|
||||
mPowerMonitorStates = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ensurePowerMonitors() {
|
||||
if (mPowerMonitors != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (mPowerMonitors != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIntervalRandomNoiseGenerator == null) {
|
||||
mIntervalRandomNoiseGenerator = mInjector.createIntervalRandomNoiseGenerator();
|
||||
}
|
||||
|
||||
if (!mPowerMonitorApiEnabled) {
|
||||
mPowerMonitors = new PowerMonitor[0];
|
||||
mPowerMonitorStates = new PowerMonitorState[0];
|
||||
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, int callingUid) {
|
||||
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);
|
||||
mIntervalRandomNoiseGenerator.refresh();
|
||||
}
|
||||
|
||||
long[] energy = new long[powerMonitorStates.length];
|
||||
long[] timestamps = new long[powerMonitorStates.length];
|
||||
for (int i = 0; i < powerMonitorStates.length; i++) {
|
||||
PowerMonitorState state = powerMonitorStates[i];
|
||||
if (state.energyUws != PowerMonitorReadings.ENERGY_UNAVAILABLE
|
||||
&& state.prevEnergyUws != PowerMonitorReadings.ENERGY_UNAVAILABLE) {
|
||||
energy[i] = mIntervalRandomNoiseGenerator.addNoise(
|
||||
Math.max(state.prevEnergyUws, state.energyUws - MAX_RANDOM_NOISE_UWS),
|
||||
state.energyUws, callingUid);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, String.format(Locale.ENGLISH,
|
||||
"Monitor=%s timestamp=%d energy=%d"
|
||||
+ " uid=%d noise=%.1f%% returned=%d",
|
||||
state.powerMonitor.name,
|
||||
state.timestampMs,
|
||||
state.energyUws,
|
||||
callingUid,
|
||||
state.energyUws != state.prevEnergyUws
|
||||
? (state.energyUws - energy[i]) * 100.0
|
||||
/ (state.energyUws - state.prevEnergyUws)
|
||||
: 0,
|
||||
energy[i]));
|
||||
}
|
||||
} else {
|
||||
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.prevEnergyUws = powerMonitorState.energyUws;
|
||||
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.prevEnergyUws = powerMonitorState.energyUws;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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 com.android.server.powerstats;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class IntervalRandomNoiseGeneratorTest {
|
||||
|
||||
@Test
|
||||
public void parameterizedDistribution() {
|
||||
// Assert closeness to theoretical distribution
|
||||
assertDistribution(3.0,
|
||||
0.0392,
|
||||
0.2617,
|
||||
0.6990);
|
||||
|
||||
assertDistribution(5.0,
|
||||
0.0003,
|
||||
0.0098,
|
||||
0.0676,
|
||||
0.2502,
|
||||
0.6720);
|
||||
|
||||
assertDistribution(9.0,
|
||||
0.0000,
|
||||
0.0002,
|
||||
0.0097,
|
||||
0.1242,
|
||||
0.8658);
|
||||
}
|
||||
|
||||
private void assertDistribution(double alpha, Double... expectedBuckets) {
|
||||
IntervalRandomNoiseGenerator generator = new IntervalRandomNoiseGenerator(alpha);
|
||||
generator.reseed(42); // Make test repeatable
|
||||
final int sampleCount = 1000;
|
||||
final int bucketCount = expectedBuckets.length;
|
||||
int[] histogram = buildHistogram(() -> {
|
||||
generator.refresh();
|
||||
return generator.addNoise(100, 200, 12345);
|
||||
}, sampleCount, bucketCount, 100, 200);
|
||||
|
||||
for (int i = 0; i < expectedBuckets.length; i++) {
|
||||
assertWithMessage("Bucket #" + i)
|
||||
.that((double) histogram[i] / sampleCount)
|
||||
.isWithin(0.05)
|
||||
.of(expectedBuckets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private int[] buildHistogram(Supplier<Long> generator, int sampleCount,
|
||||
int bucketCount, int lowBound, int highBound) {
|
||||
int[] buckets = new int[bucketCount];
|
||||
for (int i = 0; i < sampleCount; i++) {
|
||||
long sample = generator.get();
|
||||
assertThat(sample).isAtLeast(lowBound);
|
||||
assertThat(sample).isAtMost(highBound);
|
||||
buckets[(int) ((double) (sample - lowBound) / (highBound - lowBound) * bucketCount)]++;
|
||||
}
|
||||
return buckets;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stickiness() {
|
||||
IntervalRandomNoiseGenerator generator = new IntervalRandomNoiseGenerator(9);
|
||||
generator.reseed(42); // Make test repeatable
|
||||
|
||||
long value1a = generator.addNoise(1000, 5000, 123);
|
||||
long value1b = generator.addNoise(1000, 5000, 123);
|
||||
long value1c = generator.addNoise(1000, 5000, 123);
|
||||
assertThat(value1b).isEqualTo(value1a);
|
||||
assertThat(value1c).isEqualTo(value1a);
|
||||
|
||||
// Different stickyKey
|
||||
long value2a = generator.addNoise(1000, 5000, 321);
|
||||
long value2b = generator.addNoise(1000, 5000, 321);
|
||||
assertThat(value2a).isNotEqualTo(value1a);
|
||||
assertThat(value2b).isEqualTo(value2a);
|
||||
|
||||
generator.refresh();
|
||||
|
||||
// Same stickyKey after a refresh - different value
|
||||
long value3 = generator.addNoise(1000, 5000, 123);
|
||||
long value4 = generator.addNoise(1000, 5000, 321);
|
||||
assertThat(value3).isNotEqualTo(value1a);
|
||||
assertThat(value4).isNotEqualTo(value2a);
|
||||
}
|
||||
}
|
||||
@@ -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,17 @@ 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 android.provider.DeviceConfig;
|
||||
import android.provider.DeviceConfigInterface;
|
||||
|
||||
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;
|
||||
@@ -46,6 +55,7 @@ import com.android.server.powerstats.nano.PowerStatsServiceResidencyProto;
|
||||
import com.android.server.powerstats.nano.StateProto;
|
||||
import com.android.server.powerstats.nano.StateResidencyProto;
|
||||
import com.android.server.powerstats.nano.StateResidencyResultProto;
|
||||
import com.android.server.testutils.FakeDeviceConfigInterface;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -58,13 +68,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();
|
||||
@@ -87,16 +99,35 @@ public class PowerStatsServiceTest {
|
||||
private static final int POWER_ENTITY_COUNT = 3;
|
||||
private static final int STATE_INFO_COUNT = 5;
|
||||
private static final int STATE_RESIDENCY_COUNT = 4;
|
||||
private static final int APP_UID = 10042;
|
||||
|
||||
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 DeviceConfigInterface mMockDeviceConfig = new FakeDeviceConfigInterface();
|
||||
private IntervalRandomNoiseGenerator mMockNoiseGenerator = new IntervalRandomNoiseGenerator(42);
|
||||
|
||||
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 +173,7 @@ public class PowerStatsServiceTest {
|
||||
|
||||
@Override
|
||||
IPowerStatsHALWrapper getPowerStatsHALWrapperImpl() {
|
||||
return mTestPowerStatsHALWrapper;
|
||||
return mPowerStatsHALWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,29 +183,41 @@ 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;
|
||||
}
|
||||
|
||||
DeviceConfigInterface getDeviceConfig() {
|
||||
return mMockDeviceConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
IntervalRandomNoiseGenerator createIntervalRandomNoiseGenerator() {
|
||||
return mMockNoiseGenerator;
|
||||
}
|
||||
};
|
||||
|
||||
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 +238,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 +269,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 +303,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 +334,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 +380,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 +419,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 +1059,143 @@ 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 int resultCode;
|
||||
public long[] energyUws;
|
||||
public long[] timestamps;
|
||||
|
||||
GetPowerMonitorsResult() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
this.resultCode = resultCode;
|
||||
if (resultData != null) {
|
||||
energyUws = resultData.getLongArray(IPowerStatsService.KEY_ENERGY);
|
||||
timestamps = resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPowerMonitors() {
|
||||
mMockClock.realtime = 10 * 60_000;
|
||||
mMockNoiseGenerator.reseed(314);
|
||||
|
||||
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, APP_UID);
|
||||
|
||||
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, APP_UID);
|
||||
|
||||
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, APP_UID);
|
||||
|
||||
// This time, random noise is added
|
||||
assertThat(result.energyUws).isEqualTo(new long[]{298, 399});
|
||||
assertThat(result.timestamps).isEqualTo(new long[]{600_301, 600_401});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void featureFlag() {
|
||||
mMockDeviceConfig.setProperty(DeviceConfig.NAMESPACE_BATTERY_STATS,
|
||||
PowerStatsService.KEY_POWER_MONITOR_API_ENABLED, "false", false);
|
||||
|
||||
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
|
||||
|
||||
GetSupportedPowerMonitorsResult supportedPowerMonitorsResult =
|
||||
new GetSupportedPowerMonitorsResult();
|
||||
mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
|
||||
assertThat(supportedPowerMonitorsResult.powerMonitors).isNotNull();
|
||||
assertThat(supportedPowerMonitorsResult.powerMonitors).isEmpty();
|
||||
|
||||
GetPowerMonitorsResult getPowerMonitorsResult = new GetPowerMonitorsResult();
|
||||
mService.getPowerMonitorReadingsImpl(new int[]{0}, getPowerMonitorsResult, APP_UID);
|
||||
assertThat(getPowerMonitorsResult.resultCode).isEqualTo(
|
||||
IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR);
|
||||
|
||||
mMockDeviceConfig.setProperty(DeviceConfig.NAMESPACE_BATTERY_STATS,
|
||||
PowerStatsService.KEY_POWER_MONITOR_API_ENABLED, "true", false);
|
||||
supportedPowerMonitorsResult = new GetSupportedPowerMonitorsResult();
|
||||
mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
|
||||
assertThat(Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
|
||||
.map(pm -> pm.name).toList()).contains("energyconsumer0");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user