diff options
11 files changed, 504 insertions, 3 deletions
diff --git a/tools/powermodel/src/com/android/powermodel/AppPower.java b/tools/powermodel/src/com/android/powermodel/AppPower.java new file mode 100644 index 000000000000..283982b8eda6 --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/AppPower.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2018 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.powermodel; + +import java.util.HashMap; +import java.util.Set; + +import com.google.common.collect.ImmutableMap; + +public class AppPower extends AppInfo { + private ImmutableMap<Component, ComponentPower> mComponents; + + private double mAppPowerMah; + + + private AppPower() { + } + + /** + * Returns the {@link ComponentPower} for the {@link Component} provided, + * or null if this AppPower does not have that component. + * @more + * If the component was in the power profile for this device, there + * will be a component for it, even if there was no power used + * by that component. In that case, the + * {@link ComponentPower.getUsage() ComponentPower.getUsage()} + * method will return 0. + */ + public ComponentPower getComponentPower(Component component) { + return mComponents.get(component); + } + + public Set<Component> getComponents() { + return mComponents.keySet(); + } + + /** + * Return the total power used by this app. + */ + public double getAppPowerMah() { + return mAppPowerMah; + } + + /** + * Builder class for {@link AppPower} + */ + public static class Builder extends AppInfo.Builder<AppPower> { + private HashMap<Component, ComponentPower> mComponents = new HashMap(); + + public Builder() { + } + + public AppPower build() { + final AppPower result = new AppPower(); + init(result); + result.mComponents = ImmutableMap.copyOf(mComponents); + + // Add up the components + double appPowerMah = 0; + for (final ComponentPower componentPower: mComponents.values()) { + appPowerMah += componentPower.powerMah; + } + result.mAppPowerMah = appPowerMah; + + return result; + } + + public void addComponentPower(Component component, ComponentPower componentPower) { + mComponents.put(component, componentPower); + } + } +} diff --git a/tools/powermodel/src/com/android/powermodel/ComponentActivity.java b/tools/powermodel/src/com/android/powermodel/ComponentActivity.java index e619a96687ab..c1e2662b7b5f 100644 --- a/tools/powermodel/src/com/android/powermodel/ComponentActivity.java +++ b/tools/powermodel/src/com/android/powermodel/ComponentActivity.java @@ -23,8 +23,20 @@ package com.android.powermodel; public class ComponentActivity { public AttributionKey attribution; - public ComponentActivity(AttributionKey attribution) { + protected ComponentActivity(AttributionKey attribution) { this.attribution = attribution; } + + // TODO: Can we refactor what goes into the activities so this function + // doesn't need the global state? + /** + * Apply the power profile for this component. Subclasses should implement this + * to do the per-component calculatinos. The default implementation returns null. + * If this method returns null, then there will be no power associated for this + * component, which, for example is true with some of the GLOBAL activities. + */ + public ComponentPower applyProfile(ActivityReport activityReport, PowerProfile profile) { + return null; + } } diff --git a/tools/powermodel/src/com/android/powermodel/ComponentPower.java b/tools/powermodel/src/com/android/powermodel/ComponentPower.java new file mode 100644 index 000000000000..b22ff8731d6f --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/ComponentPower.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 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.powermodel; + +/** + * The hardware component that uses power on a device. + * <p> + * This base class contains the total power used by each Component in an app. + * Subclasses may add more detail, which is a drill-down, but is not to be + * <i>added</i> to {@link #powerMah}. + */ +public abstract class ComponentPower<ACTIVITY extends ComponentActivity> { + /** + * The app associated with this ComponentPower. + */ + public AttributionKey attribution; + + /** + * The app activity that resulted in the power usage for this component. + */ + public ACTIVITY activity; + + /** + * The total power used by this component in this app. + */ + public double powerMah; +} diff --git a/tools/powermodel/src/com/android/powermodel/PowerReport.java b/tools/powermodel/src/com/android/powermodel/PowerReport.java new file mode 100644 index 000000000000..76ba67235b0a --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/PowerReport.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2018 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.powermodel; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.google.common.collect.ImmutableMap; + +/** + * PowerReport contains the summary of all power used on a device + * as reported by batterystats or statsd, based on the power profile. + */ +public class PowerReport { + private AppList<AppPower> mApps; + private double mTotalPowerMah; + + private PowerReport() { + } + + /** + * The total power used by this device for this PowerReport. + */ + public double getTotalPowerMah() { + return mTotalPowerMah; + } + + public List<AppPower> getAllApps() { + return mApps.getAllApps(); + } + + public List<AppPower> getRegularApps() { + return mApps.getRegularApps(); + } + + public List<AppPower> findApp(String pkg) { + return mApps.findApp(pkg); + } + + public AppPower findApp(SpecialApp specialApp) { + return mApps.findApp(specialApp); + } + + public static PowerReport createReport(PowerProfile profile, ActivityReport activityReport) { + final PowerReport.Builder powerReport = new PowerReport.Builder(); + for (final AppActivity appActivity: activityReport.getAllApps()) { + final AppPower.Builder appPower = new AppPower.Builder(); + appPower.setAttribution(appActivity.getAttribution()); + + for (final ImmutableMap.Entry<Component,ComponentActivity> entry: + appActivity.getComponentActivities().entrySet()) { + final ComponentPower componentPower = entry.getValue() + .applyProfile(activityReport, profile); + if (componentPower != null) { + appPower.addComponentPower(entry.getKey(), componentPower); + } + } + + powerReport.add(appPower); + } + return powerReport.build(); + } + + private static class Builder { + private AppList.Builder mApps = new AppList.Builder(); + + public Builder() { + } + + public PowerReport build() { + final PowerReport report = new PowerReport(); + + report.mApps = mApps.build(); + + for (AppPower app: report.mApps.getAllApps()) { + report.mTotalPowerMah += app.getAppPowerMah(); + } + + return report; + } + + public void add(AppPower.Builder app) { + mApps.put(app.getAttribution(), app); + } + } +} diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemAppActivity.java b/tools/powermodel/src/com/android/powermodel/component/ModemAppActivity.java index 53aa3c00aa40..cb70051f1ae6 100644 --- a/tools/powermodel/src/com/android/powermodel/component/ModemAppActivity.java +++ b/tools/powermodel/src/com/android/powermodel/component/ModemAppActivity.java @@ -21,6 +21,7 @@ import com.android.powermodel.AttributionKey; import com.android.powermodel.Component; import com.android.powermodel.ComponentActivity; import com.android.powermodel.PowerProfile; +import com.android.powermodel.util.Conversion; /** * Encapsulates the work done by the celluar modem on behalf of an app. @@ -42,5 +43,43 @@ public class ModemAppActivity extends ComponentActivity { * The number of packets sent by the app. */ public long txPacketCount; + + @Override + public ModemAppPower applyProfile(ActivityReport activityReport, PowerProfile profile) { + // Profile + final ModemProfile modemProfile = (ModemProfile)profile.getComponent(Component.MODEM); + if (modemProfile == null) { + // TODO: This is kind of a big problem... Should this throw instead? + return null; + } + + // Activity + final ModemGlobalActivity global + = (ModemGlobalActivity)activityReport.findGlobalComponent(Component.MODEM); + if (global == null) { + return null; + } + + final double averageModemPowerMa = getAverageModemPowerMa(modemProfile); + final long totalPacketCount = global.rxPacketCount + global.txPacketCount; + final long appPacketCount = this.rxPacketCount + this.txPacketCount; + + final ModemAppPower result = new ModemAppPower(); + result.attribution = this.attribution; + result.activity = this; + result.powerMah = Conversion.msToHr( + (totalPacketCount > 0 ? (appPacketCount / (double)totalPacketCount) : 0) + * global.totalActiveTimeMs + * averageModemPowerMa); + return result; + } + + static final double getAverageModemPowerMa(ModemProfile profile) { + double sumMa = profile.getRxMa(); + for (float powerAtTxLevelMa: profile.getTxMa()) { + sumMa += powerAtTxLevelMa; + } + return sumMa / (profile.getTxMa().length + 1); + } } diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemAppPower.java b/tools/powermodel/src/com/android/powermodel/component/ModemAppPower.java new file mode 100644 index 000000000000..f5531272d0b9 --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/component/ModemAppPower.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 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.powermodel.component; + +import com.android.powermodel.Component; +import com.android.powermodel.ComponentPower; + +public class ModemAppPower extends ComponentPower<ModemAppActivity> { +} + diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemGlobalActivity.java b/tools/powermodel/src/com/android/powermodel/component/ModemGlobalActivity.java index b9b39672f014..a53b53eede2b 100644 --- a/tools/powermodel/src/com/android/powermodel/component/ModemGlobalActivity.java +++ b/tools/powermodel/src/com/android/powermodel/component/ModemGlobalActivity.java @@ -19,6 +19,8 @@ package com.android.powermodel.component; import com.android.powermodel.ActivityReport; import com.android.powermodel.AttributionKey; import com.android.powermodel.ComponentActivity; +import com.android.powermodel.ComponentPower; +import com.android.powermodel.PowerProfile; /** * Encapsulates total work done by the modem for the device. diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemRemainderActivity.java b/tools/powermodel/src/com/android/powermodel/component/ModemRemainderActivity.java index 5694de301d2d..0e268c21d01d 100644 --- a/tools/powermodel/src/com/android/powermodel/component/ModemRemainderActivity.java +++ b/tools/powermodel/src/com/android/powermodel/component/ModemRemainderActivity.java @@ -20,13 +20,13 @@ import com.android.powermodel.ActivityReport; import com.android.powermodel.AttributionKey; import com.android.powermodel.Component; import com.android.powermodel.ComponentActivity; +import com.android.powermodel.PowerProfile; +import com.android.powermodel.util.Conversion; /** * Encapsulates the work done by the remaining */ public class ModemRemainderActivity extends ComponentActivity { - private static final double MS_PER_HR = 3600000.0; - /** * Construct a new ModemRemainderActivity. */ @@ -49,5 +49,39 @@ public class ModemRemainderActivity extends ComponentActivity { * than an app transmitting and receiving data. */ public long activeTimeMs; + + @Override + public ModemRemainderPower applyProfile(ActivityReport activityReport, PowerProfile profile) { + // Profile + final ModemProfile modemProfile = (ModemProfile)profile.getComponent(Component.MODEM); + if (modemProfile == null) { + return null; + } + + // Activity + final ModemRemainderPower result = new ModemRemainderPower(); + result.attribution = this.attribution; + result.activity = this; + + // strengthMah + // TODO: If the array lengths don't match... then? + result.strengthMah = new double[this.strengthTimeMs.length]; + for (int i=0; i<this.strengthTimeMs.length; i++) { + result.strengthMah[i] = Conversion.msToHr( + this.strengthTimeMs[i] * modemProfile.getTxMa()[i]); + result.powerMah += result.strengthMah[i]; + } + + // scanningMah + result.scanningMah = Conversion.msToHr(this.scanningTimeMs * modemProfile.getScanningMa()); + result.powerMah += result.scanningMah; + + // activeMah + result.activeMah = Conversion.msToHr( + this.activeTimeMs * ModemAppActivity.getAverageModemPowerMa(modemProfile)); + result.powerMah += result.activeMah; + + return result; + } } diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemRemainderPower.java b/tools/powermodel/src/com/android/powermodel/component/ModemRemainderPower.java new file mode 100644 index 000000000000..7f38cd342e2f --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/component/ModemRemainderPower.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 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.powermodel.component; + +import com.android.powermodel.Component; +import com.android.powermodel.ComponentPower; + +public class ModemRemainderPower extends ComponentPower<ModemRemainderActivity> { + + public double[] strengthMah; + + public double scanningMah; + + public double activeMah; +} + diff --git a/tools/powermodel/src/com/android/powermodel/util/Conversion.java b/tools/powermodel/src/com/android/powermodel/util/Conversion.java index 9a79a2d48a59..e556c251a1c9 100644 --- a/tools/powermodel/src/com/android/powermodel/util/Conversion.java +++ b/tools/powermodel/src/com/android/powermodel/util/Conversion.java @@ -35,6 +35,10 @@ public class Conversion { return result; } + public static double msToHr(double ms) { + return ms / 3600.0 / 1000.0; + } + /** * No public constructor. */ diff --git a/tools/powermodel/test/com/android/powermodel/PowerReportTest.java b/tools/powermodel/test/com/android/powermodel/PowerReportTest.java new file mode 100644 index 000000000000..1a61737a4b2f --- /dev/null +++ b/tools/powermodel/test/com/android/powermodel/PowerReportTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2018 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.powermodel; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import org.junit.Test; +import org.junit.Assert; + +import com.android.powermodel.component.ModemAppPower; +import com.android.powermodel.component.ModemRemainderPower; + +/** + * Tests {@link PowerReport}. + */ +public class PowerReportTest { + private static final double EPSILON = 0.001; + private static final double MS_PER_HR = 3600000.0; + + private static final double AVERAGE_MODEM_POWER = ((11+16+19+22+73+132) / 6.0); + private static final double GMAIL_MODEM_MAH = ((9925+5577) / (double)(97840+72941)) + * 5113727 * AVERAGE_MODEM_POWER * (1.0 / 3600 / 1000); + private static final double GMAIL_MAH + = GMAIL_MODEM_MAH; + + private static final double REMAINDER_MODEM_MAH + = (1.0 / 3600 / 1000) + * ((3066958 * 16) + (0 * 19) + (34678 * 22) + (1643364 * 73) + (7045084 * 132) + + (2443805 * 12) + + (4923676 * AVERAGE_MODEM_POWER)); + private static final double REMAINDER_MAH + = REMAINDER_MODEM_MAH; + + private static final double TOTAL_MAH + = GMAIL_MAH + + REMAINDER_MAH; + + private static InputStream loadPowerProfileStream() { + return PowerProfileTest.class.getResourceAsStream("/power_profile.xml"); + } + + private static InputStream loadCsvStream() { + return BatteryStatsReaderTest.class.getResourceAsStream("/bs.csv"); + } + + private static PowerReport loadPowerReport() throws Exception { + final PowerProfile profile = PowerProfile.parse(loadPowerProfileStream()); + final ActivityReport activity = BatteryStatsReader.parse(loadCsvStream()); + return PowerReport.createReport(profile, activity); + } + + @Test public void testModemApp() throws Exception { + final PowerReport report = loadPowerReport(); + + final List<AppPower> gmailList = report.findApp("com.google.android.gm"); + Assert.assertEquals(1, gmailList.size()); + final AppPower gmail = gmailList.get(0); + + final ModemAppPower modem = (ModemAppPower)gmail.getComponentPower(Component.MODEM); + Assert.assertNotNull(modem); + Assert.assertEquals(GMAIL_MODEM_MAH, modem.powerMah, EPSILON); + } + + @Test public void testModemRemainder() throws Exception { + final PowerReport report = loadPowerReport(); + + final AppPower remainder = report.findApp(SpecialApp.REMAINDER); + Assert.assertNotNull(remainder); + + final ModemRemainderPower modem + = (ModemRemainderPower)remainder.getComponentPower(Component.MODEM); + Assert.assertNotNull(modem); + + Assert.assertArrayEquals(new double[] { + 3066958 * 16.0 / MS_PER_HR, + 0 * 19.0 / MS_PER_HR, + 34678 * 22.0 / MS_PER_HR, + 1643364 * 73.0 / MS_PER_HR, + 7045084 * 132.0 / MS_PER_HR }, + modem.strengthMah, EPSILON); + Assert.assertEquals(2443805 * 12 / MS_PER_HR, modem.scanningMah, EPSILON); + Assert.assertEquals(4923676 * AVERAGE_MODEM_POWER / MS_PER_HR, modem.activeMah, EPSILON); + + Assert.assertEquals(REMAINDER_MODEM_MAH, modem.powerMah, EPSILON); + } + + @Test public void testAppTotal() throws Exception { + final PowerReport report = loadPowerReport(); + + final List<AppPower> gmailList = report.findApp("com.google.android.gm"); + Assert.assertEquals(1, gmailList.size()); + final AppPower gmail = gmailList.get(0); + + Assert.assertEquals(GMAIL_MAH, gmail.getAppPowerMah(), EPSILON); + } + + @Test public void testRemainderTotal() throws Exception { + final PowerReport report = loadPowerReport(); + + final AppPower remainder = report.findApp(SpecialApp.REMAINDER); + Assert.assertNotNull(remainder); + + Assert.assertEquals(REMAINDER_MAH, remainder.getAppPowerMah(), EPSILON); + } + + @Test public void testTotal() throws Exception { + final PowerReport report = loadPowerReport(); + + Assert.assertEquals(TOTAL_MAH, report.getTotalPowerMah(), EPSILON); + } +} + |