From 6d398ca594a45203aa2c611a6621e2c2e0ad4e56 Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Wed, 22 Jul 2020 18:15:35 -0700 Subject: [PATCH] Add a power load test Bug: 162611167 Test: mp :PowerStatsLoadTests && android shell am instrument -w -e class com.android.frameworks.core.powerstatsloadtests.WiFiLoadTest com.android.frameworks.core.powerstatsloadtests/androidx.test.runner.AndroidJUnitRunner Test: mp :PowerStatsLoadTests && android shell am instrument -w -e class com.android.frameworks.core.powerstatsloadtests.SystemServiceCallLoadTest com.android.frameworks.core.powerstatsloadtests/androidx.test.runner.AndroidJUnitRunner Change-Id: Id15e54bf07e558f1e90c788a5b095f8aa37ecbab --- .../powertests/PowerStatsLoadTests/Android.bp | 13 + .../PowerStatsLoadTests/AndroidManifest.xml | 36 +++ .../ConnectivitySetupRule.java | 159 ++++++++++ .../powerstatsloadtests/PowerMetrics.java | 278 +++++++++++++++++ .../PowerMetricsCollector.java | 281 ++++++++++++++++++ .../SystemServiceCallLoadTest.java | 69 +++++ .../powerstatsloadtests/WiFiLoadTest.java | 72 +++++ 7 files changed, 908 insertions(+) create mode 100644 core/tests/powertests/PowerStatsLoadTests/Android.bp create mode 100644 core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml create mode 100644 core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/ConnectivitySetupRule.java create mode 100644 core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetrics.java create mode 100644 core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java create mode 100644 core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/SystemServiceCallLoadTest.java create mode 100644 core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/WiFiLoadTest.java diff --git a/core/tests/powertests/PowerStatsLoadTests/Android.bp b/core/tests/powertests/PowerStatsLoadTests/Android.bp new file mode 100644 index 0000000000000..66c91adc6540b --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/Android.bp @@ -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", +} diff --git a/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml b/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml new file mode 100644 index 0000000000000..b1c2a639aff4c --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/ConnectivitySetupRule.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/ConnectivitySetupRule.java new file mode 100644 index 0000000000000..ca2942647f08a --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/ConnectivitySetupRule.java @@ -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); + } + } + } +} diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetrics.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetrics.java new file mode 100644 index 0000000000000..88cb719add60d --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetrics.java @@ -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 mMetrics = new ArrayList<>(); + + public PowerMetrics(BatteryStatsHelper batteryStatsHelper, int uid) { + mMinDrainedPower = batteryStatsHelper.getMinDrainedPower(); + mMaxDrainedPower = batteryStatsHelper.getMaxDrainedPower(); + + List 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 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); + } +} diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java new file mode 100644 index 0000000000000..0cdb404f6aaa0 --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java @@ -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 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 initialPowerMetrics = mInitialPowerMetrics.getMetrics(); + List 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 initialPowerMetrics = mInitialPowerMetrics.getMetrics(); + List 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 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; + } +} diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/SystemServiceCallLoadTest.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/SystemServiceCallLoadTest.java new file mode 100644 index 0000000000000..911ccba3ac78f --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/SystemServiceCallLoadTest.java @@ -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); + } +} diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/WiFiLoadTest.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/WiFiLoadTest.java new file mode 100644 index 0000000000000..90627192946df --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/WiFiLoadTest.java @@ -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); + } +}