Merge "Add a power load test"

This commit is contained in:
TreeHugger Robot
2020-09-09 02:09:43 +00:00
committed by Android (Google) Code Review
7 changed files with 908 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
android_test {
name: "PowerStatsLoadTests",
srcs: ["src/**/*.java"],
static_libs: [
"androidx.test.rules",
"androidx.test.ext.junit",
"compatibility-device-util-axt",
"junit",
],
libs: ["android.test.runner"],
platform_apis: true,
certificate: "platform",
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.core.powerstatsloadtests">
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.BATTERY_STATS"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.frameworks.core.powerstatsloadtests"
android:label="Power Stats Load Tests" />
<queries>
<!-- The load test resolves http://... intents. Let it do so. -->
<package android:name="com.android.chrome"/>
</queries>
</manifest>

View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) 2020 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.frameworks.core.powerstatsloadtests;
import static org.junit.Assert.assertEquals;
import android.app.Instrumentation;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.SystemUtil;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class ConnectivitySetupRule implements TestRule {
private final boolean mWifiEnabled;
private final ConnectivityManager mConnectivityManager;
private final WifiManager mWifiManager;
private boolean mInitialWifiState;
public ConnectivitySetupRule(boolean wifiEnabled) {
mWifiEnabled = wifiEnabled;
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
Context context = instrumentation.getContext();
mConnectivityManager = context.getSystemService(ConnectivityManager.class);
mWifiManager = context.getSystemService(WifiManager.class);
}
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
mInitialWifiState = isWiFiConnected();
setWiFiState(mWifiEnabled);
base.evaluate();
} finally {
setWiFiState(mInitialWifiState);
}
}
};
}
private void setWiFiState(final boolean enable) throws InterruptedException {
boolean wiFiConnected = isWiFiConnected();
if (enable == wiFiConnected) {
return;
}
NetworkTracker tracker = new NetworkTracker(!mWifiEnabled);
mConnectivityManager.registerNetworkCallback(
new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED).build(),
tracker);
if (enable) {
SystemUtil.runShellCommand("svc wifi enable");
//noinspection deprecation
SystemUtil.runWithShellPermissionIdentity(mWifiManager::reconnect,
android.Manifest.permission.NETWORK_SETTINGS);
} else {
SystemUtil.runShellCommand("svc wifi disable");
}
tracker.waitForExpectedState();
assertEquals("Wifi must be " + (enable ? "connected to" : "disconnected from")
+ " an access point for this test.", enable, isWiFiConnected());
mConnectivityManager.unregisterNetworkCallback(tracker);
}
private boolean isWiFiConnected() {
return mWifiManager.isWifiEnabled() && mConnectivityManager.getActiveNetwork() != null
&& !mConnectivityManager.isActiveNetworkMetered();
}
private class NetworkTracker extends ConnectivityManager.NetworkCallback {
private static final int MSG_CHECK_ACTIVE_NETWORK = 1;
private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
private final boolean mExpectedMetered;
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_CHECK_ACTIVE_NETWORK) {
checkActiveNetwork();
}
}
};
private NetworkTracker(boolean expectedMetered) {
mExpectedMetered = expectedMetered;
}
@Override
public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
LinkProperties linkProperties, boolean blocked) {
checkActiveNetwork();
}
@Override
public void onLost(Network network) {
checkActiveNetwork();
}
boolean waitForExpectedState() throws InterruptedException {
checkActiveNetwork();
return mReceiveLatch.await(60, TimeUnit.SECONDS);
}
private void checkActiveNetwork() {
if (mReceiveLatch.getCount() == 0) {
return;
}
if (mConnectivityManager.getActiveNetwork() != null
&& mConnectivityManager.isActiveNetworkMetered() == mExpectedMetered) {
mReceiveLatch.countDown();
} else {
mHandler.sendEmptyMessageDelayed(MSG_CHECK_ACTIVE_NETWORK, 5000);
}
}
}
}

View File

@@ -0,0 +1,278 @@
/*
* Copyright (C) 2020 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.frameworks.core.powerstatsloadtests;
import android.os.Process;
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
import java.util.ArrayList;
import java.util.List;
public class PowerMetrics {
private static final String PACKAGE_CALENDAR_PROVIDER = "com.android.providers.calendar";
private static final String PACKAGE_MEDIA_PROVIDER = "com.android.providers.media";
private static final String PACKAGE_SYSTEMUI = "com.android.systemui";
private static final String[] PACKAGES_SYSTEM = {PACKAGE_MEDIA_PROVIDER,
PACKAGE_CALENDAR_PROVIDER, PACKAGE_SYSTEMUI};
enum MetricKind {
POWER,
DURATION,
}
public static final String METRIC_APP_POWER = "appPower";
public static final String METRIC_APP_POWER_EXCLUDE_SYSTEM_FROM_TOTAL = "appPowerExcludeSystem";
public static final String METRIC_APP_POWER_EXCLUDE_SMEARED = "appPowerExcludeSmeared";
public static final String METRIC_SCREEN_POWER = "screenPower";
public static final String METRIC_WIFI_POWER = "wifiPower";
public static final String METRIC_SYSTEM_SERVICE_CPU_POWER = "systemService";
public static final String METRIC_OTHER_POWER = "otherPower";
public static final String METRIC_CPU_POWER = "cpuPower";
public static final String METRIC_RAM_POWER = "ramPower";
public static final String METRIC_WAKELOCK_POWER = "wakelockPower";
public static final String METRIC_MOBILE_RADIO_POWER = "mobileRadioPower";
public static final String METRIC_BLUETOOTH_POWER = "bluetoothPower";
public static final String METRIC_GPS_POWER = "gpsPower";
public static final String METRIC_CAMERA_POWER = "cameraPower";
public static final String METRIC_FLASHLIGHT_POWER = "flashlightPower";
public static final String METRIC_SENSORS_POWER = "sensorsPower";
public static final String METRIC_AUDIO_POWER = "audioPower";
public static final String METRIC_VIDEO_POWER = "videoPower";
public static final String METRIC_CPU_TIME = "cpuTime";
public static final String METRIC_CPU_FOREGROUND_TIME = "cpuForegroundTime";
public static final String METRIC_WAKELOCK_TIME = "wakelockTime";
public static final String METRIC_WIFI_RUNNING_TIME = "wifiRunningTime";
public static final String METRIC_BLUETOOTH_RUNNING_TIME = "bluetoothRunningTime";
public static final String METRIC_GPS_TIME = "gpsTime";
public static final String METRIC_CAMERA_TIME = "cameraTime";
public static final String METRIC_FLASHLIGHT_TIME = "flashlightTime";
public static final String METRIC_AUDIO_TIME = "audioTime";
public static final String METRIC_VIDEO_TIME = "videoTime";
public static class Metric {
public String metricType;
public MetricKind metricKind;
public String title;
public double value;
public double total;
}
private final double mMinDrainedPower;
private final double mMaxDrainedPower;
private List<Metric> mMetrics = new ArrayList<>();
public PowerMetrics(BatteryStatsHelper batteryStatsHelper, int uid) {
mMinDrainedPower = batteryStatsHelper.getMinDrainedPower();
mMaxDrainedPower = batteryStatsHelper.getMaxDrainedPower();
List<BatterySipper> usageList = batteryStatsHelper.getUsageList();
double totalPowerMah = 0;
double totalSmearedPowerMah = 0;
double totalPowerExcludeSystemMah = 0;
double totalScreenPower = 0;
double totalProportionalSmearMah = 0;
double totalCpuPowerMah = 0;
double totalSystemServiceCpuPowerMah = 0;
double totalUsagePowerMah = 0;
double totalWakeLockPowerMah = 0;
double totalMobileRadioPowerMah = 0;
double totalWifiPowerMah = 0;
double totalBluetoothPowerMah = 0;
double totalGpsPowerMah = 0;
double totalCameraPowerMah = 0;
double totalFlashlightPowerMah = 0;
double totalSensorPowerMah = 0;
double totalAudioPowerMah = 0;
double totalVideoPowerMah = 0;
long totalCpuTimeMs = 0;
long totalCpuFgTimeMs = 0;
long totalWakeLockTimeMs = 0;
long totalWifiRunningTimeMs = 0;
long totalBluetoothRunningTimeMs = 0;
long totalGpsTimeMs = 0;
long totalCameraTimeMs = 0;
long totalFlashlightTimeMs = 0;
long totalAudioTimeMs = 0;
long totalVideoTimeMs = 0;
BatterySipper uidSipper = null;
for (BatterySipper sipper : usageList) {
if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
totalScreenPower = sipper.sumPower();
}
if (isHiddenDrainType(sipper.drainType)) {
continue;
}
if (sipper.drainType == BatterySipper.DrainType.APP && sipper.getUid() == uid) {
uidSipper = sipper;
}
totalPowerMah += sipper.sumPower();
totalSmearedPowerMah += sipper.totalSmearedPowerMah;
totalProportionalSmearMah += sipper.proportionalSmearMah;
if (!isSystemSipper(sipper)) {
totalPowerExcludeSystemMah += sipper.totalSmearedPowerMah;
}
totalCpuPowerMah += sipper.cpuPowerMah;
totalSystemServiceCpuPowerMah += sipper.systemServiceCpuPowerMah;
totalUsagePowerMah += sipper.usagePowerMah;
totalWakeLockPowerMah += sipper.wakeLockPowerMah;
totalMobileRadioPowerMah += sipper.mobileRadioPowerMah;
totalWifiPowerMah += sipper.wifiPowerMah;
totalBluetoothPowerMah += sipper.bluetoothPowerMah;
totalGpsPowerMah += sipper.gpsPowerMah;
totalCameraPowerMah += sipper.cameraPowerMah;
totalFlashlightPowerMah += sipper.flashlightPowerMah;
totalSensorPowerMah += sipper.sensorPowerMah;
totalAudioPowerMah += sipper.audioPowerMah;
totalVideoPowerMah += sipper.videoPowerMah;
totalCpuTimeMs += sipper.cpuTimeMs;
totalCpuFgTimeMs += sipper.cpuFgTimeMs;
totalWakeLockTimeMs += sipper.wakeLockTimeMs;
totalWifiRunningTimeMs += sipper.wifiRunningTimeMs;
totalBluetoothRunningTimeMs += sipper.bluetoothRunningTimeMs;
totalGpsTimeMs += sipper.gpsTimeMs;
totalCameraTimeMs += sipper.cameraTimeMs;
totalFlashlightTimeMs += sipper.flashlightTimeMs;
totalAudioTimeMs += sipper.audioTimeMs;
totalVideoTimeMs += sipper.videoTimeMs;
}
if (uidSipper == null) {
return;
}
addMetric(METRIC_APP_POWER, MetricKind.POWER, "Total power",
uidSipper.totalSmearedPowerMah, totalSmearedPowerMah);
addMetric(METRIC_APP_POWER_EXCLUDE_SYSTEM_FROM_TOTAL, MetricKind.POWER,
"Total power excluding system",
uidSipper.totalSmearedPowerMah, totalPowerExcludeSystemMah);
addMetric(METRIC_SCREEN_POWER, MetricKind.POWER, "Screen, smeared",
uidSipper.screenPowerMah, totalScreenPower);
addMetric(METRIC_OTHER_POWER, MetricKind.POWER, "Other, smeared",
uidSipper.proportionalSmearMah, totalProportionalSmearMah);
addMetric(METRIC_APP_POWER_EXCLUDE_SMEARED, MetricKind.POWER, "Excluding smeared",
uidSipper.totalPowerMah, totalPowerMah);
addMetric(METRIC_CPU_POWER, MetricKind.POWER, "CPU",
uidSipper.cpuPowerMah, totalCpuPowerMah);
addMetric(METRIC_SYSTEM_SERVICE_CPU_POWER, MetricKind.POWER, "System services",
uidSipper.systemServiceCpuPowerMah, totalSystemServiceCpuPowerMah);
addMetric(METRIC_RAM_POWER, MetricKind.POWER, "RAM",
uidSipper.usagePowerMah, totalUsagePowerMah);
addMetric(METRIC_WAKELOCK_POWER, MetricKind.POWER, "Wake lock",
uidSipper.wakeLockPowerMah, totalWakeLockPowerMah);
addMetric(METRIC_MOBILE_RADIO_POWER, MetricKind.POWER, "Mobile radio",
uidSipper.mobileRadioPowerMah, totalMobileRadioPowerMah);
addMetric(METRIC_WIFI_POWER, MetricKind.POWER, "WiFi",
uidSipper.wifiPowerMah, totalWifiPowerMah);
addMetric(METRIC_BLUETOOTH_POWER, MetricKind.POWER, "Bluetooth",
uidSipper.bluetoothPowerMah, totalBluetoothPowerMah);
addMetric(METRIC_GPS_POWER, MetricKind.POWER, "GPS",
uidSipper.gpsPowerMah, totalGpsPowerMah);
addMetric(METRIC_CAMERA_POWER, MetricKind.POWER, "Camera",
uidSipper.cameraPowerMah, totalCameraPowerMah);
addMetric(METRIC_FLASHLIGHT_POWER, MetricKind.POWER, "Flashlight",
uidSipper.flashlightPowerMah, totalFlashlightPowerMah);
addMetric(METRIC_SENSORS_POWER, MetricKind.POWER, "Sensors",
uidSipper.sensorPowerMah, totalSensorPowerMah);
addMetric(METRIC_AUDIO_POWER, MetricKind.POWER, "Audio",
uidSipper.audioPowerMah, totalAudioPowerMah);
addMetric(METRIC_VIDEO_POWER, MetricKind.POWER, "Video",
uidSipper.videoPowerMah, totalVideoPowerMah);
addMetric(METRIC_CPU_TIME, MetricKind.DURATION, "CPU time",
uidSipper.cpuTimeMs, totalCpuTimeMs);
addMetric(METRIC_CPU_FOREGROUND_TIME, MetricKind.DURATION, "CPU foreground time",
uidSipper.cpuFgTimeMs, totalCpuFgTimeMs);
addMetric(METRIC_WAKELOCK_TIME, MetricKind.DURATION, "Wake lock time",
uidSipper.wakeLockTimeMs, totalWakeLockTimeMs);
addMetric(METRIC_WIFI_RUNNING_TIME, MetricKind.DURATION, "WiFi running time",
uidSipper.wifiRunningTimeMs, totalWifiRunningTimeMs);
addMetric(METRIC_BLUETOOTH_RUNNING_TIME, MetricKind.DURATION, "Bluetooth time",
uidSipper.bluetoothRunningTimeMs, totalBluetoothRunningTimeMs);
addMetric(METRIC_GPS_TIME, MetricKind.DURATION, "GPS time",
uidSipper.gpsTimeMs, totalGpsTimeMs);
addMetric(METRIC_CAMERA_TIME, MetricKind.DURATION, "Camera time",
uidSipper.cameraTimeMs, totalCameraTimeMs);
addMetric(METRIC_FLASHLIGHT_TIME, MetricKind.DURATION, "Flashlight time",
uidSipper.flashlightTimeMs, totalFlashlightTimeMs);
addMetric(METRIC_AUDIO_TIME, MetricKind.DURATION, "Audio time",
uidSipper.audioTimeMs, totalAudioTimeMs);
addMetric(METRIC_VIDEO_TIME, MetricKind.DURATION, "Video time",
uidSipper.videoTimeMs, totalVideoTimeMs);
}
public List<Metric> getMetrics() {
return mMetrics;
}
public double getMinDrainedPower() {
return mMinDrainedPower;
}
public double getMaxDrainedPower() {
return mMaxDrainedPower;
}
protected boolean isHiddenDrainType(BatterySipper.DrainType drainType) {
return drainType == BatterySipper.DrainType.IDLE
|| drainType == BatterySipper.DrainType.CELL
|| drainType == BatterySipper.DrainType.SCREEN
|| drainType == BatterySipper.DrainType.UNACCOUNTED
|| drainType == BatterySipper.DrainType.OVERCOUNTED
|| drainType == BatterySipper.DrainType.BLUETOOTH
|| drainType == BatterySipper.DrainType.WIFI;
}
private boolean isSystemSipper(BatterySipper sipper) {
final int uid = sipper.uidObj == null ? -1 : sipper.getUid();
if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) {
return true;
} else if (sipper.mPackages != null) {
for (final String packageName : sipper.mPackages) {
for (final String systemPackage : PACKAGES_SYSTEM) {
if (systemPackage.equals(packageName)) {
return true;
}
}
}
}
return false;
}
private void addMetric(String metricType, MetricKind metricKind, String title, double amount,
double totalAmount) {
Metric metric = new Metric();
metric.metricType = metricType;
metric.metricKind = metricKind;
metric.title = title;
metric.value = amount;
metric.total = totalAmount;
mMetrics.add(metric);
}
}

View File

@@ -0,0 +1,281 @@
/*
* Copyright (C) 2020 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.frameworks.core.powerstatsloadtests;
import static org.junit.Assert.fail;
import android.app.Instrumentation;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Bundle;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserManager;
import android.util.Log;
import android.util.TimeUtils;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.os.BatteryStatsHelper;
import com.android.internal.os.LoggingPrintStream;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class PowerMetricsCollector implements TestRule {
private final String mTag;
private final float mBatteryDrainThresholdPct;
private final int mTimeoutMillis;
private final Context mContext;
private final UserManager mUserManager;
private final int mUid;
private final BatteryStatsHelper mStatsHelper;
private long mStartTime;
private volatile float mInitialBatteryLevel;
private volatile float mCurrentBatteryLevel;
private int mIterations;
private PowerMetrics mInitialPowerMetrics;
private PowerMetrics mFinalPowerMetrics;
private List<PowerMetrics.Metric> mPowerMetricsDelta;
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
disableCharger();
try {
prepareBatteryLevelMonitor();
mStartTime = SystemClock.uptimeMillis();
base.evaluate();
captureFinalPowerStatsData();
} finally {
enableCharger();
}
}
};
}
public PowerMetricsCollector(String tag, float batteryDrainThresholdPct, int timeoutMillis) {
mTag = tag;
mBatteryDrainThresholdPct = batteryDrainThresholdPct;
mTimeoutMillis = timeoutMillis;
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
mContext = instrumentation.getContext();
mUid = Process.myUid();
mUserManager = mContext.getSystemService(UserManager.class);
mStatsHelper = new BatteryStatsHelper(mContext, false /* collectBatteryBroadcast */);
mStatsHelper.create((Bundle) null);
}
private void disableCharger() {
// TODO(b/167636754): implement this method once the charger suspension API is available
}
private void enableCharger() {
// TODO(b/167636754): implement this method once the charger suspension API is available
}
private PowerMetrics readBatteryStatsData() {
mStatsHelper.clearStats();
mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED,
mUserManager.getUserProfiles());
return new PowerMetrics(mStatsHelper, mUid);
}
protected void prepareBatteryLevelMonitor() {
Intent batteryStatus = mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
handleBatteryStatus(intent);
}
}, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
handleBatteryStatus(batteryStatus);
mInitialBatteryLevel = mCurrentBatteryLevel;
}
protected void handleBatteryStatus(Intent intent) {
if (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) != 0) {
fail("Device must remain disconnected from the power source "
+ "for the duration of the test");
}
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
mCurrentBatteryLevel = level * 100 / (float) scale;
Log.i(mTag, "Battery level = " + mCurrentBatteryLevel);
// We delay tracking until the battery level drops. If the resolution of
// battery level is 1%, and the initially reported level is 73, we don't know whether
// it's 73.1 or 73.7. Once it drops to 72, we can be confident that the real battery
// level it is very close to 72.0 and can start tracking.
if (mInitialPowerMetrics == null && mCurrentBatteryLevel < mInitialBatteryLevel) {
mInitialBatteryLevel = mCurrentBatteryLevel;
mInitialPowerMetrics = readBatteryStatsData();
}
}
private void captureFinalPowerStatsData() {
if (mFinalPowerMetrics != null) {
return;
}
mFinalPowerMetrics = readBatteryStatsData();
mPowerMetricsDelta = new ArrayList<>();
List<PowerMetrics.Metric> initialPowerMetrics = mInitialPowerMetrics.getMetrics();
List<PowerMetrics.Metric> finalPowerMetrics = mFinalPowerMetrics.getMetrics();
for (PowerMetrics.Metric initialMetric : initialPowerMetrics) {
PowerMetrics.Metric finalMetric = null;
for (PowerMetrics.Metric metric : finalPowerMetrics) {
if (metric.title.equals(initialMetric.title)) {
finalMetric = metric;
break;
}
}
if (finalMetric != null) {
PowerMetrics.Metric delta = new PowerMetrics.Metric();
delta.metricType = initialMetric.metricType;
delta.metricKind = initialMetric.metricKind;
delta.title = initialMetric.title;
delta.total = finalMetric.total - initialMetric.total;
delta.value = finalMetric.value - initialMetric.value;
mPowerMetricsDelta.add(delta);
}
}
}
/**
* Returns false if sufficient data has been accumulated.
*/
public boolean checkpoint() {
long elapsedTime = SystemClock.uptimeMillis() - mStartTime;
if (elapsedTime >= mTimeoutMillis) {
Log.i(mTag, "Timeout reached " + TimeUtils.formatDuration(elapsedTime));
captureFinalPowerStatsData();
return false;
}
if (mInitialPowerMetrics == null) {
return true;
}
if (mInitialBatteryLevel - mCurrentBatteryLevel >= mBatteryDrainThresholdPct) {
Log.i(mTag,
"Battery drain reached " + (mInitialBatteryLevel - mCurrentBatteryLevel) + "%");
captureFinalPowerStatsData();
return false;
}
mIterations++;
return true;
}
public int getIterationCount() {
return mIterations;
}
public void dumpMetrics() {
dumpMetrics(new LoggingPrintStream() {
@Override
protected void log(String line) {
Log.i(mTag, line);
}
});
}
public void dumpMetrics(PrintStream out) {
List<PowerMetrics.Metric> initialPowerMetrics = mInitialPowerMetrics.getMetrics();
List<PowerMetrics.Metric> finalPowerMetrics = mFinalPowerMetrics.getMetrics();
out.println("== Power metrics at test start");
dumpPowerStatsData(out, initialPowerMetrics);
out.println("== Power metrics at test end");
dumpPowerStatsData(out, finalPowerMetrics);
out.println("== Power metrics delta");
dumpPowerStatsData(out, mPowerMetricsDelta);
}
protected void dumpPowerStatsData(PrintStream out, List<PowerMetrics.Metric> metrics) {
Locale locale = Locale.getDefault();
for (PowerMetrics.Metric metric : metrics) {
double proportion = metric.total != 0 ? metric.value * 100 / metric.total : 0;
switch (metric.metricKind) {
case POWER:
out.println(
String.format(locale, " %-30s %7.1f mAh %4.1f%%", metric.title,
metric.value, proportion));
break;
case DURATION:
out.println(
String.format(locale, " %-30s %,7d ms %4.1f%%", metric.title,
(long) metric.value, proportion));
break;
}
}
}
public void dumpMetricAsPercentageOfDrainedPower(String metricType) {
double minDrainedPower =
mFinalPowerMetrics.getMinDrainedPower() - mInitialPowerMetrics.getMinDrainedPower();
double maxDrainedPower =
mFinalPowerMetrics.getMaxDrainedPower() - mInitialPowerMetrics.getMaxDrainedPower();
PowerMetrics.Metric metric = getMetric(metricType);
double metricDelta = metric.value;
if (maxDrainedPower - minDrainedPower < 0.1f) {
Log.i(mTag, String.format(Locale.getDefault(),
"%s power consumed by the test: %.1f of %.1f mAh (%.1f%%)",
metric.title, metricDelta, maxDrainedPower,
metricDelta / maxDrainedPower * 100));
} else {
Log.i(mTag, String.format(Locale.getDefault(),
"%s power consumed by the test: %.1f of %.1f - %.1f mAh (%.1f%% - %.1f%%)",
metric.title, metricDelta, minDrainedPower, maxDrainedPower,
metricDelta / minDrainedPower * 100, metricDelta / maxDrainedPower * 100));
}
}
public PowerMetrics.Metric getMetric(String metricType) {
for (PowerMetrics.Metric metric : mPowerMetricsDelta) {
if (metric.metricType.equals(metricType)) {
return metric;
}
}
return null;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2020 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.frameworks.core.powerstatsloadtests;
import static org.junit.Assert.assertNotNull;
import android.app.Instrumentation;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
public class SystemServiceCallLoadTest {
private static final String TAG = "SystemServiceCallLoadTest";
private static final int TIMEOUT_MILLIS = 60 * 60 * 1000;
private static final float BATTERY_DRAIN_THRESHOLD_PCT = 2.99f;
@Rule
public PowerMetricsCollector mPowerMetricsCollector = new PowerMetricsCollector(TAG,
BATTERY_DRAIN_THRESHOLD_PCT, TIMEOUT_MILLIS);
private PackageManager mPackageManager;
@Before
public void setup() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
mPackageManager = instrumentation.getContext().getPackageManager();
}
@Test
public void test() {
while (mPowerMetricsCollector.checkpoint()) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("http://example.com/"), "text/plain");
intent.addCategory(Intent.CATEGORY_BROWSABLE);
ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0);
assertNotNull(resolveInfo);
}
mPowerMetricsCollector.dumpMetrics();
Log.i(TAG, "==");
Log.i(TAG, "Total system server calls made " + mPowerMetricsCollector.getIterationCount());
mPowerMetricsCollector.dumpMetricAsPercentageOfDrainedPower(
PowerMetrics.METRIC_SYSTEM_SERVICE_CPU_POWER);
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2020 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.frameworks.core.powerstatsloadtests;
import android.util.Log;
import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class WiFiLoadTest {
private static final String TAG = "WiFiLoadTest";
private static final String DOWNLOAD_TEST_URL =
"https://i.ytimg.com/vi/l5mE3Tpjejs/maxresdefault.jpg";
private static final int TIMEOUT_MILLIS = 60 * 60 * 1000;
private static final float BATTERY_DRAIN_THRESHOLD_PCT = 0.99f;
@Rule
public PowerMetricsCollector mPowerMetricsCollector = new PowerMetricsCollector(TAG,
BATTERY_DRAIN_THRESHOLD_PCT, TIMEOUT_MILLIS);
@Rule
public ConnectivitySetupRule mConnectivitySetupRule =
new ConnectivitySetupRule(/* WiFi enabled */true);
@Test
public void test() throws IOException {
long totalBytesRead = 0;
URL url = new URL(DOWNLOAD_TEST_URL);
byte[] buffer = new byte[131072]; // Large buffer to minimize CPU usage
while (mPowerMetricsCollector.checkpoint()) {
try (InputStream inputStream = url.openStream()) {
while (true) {
int count = inputStream.read(buffer);
if (count < 0) {
break;
}
totalBytesRead += count;
}
}
}
mPowerMetricsCollector.dumpMetrics();
Log.i(TAG, "==");
Log.i(TAG, "WiFi running time: " + (long) mPowerMetricsCollector.getMetric(
PowerMetrics.METRIC_WIFI_RUNNING_TIME).value);
Log.i(TAG, "Total bytes read over WiFi: " + totalBytesRead);
mPowerMetricsCollector.dumpMetricAsPercentageOfDrainedPower(
PowerMetrics.METRIC_WIFI_POWER);
}
}