Android 7.0 耗电详情-应用消耗CPU电量统计原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/su749520/article/details/83385987

1. 原理

应用CPU耗电计算 = APP运行中的每个CPU消耗时间和电量的总和,具体算法如下

  1. 获取每个CPU频率运行时间接口-BatteryStatsImpl.getTimeAtCpuSpeed
  2. 获取每个CPU频率消耗的电量-Profile.getAveragePowerForCpu

其中一些应用没有电量消耗问题或者电量不准确,重点看下 time_in_state 节点有没有生成和power_profile.xml是否被正确配置

  • 源码
  • frameworks\base\core\java\com\android\internal\os\CpuPowerCalculator.java
    @Override
    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                             long rawUptimeUs, int statsType) {
        app.cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;

        // Aggregate total time spent on each cluster.
        long totalTime = 0;
        // 获取 CPU 群簇数量,即 cpu.clusters.cores 的size
        final int numClusters = mProfile.getNumCpuClusters();
        // 遍历每个 CPU 群簇数量 cpu.clusters.cores 
        for (int cluster = 0; cluster < numClusters; cluster++) {
            // 获取每个 CPU 群簇的CPU档位size,例如 cpu.speeds.cluster0 的档位数量
            final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
            for (int speed = 0; speed < speedsForCluster; speed++) {
                // 累增每个CPU档位的消耗时间
                totalTime += u.getTimeAtCpuSpeed(cluster, speed, statsType);
            }
        }
        totalTime = Math.max(totalTime, 1);

        double cpuPowerMaMs = 0;
        // 遍历每个 CPU 群簇数量 cpu.clusters.cores 
        for (int cluster = 0; cluster < numClusters; cluster++) {
            // 获取每个 CPU 群簇的CPU档位size,例如 cpu.speeds.cluster0 的档位数量
            final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
            for (int speed = 0; speed < speedsForCluster; speed++) {
                // ratio = 每个档位的CPU消耗时间 / 总运行时间
                final double ratio = (double) u.getTimeAtCpuSpeed(cluster, speed, statsType) /
                        totalTime;
                
                // cpuSpeedStepPower 计算每个CPU频率档位消耗的电流
                final double cpuSpeedStepPower = ratio * app.cpuTimeMs *
                        mProfile.getAveragePowerForCpu(cluster, speed);
                if (DEBUG && ratio != 0) {
                    Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
                            + speed + " ratio=" + BatteryStatsHelper.makemAh(ratio) + " power="
                            + BatteryStatsHelper.makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
                }
                
                // 应用CPU消耗总电流等于应用在每个CPU单位消耗的电量之和
                cpuPowerMaMs += cpuSpeedStepPower;
            }
        }
        
        
        app.cpuPowerMah = cpuPowerMaMs / (60 * 60 * 1000);

        if (DEBUG && (app.cpuTimeMs != 0 || app.cpuPowerMah != 0)) {
            Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + app.cpuTimeMs + " ms power="
                    + BatteryStatsHelper.makemAh(app.cpuPowerMah));
        }

2. 获取每个CPU频率运行时间接口 BatteryStatsImpl.getTimeAtCpuSpeed

  • 源码
  • frameworks\base\core\java\com\android\internal\os\BatteryStatsImpl.java

这里主要是 LongSamplingCounter 进行CPU时间计算,这里主要是使用 time_in_state 的节点数据

        LongSamplingCounter mCpuClusterSpeed;

        @Override
        public long getTimeAtCpuSpeed(int cluster, int step, int which) {
            if (mCpuClusterSpeed != null) {
                if (cluster >= 0 && cluster < mCpuClusterSpeed.length) {
                    final LongSamplingCounter[] cpuSpeeds = mCpuClusterSpeed[cluster];
                    if (cpuSpeeds != null) {
                        if (step >= 0 && step < cpuSpeeds.length) {
                            final LongSamplingCounter c = cpuSpeeds[step];
                            if (c != null) {
                                return c.getCountLocked(which);
                            }
                        }
                    }
                }
            }
            return 0;
        }

2.1 CPU时间来源 time_in_state

统计的对象是 time_in_state 节点的数据

adb shell "cat /sys/devices/system/cpu/cpu5/cpufreq/stats/time_in_state"
1500000 20000
1429000 495
1367000 549
1314000 528
1261000 578
1208000 12754
1155000 92
1102000 54
1050000 642
948000 186
846000 261
745000 142
643000 168
542000 182
460000 171
400000 31079
  • 源码
  • frameworks\base\core\java\com\android\internal\os\KernelCpuSpeedReader
/**
 * Reads CPU time of a specific core spent at various frequencies and provides a delta from the
 * last call to {@link #readDelta}. Each line in the proc file has the format:
 *
 * freq time
 *
 * where time is measured in jiffies.
 */
public class KernelCpuSpeedReader {
    private static final String TAG = "KernelCpuSpeedReader";

    private final String mProcFile;
    private final long[] mLastSpeedTimes;
    private final long[] mDeltaSpeedTimes;

    // How long a CPU jiffy is in milliseconds.
    private final long mJiffyMillis;

    /**
     * @param cpuNumber The cpu (cpu0, cpu1, etc) whose state to read.
     */
    public KernelCpuSpeedReader(int cpuNumber, int numSpeedSteps) {
        mProcFile = String.format("/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state",
                cpuNumber);
        mLastSpeedTimes = new long[numSpeedSteps];
        mDeltaSpeedTimes = new long[numSpeedSteps];
        long jiffyHz = Libcore.os.sysconf(OsConstants._SC_CLK_TCK);
        mJiffyMillis = 1000/jiffyHz;
    }

2.2 如果没有 time_in_state 的解决方案

配置下 CONFIG_CPU_FREQ_STAT=y 宏即可

https://blog.csdn.net/su749520/article/details/83385322

2.3 更新CPU时间函数

2.3.1 刷新 updateCpuTimeLocked 不同CPU频率的运行时间

    /**
     * Read and distribute CPU usage across apps. If their are partial wakelocks being held
     * and we are on battery with screen off, we give more of the cpu time to those apps holding
     * wakelocks. If the screen is on, we just assign the actual cpu time an app used.
     */
    public void updateCpuTimeLocked() {
        if (mPowerProfile == null) {
            return;
        }

        if (DEBUG_ENERGY_CPU) {
            Slog.d(TAG, "!Cpu updating!");
        }

        // Holding a wakelock costs more than just using the cpu.
        // Currently, we assign only half the cpu time to an app that is running but
        // not holding a wakelock. The apps holding wakelocks get the rest of the blame.
        // If no app is holding a wakelock, then the distribution is normal.
        final int wakelockWeight = 50;

        // Read the time spent for each cluster at various cpu frequencies.
        final long[][] clusterSpeeds = new long[mKernelCpuSpeedReaders.length][];
        for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
            clusterSpeeds[cluster] = mKernelCpuSpeedReaders[cluster].readDelta();
        }

2.3.2 刷新不同应用的CPU消耗时间

当 KernelUidCpuTimeReader 中的文件节点发生变化的时候刷新,对UID下的CPU运行时间

        // Read the CPU data for each UID. This will internally generate a snapshot so next time
        // we read, we get a delta. If we are to distribute the cpu time, then do so. Otherwise
        // we just ignore the data.
        final long startTimeMs = mClocks.elapsedRealtime();
        mKernelUidCpuTimeReader.readDelta(!mOnBatteryInternal ? null :
                new KernelUidCpuTimeReader.Callback() {
                ...
                // Add the cpu speeds to this UID. These are used as a ratio
                // for computing the power this UID used.
                final int numClusters = mPowerProfile.getNumCpuClusters();
                if (u.mCpuClusterSpeed == null || u.mCpuClusterSpeed.length !=
                        numClusters) {
                    u.mCpuClusterSpeed = new LongSamplingCounter[numClusters][];
                }

                for (int cluster = 0; cluster < clusterSpeeds.length; cluster++) {
                    final int speedsInCluster = mPowerProfile.getNumSpeedStepsInCpuCluster(
                            cluster);
                    if (u.mCpuClusterSpeed[cluster] == null || speedsInCluster !=
                            u.mCpuClusterSpeed[cluster].length) {
                        u.mCpuClusterSpeed[cluster] =
                                new LongSamplingCounter[speedsInCluster];
                    }

                    final LongSamplingCounter[] cpuSpeeds = u.mCpuClusterSpeed[cluster];
                    for (int speed = 0; speed < clusterSpeeds[cluster].length; speed++) {
                        if (cpuSpeeds[speed] == null) {
                            cpuSpeeds[speed] = new LongSamplingCounter(mOnBatteryTimeBase);
                        }
                        // 累加数据,最终会存了BatteryState.bin文件中
                        cpuSpeeds[speed].addCountLocked(clusterSpeeds[cluster][speed]);
                    }
                }

3. 获取每个CPU频率消耗的电量 Profile.getAveragePowerForCpu

  • 源码
  • frameworks\base\core\java\com\android\internal\os\PowerProfile.java

具体可以查看
https://blog.csdn.net/su749520/article/details/83340832

    /**
     * 获取cpu.active.cluster[cluster].[step] 对应下标的CPU耗电信息
     * 例如cpu.active.cluster0下第0下标的电流为100 mA
     */
    public double getAveragePowerForCpu(int cluster, int step) {
        if (cluster >= 0 && cluster < mCpuClusters.length) {
            return getAveragePower(mCpuClusters[cluster].powerKey, step);
        }
        return 0;
    }

上述数据,需要手机厂商根据整机进行测试,并填入power_profile文件中

3.1 power_profile.xml文件

  • frameworks\base\core\res\res\xml\power_profile.xml
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2009, 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.
*/
-->

<device name="Android">
  <!-- Most values are the incremental current used by a feature,
       in mA (measured at nominal voltage).
       The default values are deliberately incorrect dummy values.
       OEM's must measure and provide actual values before
       shipping a device.
       Example real-world values are given in comments, but they
       are totally dependent on the platform and can vary
       significantly, so should be measured on the shipping platform
       with a power meter. -->
  <item name="none">0</item>
  <item name="screen.on">200</item>  <!-- ~200mA -->
  <item name="screen.full">300</item>  <!-- ~300mA -->
  <item name="bluetooth.active">66</item> <!-- Bluetooth data transfer, ~10mA -->
  <item name="bluetooth.on">0.5</item>  <!-- Bluetooth on & connectable, but not connected, ~0.1mA -->
  <item name="wifi.on">4.83</item>  <!-- ~3mA -->
  <item name="wifi.active">166</item>  <!-- WIFI data transfer, ~200mA -->
  <item name="wifi.scan">20</item>  <!-- WIFI network scanning, ~100mA -->
  <item name="dsp.audio">39</item> <!-- ~10mA -->
  <item name="dsp.video">145</item> <!-- ~50mA -->
  <item name="camera.flashlight">109</item> <!-- Avg. power for camera flash, ~160mA -->
  <item name="camera.avg">659</item> <!-- Avg. power use of camera in standard usecases, ~550mA -->
  <item name="gps.on">50</item> <!-- ~50mA -->

  <!-- Radio related values. For modems without energy reporting support in firmware, use
       radio.active, radio.scanning, and radio.on. -->
  <item name="radio.active">200</item> <!-- ~200mA -->
  <item name="radio.scanning">32</item> <!-- cellular radio scanning for signal, ~10mA -->
  <!-- Current consumed by the radio at different signal strengths, when paging -->
  <array name="radio.on"> <!-- Strength 0 to BINS-1 -->
      <value>2</value> <!-- ~2mA -->
      <value>1</value> <!-- ~1mA -->
  </array>


  <!-- Radio related values. For modems WITH energy reporting support in firmware, use
       modem.controller.idle, modem.controller.tx, modem.controller.rx, modem.controller.voltage.
       -->
  <item name="modem.controller.idle">0</item>
  <item name="modem.controller.rx">0</item>
  <item name="modem.controller.tx">0</item>
  <item name="modem.controller.voltage">0</item>

  <!-- A list of heterogeneous CPU clusters, where the value for each cluster represents the
       number of CPU cores for that cluster.

       Ex:
       <array name="cpu.clusters.cores">
         <value>4</value> // cluster 0 has cpu0, cpu1, cpu2, cpu3
         <value>2</value> // cluster 1 has cpu4, cpu5
       </array> -->
  <array name="cpu.clusters.cores">
      <value>4</value> <!-- cluster 0 has cpu0, cpu1, cpu2, cpu3 -->
      <value>4</value> <!-- cluster 1 has cpu4, cpu5, cpu6, cpu7 -->
  </array>

    <!-- Different CPU speeds for cluster 0 as reported in
       /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state.

       There must be one of these for each cluster, labeled:
       cpu.speeds.cluster0, cpu.speeds.cluster1, etc... -->
  <array name="cpu.speeds.cluster0">
      <value>900000</value>
      <value>979000</value>
      <value>1085000</value>
      <value>1218000</value>
      <value>1351000</value>
      <value>1484000</value>
      <value>1617000</value>
      <value>1750000</value>
      <value>1779000</value>
      <value>1809000</value>
      <value>1838000</value>
      <value>1868000</value>
      <value>1897000</value>
      <value>1927000</value>
      <value>1961000</value>
      <value>2001000</value>
  </array>

  <array name="cpu.speeds.cluster1">
      <value>400000</value>
      <value>460000</value>
      <value>542000</value>
      <value>643000</value>
      <value>745000</value>
      <value>846000</value>
      <value>948000</value>
      <value>1050000</value>
      <value>1102000</value>
      <value>1155000</value>
      <value>1208000</value>
      <value>1261000</value>
      <value>1314000</value>
      <value>1367000</value>
      <value>1429000</value>
      <value>1500000</value>
  </array>

  <!-- Current at each CPU speed for cluster 0, as per 'cpu.speeds.cluster0'.
       Like cpu.speeds.cluster0, there must be one of these present for
       each heterogeneous CPU cluster. -->
  <array name="cpu.active.cluster0">
      <value>100</value>
      <value>110</value>
      <value>123</value>
      <value>134</value>
      <value>142</value>
      <value>153</value>
      <value>163</value>
      <value>171</value>
      <value>186</value>
      <value>190</value>
      <value>201</value>
      <value>213</value>
      <value>223</value>
      <value>236</value>
      <value>248</value>
      <value>256</value>
  </array>

  <array name="cpu.active.cluster1">
      <value>100</value>
      <value>114</value>
      <value>125</value>
      <value>134</value>
      <value>140</value>
      <value>147</value>
      <value>152</value>
      <value>158</value>
      <value>164</value>
      <value>169</value>
      <value>176</value>
      <value>181</value>
      <value>185</value>
      <value>190</value>
      <value>195</value>
      <value>200</value>
  </array>

  <!-- Current when CPU is idle -->
  <item name="cpu.idle">6</item>

  <!-- Memory bandwidth power values in mA at the rail. There must be one value
       for each bucket defined in the device tree. -->
  <array name="memory.bandwidths">
    <value>22.7</value> <!-- mA for bucket: 100mb/s-1.5 GB/s memory bandwidth -->
  </array>

  <!-- This is the battery capacity in mAh (measured at nominal voltage) -->
  <item name="battery.capacity">3000</item>

  <!-- Wifi related values. -->
  <!-- Idle Receive current for wifi radio in mA. 0 by default-->
  <item name="wifi.controller.idle">0</item>
  <!-- Rx current for wifi radio in mA. 0 by default-->
  <item name="wifi.controller.rx">0</item>
  <!-- Tx current for wifi radio in mA. 0 by default-->
  <item name="wifi.controller.tx">0</item>
  <!-- Current at each of the wifi Tx levels in mA. The number of tx levels varies per device
       and is available only of wifi chipsets which support the tx level reporting. Use
        wifi.tx for other chipsets. none by default -->
  <array name="wifi.controller.tx_levels"> <!-- mA -->
  </array>
  <!-- Operating volatage for wifi radio in mV. 0 by default-->
  <item name="wifi.controller.voltage">0</item>

  <array name="wifi.batchedscan"> <!-- mA -->
    <value>.0002</value> <!-- 1-8/hr -->
    <value>.002</value>  <!-- 9-64/hr -->
    <value>.02</value>   <!-- 65-512/hr -->
    <value>.2</value>    <!-- 513-4,096/hr -->
    <value>2</value>    <!-- 4097-/hr -->
  </array>

</device>

猜你喜欢

转载自blog.csdn.net/su749520/article/details/83385987