Android 功耗统计的核心函数是文件BatteryStatsHelper.java中的refreshStats函数,此函数会调用processAppUsage函数和processMiscUsage函数分别计算APP功耗和系统硬件功耗。下面将详细介绍如何计算APP功耗,系统硬件功耗与APP功耗的计算方法相似,就不再介绍了。
在processAppUsage函数中,分别调用了如下函数:
Android 功耗统计的核心函数是文件BatteryStatsHelper.java中的refreshStats函数,此函数会调用processAppUsage函数和processMiscUsage函数分别计算APP功耗和系统硬件功耗。下面将详细介绍如何计算APP功耗,系统硬件功耗与APP功耗的计算方法相似,就不再介绍了。
在processAppUsage函数中,分别调用了如下函数:
1. mCpuPowerCalculator.calculateApp //CPU功耗
2. mWakelockPowerCalculator.calculateApp //持有wakelock锁时的功耗
3. mMobileRadioPowerCalculator.calculateApp//CP(PHONE)模块的功耗
4. mWifiPowerCalculator.calculateApp //WIFI模块的功耗
5. mBluetoothPowerCalculator.calculateApp //蓝牙模块的功耗
6. mSensorPowerCalculator.calculateApp //传感器模块的功耗
7. mCameraPowerCalculator.calculateApp //相机模块的功耗
8. mFlashlightPowerCalculator.calculateApp //闪光灯模块的功耗
各模块功耗的计算方法基本类似,分为如下三步:
1. 从power_profile.xml获取对应模块不同工作模式下的电流;
2. 获取模块在对应工作模式下工作的时间;
3. 将不同模式下的电流乘以对应的时间再求和得到整个模块的功耗。
public class CpuPowerCalculator extends PowerCalculator {
private static final String TAG = "CpuPowerCalculator"; private static final boolean DEBUG = BatteryStatsHelper.DEBUG; private final PowerProfile mProfile; public CpuPowerCalculator(PowerProfile profile) { mProfile = profile; } @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; final int numClusters = mProfile.getNumCpuClusters(); for (int cluster = 0; cluster < numClusters; cluster++) { final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster); for (int speed = 0; speed < speedsForCluster; speed++) { totalTime += u.getTimeAtCpuSpeed(cluster, speed, statsType); } } totalTime = Math.max(totalTime, 1); double cpuPowerMaMs = 0; for (int cluster = 0; cluster < numClusters; cluster++) { final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster); for (int speed = 0; speed < speedsForCluster; speed++) { final double ratio = (double) u.getTimeAtCpuSpeed(cluster, speed, statsType) / totalTime; 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))); } 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)); } // Keep track of the package with highest drain. double highestDrain = 0; app.cpuFgTimeMs = 0; final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats(); final int processStatsCount = processStats.size(); for (int i = 0; i < processStatsCount; i++) { final BatteryStats.Uid.Proc ps = processStats.valueAt(i); final String processName = processStats.keyAt(i); app.cpuFgTimeMs += ps.getForegroundTime(statsType); final long costValue = ps.getUserTime(statsType) + ps.getSystemTime(statsType) + ps.getForegroundTime(statsType); // Each App can have multiple packages and with multiple running processes. // Keep track of the package who's process has the highest drain. if (app.packageWithHighestDrain == null || app.packageWithHighestDrain.startsWith("*")) { highestDrain = costValue; app.packageWithHighestDrain = processName; } else if (highestDrain < costValue && !processName.startsWith("*")) { highestDrain = costValue; app.packageWithHighestDrain = processName; } } // Ensure that the CPU times make sense. if (app.cpuFgTimeMs > app.cpuTimeMs) { if (DEBUG && app.cpuFgTimeMs > app.cpuTimeMs + 10000) { Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time"); } // Statistics may not have been gathered yet. app.cpuTimeMs = app.cpuFgTimeMs; } } }
讯享网
在分析代码之前,有一些和CPU架构(ARM)相关的知识需要简单的介绍一下,目前主流的Android手机,基本都是SMP系统,也就是多核系统,咱们就以一个8核系统为例。而ARM中又有big core 和litter core的概念,所谓big core其实就是高性能的CPU,相应的litter core就是性能相对差一些的CPU,为了便于管理,ARM提出了cluster的概念,例如咱们可以将8个core分为两个cluster,第一个cluster管理性能低一些的CPU,第二个cluster管理高性能的CPU,至于每个cluster包含多少个CPU,可从相应的datasheet中获取。另外每个cluster中的CPU可以工作在不同的频率下,而不同频率下的电流是不同的,所以在power_profile.xml文件中需要指明不同频点下的电流值。
言归正传,从代码可以看出,从power_profile.xml文件中读取不同频率下的电流值采用如下接口:
mProfile.getAveragePowerForCpu(cluster, speed);
其中cluster是指明目前获取的是哪一个cluster的电流,speed指明是哪一个频点(频率)。
可以看到,在各个频点下功耗的计算,并不是简单的获取各个频点下的时间乘以对应的电流,而是先计算频点的时间再整个总频点时间的比率,然后再乘以app实际的工作时间得到一个比较精确的特定频点下的工作时间,Android选择这样做应该也是为了提高数据的可靠性吧。
app.cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
final double ratio = (double) u.getTimeAtCpuSpeed(cluster, speed, statsType) / totalTime;
final double cpuSpeedStepPower = ratio * app.cpuTimeMs *
mProfile.getAveragePowerForCpu(cluster, speed);
关于getTimeAtCpuSpeed接口的实现,比较复杂,限于篇幅,本文就不分析了。不过最终获取的CPU在频点的工作时间,还是通过kernel的节点获取的,实现在文件KernelCpuSpeedReader.java中,代码如下:
讯享网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; } / * The returned array is modified in subsequent calls to {@link #readDelta}. * @return The time (in milliseconds) spent at different cpu speeds since the last call to * {@link #readDelta}. */ public long[] readDelta() { StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads(); try (BufferedReader reader = new BufferedReader(new FileReader(mProcFile))) { TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' '); String line; int speedIndex = 0; while (speedIndex < mLastSpeedTimes.length && (line = reader.readLine()) != null) { splitter.setString(line); Long.parseLong(splitter.next()); long time = Long.parseLong(splitter.next()) * mJiffyMillis; if (time < mLastSpeedTimes[speedIndex]) { // The stats reset when the cpu hotplugged. That means that the time // we read is offset from 0, so the time is the delta. mDeltaSpeedTimes[speedIndex] = time; } else { mDeltaSpeedTimes[speedIndex] = time - mLastSpeedTimes[speedIndex]; } mLastSpeedTimes[speedIndex] = time; speedIndex++; } } catch (IOException e) { Slog.e(TAG, "Failed to read cpu-freq: " + e.getMessage()); Arrays.fill(mDeltaSpeedTimes, 0); } finally { StrictMode.setThreadPolicy(policy); } return mDeltaSpeedTimes; } }
可以看到,获取CPU在各个频点下的工作时间,是通过读取”/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state”文件获得的。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/21261.html