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

xiaoxiao2025-10-20  6

1. 原理

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

获取每个CPU频率运行时间接口-BatteryStatsImpl.getTimeAtCpuSpeed获取每个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>
转载请注明原文地址: https://www.6miu.com/read-5038271.html

最新回复(0)