Merge changes Ibc9ada6f,I2c5fce16
* changes:
Power model calculation based on batterystats data.
Parse the raw batterystats into an ActivityReport object.
diff --git a/tools/powermodel/src/com/android/powermodel/ActivityReport.java b/tools/powermodel/src/com/android/powermodel/ActivityReport.java
new file mode 100644
index 0000000..4a8f633
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/ActivityReport.java
@@ -0,0 +1,92 @@
+/*
+ * 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.Collection;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * ActivityReport contains the summary of the activity that consumes power
+ * as reported by batterystats or statsd.
+ */
+public class ActivityReport {
+ private AppList<AppActivity> mApps;
+
+ public ImmutableList<AppActivity> getAllApps() {
+ return mApps.getAllApps();
+ }
+
+ public ImmutableList<AppActivity> getRegularApps() {
+ return mApps.getRegularApps();
+ }
+
+ public List<AppActivity> findApp(String pkg) {
+ return mApps.findApp(pkg);
+ }
+
+ public AppActivity findApp(SpecialApp specialApp) {
+ return mApps.findApp(specialApp);
+ }
+
+ /**
+ * Find a component in the GLOBAL app.
+ * <p>
+ * Returns null if either the global app doesn't exist (bad data?) or the component
+ * doesn't exist in the global app.
+ */
+ public ComponentActivity findGlobalComponent(Component component) {
+ final AppActivity global = mApps.findApp(SpecialApp.GLOBAL);
+ if (global == null) {
+ return null;
+ }
+ return global.getComponentActivity(component);
+ }
+
+ public static class Builder {
+ private AppList.Builder<AppActivity,AppActivity.Builder> mApps = new AppList.Builder();
+
+ public Builder() {
+ }
+
+ public ActivityReport build() {
+ final ActivityReport result = new ActivityReport();
+ result.mApps = mApps.build();
+ return result;
+ }
+
+ public void addActivity(Component component, Collection<ComponentActivity> activities) {
+ for (final ComponentActivity activity: activities) {
+ addActivity(component, activity);
+ }
+ }
+
+ public void addActivity(Component component, ComponentActivity activity) {
+ AppActivity.Builder app = mApps.get(activity.attribution);
+ if (app == null) {
+ app = new AppActivity.Builder();
+ app.setAttribution(activity.attribution);
+ mApps.put(activity.attribution, app);
+ }
+ app.addComponentActivity(component, activity);
+ }
+ }
+}
diff --git a/tools/powermodel/src/com/android/powermodel/AppActivity.java b/tools/powermodel/src/com/android/powermodel/AppActivity.java
new file mode 100644
index 0000000..b87426c
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/AppActivity.java
@@ -0,0 +1,80 @@
+/*
+ * 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 com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class AppActivity extends AppInfo {
+
+ private ImmutableMap<Component, ComponentActivity> mComponents;
+ // TODO: power rails
+ // private ImmutableMap<Component, PowerRailActivity> mRails;
+
+ private AppActivity() {
+ }
+
+ /**
+ * Returns the {@link ComponentActivity} for the {@link Component} provided,
+ * or null if this AppActivity does not have that component.
+ * @more
+ * If there is no ComponentActivity for a particular Component, then
+ * there was no usage associated with that app for the app in question.
+ */
+ public ComponentActivity getComponentActivity(Component component) {
+ return mComponents.get(component);
+ }
+
+ public ImmutableSet<Component> getComponents() {
+ return mComponents.keySet();
+ }
+
+ public ImmutableMap<Component,ComponentActivity> getComponentActivities() {
+ return mComponents;
+ }
+
+ // TODO: power rails
+ // public ComponentActivity getPowerRail(Component component) {
+ // return mComponents.get(component);
+ // }
+ //
+ // public Set<Component> getPowerRails() {
+ // return mComponents.keySet();
+ // }
+
+ public static class Builder extends AppInfo.Builder<AppActivity> {
+ private HashMap<Component, ComponentActivity> mComponents = new HashMap();
+ // TODO power rails.
+
+ public Builder() {
+ }
+
+ public AppActivity build() {
+ final AppActivity result = new AppActivity();
+ init(result);
+ result.mComponents = ImmutableMap.copyOf(mComponents);
+ return result;
+ }
+
+ public void addComponentActivity(Component component, ComponentActivity activity) {
+ mComponents.put(component, activity);
+ }
+ }
+}
diff --git a/tools/powermodel/src/com/android/powermodel/AppInfo.java b/tools/powermodel/src/com/android/powermodel/AppInfo.java
new file mode 100644
index 0000000..208339e
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/AppInfo.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+class AppInfo {
+ private AttributionKey mAttribution;
+
+ protected AppInfo() {
+ }
+
+ public boolean hasPackage(String pkg) {
+ return mAttribution.hasPackage(pkg);
+ }
+
+ public AttributionKey getAttribution() {
+ return mAttribution;
+ }
+
+ abstract static class Builder<APP extends AppInfo> {
+ private AttributionKey mAttribution;
+
+ public Builder() {
+ }
+
+ public abstract APP build();
+
+ protected void init(AppInfo app) {
+ if (mAttribution == null) {
+ throw new RuntimeException("setAttribution(AttributionKey attribution) not called");
+ }
+ app.mAttribution = mAttribution;
+ }
+
+ public void setAttribution(AttributionKey attribution) {
+ mAttribution = attribution;
+ }
+
+ public AttributionKey getAttribution() {
+ return mAttribution;
+ }
+ }
+}
diff --git a/tools/powermodel/src/com/android/powermodel/AppList.java b/tools/powermodel/src/com/android/powermodel/AppList.java
new file mode 100644
index 0000000..19572fc
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/AppList.java
@@ -0,0 +1,91 @@
+/*
+ * 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.HashMap;
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+class AppList<APP extends AppInfo> {
+ private ImmutableList<APP> mAllApps;
+ private ImmutableList<APP> mRegularApps;
+ private ImmutableMap<SpecialApp,APP> mSpecialApps;
+
+ private AppList() {
+ }
+
+ public ImmutableList<APP> getAllApps() {
+ return mAllApps;
+ }
+
+ public ImmutableList<APP> getRegularApps() {
+ return mRegularApps;
+ }
+
+ public List<APP> findApp(String pkg) {
+ List<APP> result = new ArrayList();
+ for (APP app: mRegularApps) {
+ if (app.hasPackage(pkg)) {
+ result.add(app);
+ }
+ }
+ return result;
+ }
+
+ public APP findApp(SpecialApp specialApp) {
+ return mSpecialApps.get(specialApp);
+ }
+
+ public static class Builder<APP extends AppInfo, BUILDER extends AppInfo.Builder<APP>> {
+ private final HashMap<AttributionKey,BUILDER> mApps = new HashMap();
+
+ public Builder() {
+ }
+
+ public AppList<APP> build() {
+ final AppList<APP> result = new AppList();
+ final ArrayList<APP> allApps = new ArrayList();
+ final ArrayList<APP> regularApps = new ArrayList();
+ final HashMap<SpecialApp,APP> specialApps = new HashMap();
+ for (AppInfo.Builder<APP> app: mApps.values()) {
+ final AttributionKey attribution = app.getAttribution();
+ final APP appActivity = app.build();
+ allApps.add(appActivity);
+ if (attribution.isSpecialApp()) {
+ specialApps.put(attribution.getSpecialApp(), appActivity);
+ } else {
+ regularApps.add(appActivity);
+ }
+ }
+ result.mAllApps = ImmutableList.copyOf(allApps);
+ result.mRegularApps = ImmutableList.copyOf(regularApps);
+ result.mSpecialApps = ImmutableMap.copyOf(specialApps);
+ return result;
+ }
+
+ public BUILDER get(AttributionKey attribution) {
+ return mApps.get(attribution);
+ }
+
+ public BUILDER put(AttributionKey attribution, BUILDER app) {
+ return mApps.put(attribution, app);
+ }
+ }
+}
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 0000000..283982b
--- /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/BatteryStatsReader.java b/tools/powermodel/src/com/android/powermodel/BatteryStatsReader.java
new file mode 100644
index 0000000..595c661
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/BatteryStatsReader.java
@@ -0,0 +1,74 @@
+/*
+ * 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.InputStream;
+import java.io.IOException;
+import com.android.powermodel.component.ModemBatteryStatsReader;
+
+public class BatteryStatsReader {
+ /**
+ * Construct a reader.
+ */
+ public BatteryStatsReader() {
+ }
+
+ /**
+ * Parse a powermodel.xml file and return a PowerProfile object.
+ *
+ * @param stream An InputStream containing the batterystats output.
+ *
+ * @throws ParseException Thrown when the xml file can not be parsed.
+ * @throws IOException When there is a problem reading the stream.
+ */
+ public static ActivityReport parse(InputStream stream) throws ParseException, IOException {
+ final Parser parser = new Parser(stream);
+ return parser.parse();
+ }
+
+ /**
+ * Implements the reading and power model logic.
+ */
+ private static class Parser {
+ final InputStream mStream;
+ final ActivityReport mResult;
+ RawBatteryStats mBs;
+
+ /**
+ * Constructor to capture the parameters to read.
+ */
+ Parser(InputStream stream) {
+ mStream = stream;
+ mResult = new ActivityReport();
+ }
+
+ /**
+ * Read the stream, parse it, and apply the power model.
+ * Do not call this more than once.
+ */
+ ActivityReport parse() throws ParseException, IOException {
+ mBs = RawBatteryStats.parse(mStream);
+
+ final ActivityReport.Builder report = new ActivityReport.Builder();
+
+ report.addActivity(Component.MODEM, ModemBatteryStatsReader.createActivities(mBs));
+
+ return report.build();
+ }
+ }
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/ComponentActivity.java b/tools/powermodel/src/com/android/powermodel/ComponentActivity.java
new file mode 100644
index 0000000..c1e2662
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/ComponentActivity.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+
+/**
+ * Encapsulates the work done by an app (including synthetic apps) that costs power.
+ */
+public class ComponentActivity {
+ public 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 0000000..b22ff87
--- /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 0000000..76ba672
--- /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/RawBatteryStats.java b/tools/powermodel/src/com/android/powermodel/RawBatteryStats.java
index d0c1790..76c0482 100644
--- a/tools/powermodel/src/com/android/powermodel/RawBatteryStats.java
+++ b/tools/powermodel/src/com/android/powermodel/RawBatteryStats.java
@@ -168,6 +168,80 @@
public String lineType;
}
+ @Line(tag="bt", scope=Scope.SYSTEM, count=Count.SINGLE)
+ public static class Battery extends Record {
+ // If which != STATS_SINCE_CHARGED, the csv will be "N/A" and we will get
+ // a parsing warning. Nobody uses anything other than STATS_SINCE_CHARGED.
+ @Field(index=0)
+ public int startCount;
+
+ @Field(index=1)
+ public long whichBatteryRealtimeMs;
+
+ @Field(index=2)
+ public long whichBatteryUptimeMs;
+
+ @Field(index=3)
+ public long totalRealtimeMs;
+
+ @Field(index=4)
+ public long totalUptimeMs;
+
+ @Field(index=5)
+ public long getStartClockTimeMs;
+
+ @Field(index=6)
+ public long whichBatteryScreenOffRealtimeMs;
+
+ @Field(index=7)
+ public long whichBatteryScreenOffUptimeMs;
+
+ @Field(index=8)
+ public long estimatedBatteryCapacityMah;
+
+ @Field(index=9)
+ public long minLearnedBatteryCapacityMah;
+
+ @Field(index=10)
+ public long maxLearnedBatteryCapacityMah;
+
+ @Field(index=11)
+ public long screenDozeTimeMs;
+ }
+
+ @Line(tag="gn", scope=Scope.SYSTEM, count=Count.SINGLE)
+ public static class GlobalNetwork extends Record {
+ @Field(index=0)
+ public long mobileRxTotalBytes;
+
+ @Field(index=1)
+ public long mobileTxTotalBytes;
+
+ @Field(index=2)
+ public long wifiRxTotalBytes;
+
+ @Field(index=3)
+ public long wifiTxTotalBytes;
+
+ @Field(index=4)
+ public long mobileRxTotalPackets;
+
+ @Field(index=5)
+ public long mobileTxTotalPackets;
+
+ @Field(index=6)
+ public long wifiRxTotalPackets;
+
+ @Field(index=7)
+ public long wifiTxTotalPackets;
+
+ @Field(index=8)
+ public long btRxTotalBytes;
+
+ @Field(index=9)
+ public long btTxTotalBytes;
+ }
+
@Line(tag="gmcd", scope=Scope.SYSTEM, count=Count.SINGLE)
public static class GlobalModemController extends Record {
@Field(index=0)
@@ -183,6 +257,154 @@
public long[] txTimeMs;
}
+ @Line(tag="m", scope=Scope.SYSTEM, count=Count.SINGLE)
+ public static class Misc extends Record {
+ @Field(index=0)
+ public long screenOnTimeMs;
+
+ @Field(index=1)
+ public long phoneOnTimeMs;
+
+ @Field(index=2)
+ public long fullWakeLockTimeTotalMs;
+
+ @Field(index=3)
+ public long partialWakeLockTimeTotalMs;
+
+ @Field(index=4)
+ public long mobileRadioActiveTimeMs;
+
+ @Field(index=5)
+ public long mobileRadioActiveAdjustedTimeMs;
+
+ @Field(index=6)
+ public long interactiveTimeMs;
+
+ @Field(index=7)
+ public long powerSaveModeEnabledTimeMs;
+
+ @Field(index=8)
+ public int connectivityChangeCount;
+
+ @Field(index=9)
+ public long deepDeviceIdleModeTimeMs;
+
+ @Field(index=10)
+ public long deepDeviceIdleModeCount;
+
+ @Field(index=11)
+ public long deepDeviceIdlingTimeMs;
+
+ @Field(index=12)
+ public long deepDeviceIdlingCount;
+
+ @Field(index=13)
+ public long mobileRadioActiveCount;
+
+ @Field(index=14)
+ public long mobileRadioActiveUnknownTimeMs;
+
+ @Field(index=15)
+ public long lightDeviceIdleModeTimeMs;
+
+ @Field(index=16)
+ public long lightDeviceIdleModeCount;
+
+ @Field(index=17)
+ public long lightDeviceIdlingTimeMs;
+
+ @Field(index=18)
+ public long lightDeviceIdlingCount;
+
+ @Field(index=19)
+ public long lightLongestDeviceIdleModeTimeMs;
+
+ @Field(index=20)
+ public long deepLongestDeviceIdleModeTimeMs;
+ }
+
+ @Line(tag="nt", scope=Scope.UID, count=Count.SINGLE)
+ public static class Network extends Record {
+ @Field(index=0)
+ public long mobileRxBytes;
+
+ @Field(index=1)
+ public long mobileTxBytes;
+
+ @Field(index=2)
+ public long wifiRxBytes;
+
+ @Field(index=3)
+ public long wifiTxBytes;
+
+ @Field(index=4)
+ public long mobileRxPackets;
+
+ @Field(index=5)
+ public long mobileTxPackets;
+
+ @Field(index=6)
+ public long wifiRxPackets;
+
+ @Field(index=7)
+ public long wifiTxPackets;
+
+ // This is microseconds, because... batterystats.
+ @Field(index=8)
+ public long mobileRadioActiveTimeUs;
+
+ @Field(index=9)
+ public long mobileRadioActiveCount;
+
+ @Field(index=10)
+ public long btRxBytes;
+
+ @Field(index=11)
+ public long btTxBytes;
+
+ @Field(index=12)
+ public long mobileWakeupCount;
+
+ @Field(index=13)
+ public long wifiWakeupCount;
+
+ @Field(index=14)
+ public long mobileBgRxBytes;
+
+ @Field(index=15)
+ public long mobileBgTxBytes;
+
+ @Field(index=16)
+ public long wifiBgRxBytes;
+
+ @Field(index=17)
+ public long wifiBgTxBytes;
+
+ @Field(index=18)
+ public long mobileBgRxPackets;
+
+ @Field(index=19)
+ public long mobileBgTxPackets;
+
+ @Field(index=20)
+ public long wifiBgRxPackets;
+
+ @Field(index=21)
+ public long wifiBgTxPackets;
+ }
+
+ @Line(tag="sgt", scope=Scope.SYSTEM, count=Count.SINGLE)
+ public static class SignalStrengthTime extends Record {
+ @Field(index=0)
+ public long[] phoneSignalStrengthTimeMs;
+ }
+
+ @Line(tag="sst", scope=Scope.SYSTEM, count=Count.SINGLE)
+ public static class SignalScanningTime extends Record {
+ @Field(index=0)
+ public long phoneSignalScanningTimeMs;
+ }
+
@Line(tag="uid", scope=Scope.UID, count=Count.MULTIPLE)
public static class Uid extends Record {
@Field(index=0)
diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemAppActivity.java b/tools/powermodel/src/com/android/powermodel/component/ModemAppActivity.java
new file mode 100644
index 0000000..cb70051
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/ModemAppActivity.java
@@ -0,0 +1,85 @@
+/*
+ * 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.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 celluar modem on behalf of an app.
+ */
+public class ModemAppActivity extends ComponentActivity {
+ /**
+ * Construct a new ModemAppActivity.
+ */
+ public ModemAppActivity(AttributionKey attribution) {
+ super(attribution);
+ }
+
+ /**
+ * The number of packets received by the app.
+ */
+ public long rxPacketCount;
+
+ /**
+ * 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 0000000..f553127
--- /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/ModemBatteryStatsReader.java b/tools/powermodel/src/com/android/powermodel/component/ModemBatteryStatsReader.java
new file mode 100644
index 0000000..6dbfbc2
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/ModemBatteryStatsReader.java
@@ -0,0 +1,110 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+import com.android.powermodel.AttributionKey;
+import com.android.powermodel.ComponentActivity;
+import com.android.powermodel.RawBatteryStats;
+import com.android.powermodel.SpecialApp;
+
+public class ModemBatteryStatsReader {
+ private ModemBatteryStatsReader() {
+ }
+
+ public static List<ComponentActivity> createActivities(RawBatteryStats bs) {
+ final List<ComponentActivity> result = new ArrayList<ComponentActivity>();
+
+ // The whole system
+ createGlobal(result, bs);
+
+ // The apps
+ createApps(result, bs);
+
+ // The synthetic "cell" app.
+ createRemainder(result, bs);
+
+ return result;
+ }
+
+ private static void createGlobal(List<ComponentActivity> result, RawBatteryStats bs) {
+ final ModemGlobalActivity global
+ = new ModemGlobalActivity(new AttributionKey(SpecialApp.GLOBAL));
+
+ final RawBatteryStats.GlobalNetwork gn = bs.getSingle(RawBatteryStats.GlobalNetwork.class);
+ final RawBatteryStats.Misc misc = bs.getSingle(RawBatteryStats.Misc.class);
+
+ // Null here just means no network activity.
+ if (gn != null && misc != null) {
+ global.rxPacketCount = gn.mobileRxTotalPackets;
+ global.txPacketCount = gn.mobileTxTotalPackets;
+
+ global.totalActiveTimeMs = misc.mobileRadioActiveTimeMs;
+ }
+
+ result.add(global);
+ }
+
+ private static void createApps(List<ComponentActivity> result, RawBatteryStats bs) {
+ for (AttributionKey key: bs.getApps()) {
+ final int uid = key.getUid();
+ final RawBatteryStats.Network network
+ = bs.getSingle(RawBatteryStats.Network.class, uid);
+
+ // Null here just means no network activity.
+ if (network != null) {
+ final ModemAppActivity app = new ModemAppActivity(key);
+
+ app.rxPacketCount = network.mobileRxPackets;
+ app.txPacketCount = network.mobileTxPackets;
+
+ result.add(app);
+ }
+ }
+ }
+
+ private static void createRemainder(List<ComponentActivity> result, RawBatteryStats bs) {
+ final RawBatteryStats.SignalStrengthTime strength
+ = bs.getSingle(RawBatteryStats.SignalStrengthTime.class);
+ final RawBatteryStats.SignalScanningTime scanning
+ = bs.getSingle(RawBatteryStats.SignalScanningTime.class);
+ final RawBatteryStats.Misc misc = bs.getSingle(RawBatteryStats.Misc.class);
+
+ if (strength != null && scanning != null && misc != null) {
+ final ModemRemainderActivity remainder
+ = new ModemRemainderActivity(new AttributionKey(SpecialApp.REMAINDER));
+
+ // Signal strength buckets
+ remainder.strengthTimeMs = strength.phoneSignalStrengthTimeMs;
+
+ // Time spent scanning
+ remainder.scanningTimeMs = scanning.phoneSignalScanningTimeMs;
+
+ // Unaccounted for active time
+ final long totalActiveTimeMs = misc.mobileRadioActiveTimeMs;
+ long appActiveTimeMs = 0;
+ for (RawBatteryStats.Network nw: bs.getMultiple(RawBatteryStats.Network.class)) {
+ appActiveTimeMs += nw.mobileRadioActiveTimeUs / 1000;
+ }
+ remainder.activeTimeMs = totalActiveTimeMs - appActiveTimeMs;
+
+ result.add(remainder);
+ }
+ }
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemGlobalActivity.java b/tools/powermodel/src/com/android/powermodel/component/ModemGlobalActivity.java
new file mode 100644
index 0000000..a53b53e
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/ModemGlobalActivity.java
@@ -0,0 +1,51 @@
+/*
+ * 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.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.
+ */
+public class ModemGlobalActivity extends ComponentActivity {
+ /**
+ * Construct a new ModemGlobalActivity.
+ */
+ public ModemGlobalActivity(AttributionKey attribution) {
+ super(attribution);
+ }
+
+ /**
+ * Returns the total number of packets received in the whole device.
+ */
+ public long rxPacketCount;
+
+ /**
+ * Returns the total number of packets sent in the whole device.
+ */
+ public long txPacketCount;
+
+ /**
+ * Returns the total time the radio was active in the whole device.
+ */
+ public long totalActiveTimeMs;
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemRemainderActivity.java b/tools/powermodel/src/com/android/powermodel/component/ModemRemainderActivity.java
new file mode 100644
index 0000000..0e268c2
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/ModemRemainderActivity.java
@@ -0,0 +1,87 @@
+/*
+ * 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.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 {
+ /**
+ * Construct a new ModemRemainderActivity.
+ */
+ public ModemRemainderActivity(AttributionKey attribution) {
+ super(attribution);
+ }
+
+ /**
+ * Number of milliseconds spent at each of the signal strengths.
+ */
+ public long[] strengthTimeMs;
+
+ /**
+ * Number of milliseconds spent scanning for a network.
+ */
+ public long scanningTimeMs;
+
+ /**
+ * Number of milliseconds that the radio is active for reasons other
+ * 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 0000000..7f38cd3
--- /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 9a79a2d..e556c25 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 @@
return result;
}
+ public static double msToHr(double ms) {
+ return ms / 3600.0 / 1000.0;
+ }
+
/**
* No public constructor.
*/
diff --git a/tools/powermodel/test-resource/bs.csv b/tools/powermodel/test-resource/bs.csv
new file mode 100644
index 0000000..6e84120
--- /dev/null
+++ b/tools/powermodel/test-resource/bs.csv
@@ -0,0 +1,7 @@
+9,0,i,vers,32,177,PPR1.180326.002,PQ1A.181105.015
+9,0,i,uid,10139,com.google.android.gm
+9,0,l,gn,108060756,17293456,4896592,3290614,97840,72941,6903,8107,390,105
+9,0,l,m,2590630,0,384554,3943868,5113727,265,2565483,0,16,0,0,0,0,192,25331,3472068,17,3543323,14,614050,0
+9,10139,l,nt,13688501,534571,13842,7792,9925,5577,30,67,190051799,27,0,0,5,3,126020,42343,13842,7792,207,167,30,67
+9,0,l,sgt,3066958,0,34678,1643364,7045084
+9,0,l,sst,2443805
diff --git a/tools/powermodel/test/com/android/powermodel/BatteryStatsReaderTest.java b/tools/powermodel/test/com/android/powermodel/BatteryStatsReaderTest.java
new file mode 100644
index 0000000..e7b2c37
--- /dev/null
+++ b/tools/powermodel/test/com/android/powermodel/BatteryStatsReaderTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.ModemAppActivity;
+import com.android.powermodel.component.ModemGlobalActivity;
+import com.android.powermodel.component.ModemRemainderActivity;
+
+/**
+ * Tests {@link BatteryStatsReader}.
+ */
+public class BatteryStatsReaderTest {
+ private static InputStream loadCsvStream() {
+ return BatteryStatsReaderTest.class.getResourceAsStream("/bs.csv");
+ }
+
+ @Test public void testModemGlobal() throws Exception {
+ final ActivityReport report = BatteryStatsReader.parse(loadCsvStream());
+
+ final AppActivity global = report.findApp(SpecialApp.GLOBAL);
+ Assert.assertNotNull(global);
+
+ final ModemGlobalActivity modem
+ = (ModemGlobalActivity)global.getComponentActivity(Component.MODEM);
+ Assert.assertNotNull(modem);
+ Assert.assertEquals(97840, modem.rxPacketCount);
+ Assert.assertEquals(72941, modem.txPacketCount);
+ Assert.assertEquals(5113727, modem.totalActiveTimeMs);
+ }
+
+ @Test public void testModemApp() throws Exception {
+ final ActivityReport report = BatteryStatsReader.parse(loadCsvStream());
+
+ final List<AppActivity> gmailList = report.findApp("com.google.android.gm");
+ Assert.assertEquals(1, gmailList.size());
+ final AppActivity gmail = gmailList.get(0);
+
+ final ModemAppActivity modem
+ = (ModemAppActivity)gmail.getComponentActivity(Component.MODEM);
+ Assert.assertNotNull(modem);
+ Assert.assertEquals(9925, modem.rxPacketCount);
+ Assert.assertEquals(5577, modem.txPacketCount);
+ }
+
+ @Test public void testModemRemainder() throws Exception {
+ final ActivityReport report = BatteryStatsReader.parse(loadCsvStream());
+
+ final AppActivity remainder = report.findApp(SpecialApp.REMAINDER);
+ Assert.assertNotNull(remainder);
+
+ final ModemRemainderActivity modem
+ = (ModemRemainderActivity)remainder.getComponentActivity(Component.MODEM);
+ Assert.assertNotNull(modem);
+ Assert.assertArrayEquals(new long[] { 3066958, 0, 34678, 1643364, 7045084 },
+ modem.strengthTimeMs);
+ Assert.assertEquals(2443805, modem.scanningTimeMs);
+ Assert.assertEquals(4923676, modem.activeTimeMs);
+ }
+}
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 0000000..1a61737
--- /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);
+ }
+}
+