Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 795bfbaa authored by Joe Onorato's avatar Joe Onorato
Browse files

Power model calculation based on batterystats data.

Similar to the ActivityReport, the PowerReport contains the power
usage for a device.  To do the calculations, each of the
ComponentActivity objects are called, giving them the whole activity
info (in case they need to apportion blame) and the PowerProfile.
From that, they compute the per-component power usage, which is
then summed up into the AppPower and PowerReport objects.

Test: atest frameworks/base/tools/powermodel --host
Change-Id: Ibc9ada6f7f4a667152fc4af388f04766125ca74c
parent 68cf7a9e
Loading
Loading
Loading
Loading
+86 −0
Original line number Original line Diff line number Diff line
/*
 * 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);
        }
    }
}
+13 −1
Original line number Original line Diff line number Diff line
@@ -23,8 +23,20 @@ package com.android.powermodel;
public class ComponentActivity {
public class ComponentActivity {
    public AttributionKey attribution;
    public AttributionKey attribution;


    public ComponentActivity(AttributionKey attribution) {
    protected ComponentActivity(AttributionKey attribution) {
        this.attribution = 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;
    }
}
}
+41 −0
Original line number Original line Diff line number Diff line
/*
 * 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;
}
+101 −0
Original line number Original line Diff line number Diff line
/*
 * 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);
        }
    }
}
+39 −0
Original line number Original line Diff line number Diff line
@@ -21,6 +21,7 @@ import com.android.powermodel.AttributionKey;
import com.android.powermodel.Component;
import com.android.powermodel.Component;
import com.android.powermodel.ComponentActivity;
import com.android.powermodel.ComponentActivity;
import com.android.powermodel.PowerProfile;
import com.android.powermodel.PowerProfile;
import com.android.powermodel.util.Conversion;


/**
/**
 * Encapsulates the work done by the celluar modem on behalf of an app.
 * 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.
     * The number of packets sent by the app.
     */
     */
    public long txPacketCount;
    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);
    }
}
}
Loading