Android
中定义 power_profile
了文件用来描述各个硬件工作时的不同的状态的功耗,这个文件通常需要手机厂商提供,通过这个文件 Android
系统就可以计算不同的硬件的在某段时间中的电量消耗,或者计算不同的进程在某时间段的电量消耗,可以看看官方的描述:source.android.com/docs/core/p...
我们日常生活中通常用度
来描述电量,他的单位实际上是 KW * h
,读作 千瓦 * 时
,也就是功率为 1000 W 的电器,一小时消耗的电量;除了上面描述电量的单位外,还有 A * h
,读作 安 * 时
,也就是电流大小为 1 A 时,放电一小时消耗的电量,而我们的大多数电子产品的电量单位是 mA * h
,读作 毫安 * 时
, 1 * A * h
= 1000 mA * h
。我们经常说的手机的电池大小直接说 4000 毫安,其实 4000 毫安明明是一个电流大小的单位啊??其实完整的说法是 4000 毫安 * 时
(这种简称其实非常容易误导上学的小朋友,我个人也不喜欢这种简称)。
好了我们言归正传,继续说 power_profile
文件,它描述某个硬件的按额定功率运行时的电流大小,单位是 mA
,某些以 voltage
结尾描述的参数它是用来描述电压的,单位是 mV
。假如我们要计算某个描述为 0.5 mA
的硬件一个小时的耗电量就是 1 h
* 0.5 mA
= 0.5 mA * h
。假如我们要编程来计算某个时间段某些硬件或者进程的耗电量,就是通过上面公式。
那么在 Android
中如何拿到 power_profile
文件的内容呢?参考以下代码:
Kotlin
val id = application.resources.getIdentifier("power_profile", "xml", "android")
val parser = application.resources.getXml(id)
// ... 省略解析过程
首先拿到 power_profile
文件的 id
,然后再获取对应的 XmlResourceParser
,然后手动去解析每个 item
。如果要查看完整的解析代码,参考: github.com/Tans5/tPowe...
我这里给一个我的测试手机的 power_profile
文件:
xml
<device>
<item name="battery.capacity">4410.0</item>
<array name="cpu.clusters.cores">
<value>4.0</value>
<value>2.0</value>
<value>2.0</value>
</array>
<item name="cpu.suspend">10.0</item>
<item name="cpu.idle">28.6</item>
<item name="cpu.active">12.37</item>
<item name="cpu.cluster_power.cluster0">0.24</item>
<item name="cpu.cluster_power.cluster1">3.23</item>
<item name="cpu.cluster_power.cluster2">5.94</item>
<array name="cpu.core_speeds.cluster0">
<value>300000.0</value>
<value>574000.0</value>
<value>738000.0</value>
<value>930000.0</value>
<value>1098000.0</value>
<value>1197000.0</value>
<value>1328000.0</value>
<value>1401000.0</value>
<value>1598000.0</value>
<value>1704000.0</value>
<value>1803000.0</value>
</array>
<array name="cpu.core_speeds.cluster1">
<value>400000.0</value>
<value>553000.0</value>
<value>696000.0</value>
<value>799000.0</value>
<value>910000.0</value>
<value>1024000.0</value>
<value>1197000.0</value>
<value>1328000.0</value>
<value>1491000.0</value>
<value>1663000.0</value>
<value>1836000.0</value>
<value>1999000.0</value>
<value>2130000.0</value>
<value>2253000.0</value>
</array>
<array name="cpu.core_speeds.cluster2">
<value>500000.0</value>
<value>851000.0</value>
<value>984000.0</value>
<value>1106000.0</value>
<value>1277000.0</value>
<value>1426000.0</value>
<value>1582000.0</value>
<value>1745000.0</value>
<value>1826000.0</value>
<value>2048000.0</value>
<value>2188000.0</value>
<value>2252000.0</value>
<value>2401000.0</value>
<value>2507000.0</value>
<value>2630000.0</value>
<value>2704000.0</value>
<value>2802000.0</value>
</array>
<array name="cpu.core_power.cluster0">
<value>1.89</value>
<value>6.15</value>
<value>9.34</value>
<value>14.22</value>
<value>18.94</value>
<value>21.98</value>
<value>26.83</value>
<value>30.17</value>
<value>41.55</value>
<value>48.36</value>
<value>58.45</value>
</array>
<array name="cpu.core_power.cluster1">
<value>3.71</value>
<value>6.16</value>
<value>8.0</value>
<value>10.94</value>
<value>12.73</value>
<value>14.4</value>
<value>21.39</value>
<value>24.1</value>
<value>30.42</value>
<value>42.49</value>
<value>49.37</value>
<value>58.09</value>
<value>67.54</value>
<value>79.04</value>
</array>
<array name="cpu.core_power.cluster2">
<value>8.36</value>
<value>16.33</value>
<value>19.44</value>
<value>36.71</value>
<value>41.42</value>
<value>48.24</value>
<value>54.77</value>
<value>65.32</value>
<value>69.58</value>
<value>128.49</value>
<value>142.15</value>
<value>149.74</value>
<value>164.78</value>
<value>188.68</value>
<value>193.15</value>
<value>227.98</value>
<value>254.25</value>
</array>
<item name="ambient.on">32.0</item>
<item name="screen.on">98.0</item>
<item name="screen.full">470.0</item>
<item name="camera.flashlight">240.47</item>
<item name="camera.avg">900.0</item>
<item name="video">25.0</item>
<item name="audio">75.0</item>
<item name="modem.controller.sleep">0.0</item>
<item name="modem.controller.idle">156.0</item>
<item name="modem.controller.rx">145.0</item>
<array name="modem.controller.tx">
<value>153.0</value>
<value>212.0</value>
<value>292.0</value>
<value>359.0</value>
<value>471.0</value>
</array>
<item name="modem.controller.voltage">3700.0</item>
<array name="gps.signalqualitybased">
<value>14.33</value>
<value>12.79</value>
</array>
<item name="gps.voltage">3700.0</item>
<item name="wifi.controller.idle">38.0</item>
<item name="wifi.controller.rx">98.0</item>
<item name="wifi.controller.tx">470.0</item>
<item name="wifi.controller.voltage">3700.0</item>
<item name="bluetooth.controller.idle">2.2</item>
<item name="bluetooth.controller.rx">5.8</item>
<item name="bluetooth.controller.tx">20.0</item>
<item name="bluetooth.controller.voltage">3850.0</item>
</device>
其中包括电池的容量 和 CPU,屏幕,相机,闪光灯,多媒体编解码芯片,基带,GPS,Wi-Fi,蓝牙的功耗。
计算 CPU
核心功耗是最复杂的,通过上面的数据也知道了,我们以计算 CPU
核心的功耗为例子来讲解如何计算功耗。
首先我们要了解 CPU
簇的概念,通常我们现在的手机都是多核心的,不同的核心的时钟频率不一样,按照不同的频率我们可以分为小核,中核,大核。就可以把这些同样的核心划为一个 CPU
簇,一个 CPU
簇中的核心统一管理,可以用来控制这个簇中的所有 CPU
的核心频率等等,还可以将某些更加重要的任务交给速度更快的 CPU
簇中的核心处理。我们看上面的 power_profile
的文件中有的包含 cluster
的名字是用来描述 cluster
相应的信息,cluster
后面对应的编号就是对应的 cluster
的编号。
我们来解释一下上面 CPU
相关的信息。
cpu.clusters.cores
不同的CPU
簇的核心数量,上面我们的手机有 3 个CPU
簇,分别包含 4,2,2 个核心。cpu.suspend
CPU
在suspend
状态时的功耗。cpu.idle
CPU
在idle
状态时的额外功耗。cpu.active
CPU
在active
状态时的额外功耗。cpu.cluster_power.clusterX
ClusterX
工作时的功耗。cpu.core_speeds.clusterX
ClusterX
中的核心支持的时钟频率,单位:KHz
。cpu.core_power.clusterX
ClusterX
中的核心对应的时钟频率时的额外功耗。
我第一次看到这么多的参数时,我也是懵逼的,直到我后来看到了 AOSP
的代码中有下面一段注释。
Java
/**
* CPU Power Equation (assume two clusters):
* Total power = POWER_CPU_SUSPEND (always added)
* + POWER_CPU_IDLE (skip this and below if in power collapse mode)
* + POWER_CPU_ACTIVE (skip this and below if CPU is not running, but a wakelock
* is held)
* + cluster_power.cluster0 + cluster_power.cluster1 (skip cluster not running)
* + core_power.cluster0 * num running cores in cluster 0
* + core_power.cluster1 * num running cores in cluster 1
*/
那我就知道了怎么计算 CPU
的功耗了,看上去计算还是挺复杂的。简单翻译下(它这里是以 2 个 CPU
簇为例子): 功耗 = cpu.suspend
(始终都加这个值) + cpu.idle
(当 CPU
为 idle
状态才加) + cpu.active
(当 CPU
为 active
状态时才加)+ cpu.cluster_power.cluster0
(当 cluster0
工作时才加) + cpu.cluster_power.cluster1
(当 cluster1
工作时才加) + cpu.core_power.cluster0
* 工作的核心数(核心在不同的频率的频率功耗不同)+ cpu.core_power.cluster1
* 工作的核心数(核心在不同的频率的频率功耗不同)
光是用脑袋想想都觉得计算麻烦,写一个 demo
来试试计算看看。那么我们要怎么知道某个核心的工作时间呢???需要去读 /sys/devices/system/cpu/cpu[X]/cpufreq/stats/time_in_state
(也可以去读 /sys/devices/system/cpu/cpufreq/policy[X]/stats/time_in_state
他是表明某个 cluster
中所有的核心的时间消耗,通常同一个 cluster
中的核心同一时间 time_in_state
是一样的) 文件就能够获取它在开机到现在在不同的频率下的工作时间,单位是 jiffy
,一般 1 jiffy
= 10 ms
。
text
500000 1631091
851000 3472
984000 1440
1106000 942
1277000 1324
1426000 1706
1582000 896
1745000 4052
1826000 34474
2048000 1764
2188000 834
2252000 390
2401000 544
2507000 500
2630000 864
2704000 685
2802000 7256
每一行,左边的值为频率,右边的值为对应的频率工作的时间。
可以通过读取 proc/uptime
来获取开机到现在的时间。
text
267349.10 135392.58
这里计算有一个小问题,在 Android 8
以后的版本,是无法通过读取 /proc/stat
文件去获取 CPU
Idle
的时间。
我的 Demo
代码如下:
Kotlin
@Test
fun calculateCpuPower() {
fun Long.jiffiesToHours(): Double {
return this * 10.0 / (60.0 * 60.0 * 1000.0)
}
fun Double.secondsToHours(): Double {
return this / (60.0 * 60.0)
}
fun Double.toHoursString(): String {
return String.format(Locale.US, "%.2f H", this)
}
fun Long.toCpuSpeedString(): String {
return String.format(Locale.US, "%.2f GHz", this / 1_000_000.0)
}
fun Double.toPowerString(): String {
return String.format(Locale.US, "%.2f mAh", this)
}
val uptimeInHour = (328926.48).secondsToHours()
println("Uptime: ${uptimeInHour.toHoursString()}")
val cluster0SpeedAndTimeCostInJiffies = mapOf(
300000L to 7095890L,
574000L to 289404L,
738000L to 236020L,
930000L to 98347L,
1098000L to 31118L,
1197000L to 48818L,
1328000L to 25985L,
1401000L to 9260L,
1598000L to 11101L,
1704000L to 5630L,
1803000L to 72130L,
)
val cluster1SpeedAndTimeCostInJiffies = mapOf(
400000L to 7788706L,
553000L to 20455L,
696000L to 9286L,
799000L to 6210L,
910000L to 6652L,
1024000L to 5272L,
1197000L to 6069L,
1328000L to 3539L,
1491000L to 37000L,
1663000L to 2858L,
1836000L to 2006L,
1999000L to 2188L,
2130000L to 1573L,
2253000L to 31893L,
)
val cluster2SpeedAndTimeCostInJiffies = mapOf(
500000L to 7851333L,
851000L to 5342L,
984000L to 2268L,
1106000L to 1534L,
1277000L to 2005L,
1426000L to 2723L,
1582000L to 1322L,
1745000L to 5086L,
1826000L to 35781L,
2048000L to 2665L,
2188000L to 1078L,
2252000L to 508L,
2401000L to 758L,
2507000L to 638L,
2630000L to 1097L,
2704000L to 862L,
2802000L to 8706L,
)
fun calculateClusterPowerCost(
clusterName: String,
clusterCoreCount: Int,
clusterPower: Double,
clusterSpeeds: LongArray,
clusterSpeedExtraPower: DoubleArray,
clusterSpeedAndTimeCostInJiffies: Map<Long, Long>
): Double {
val activeTime = clusterSpeedAndTimeCostInJiffies.values.sum().jiffiesToHours()
println("----------------------------------------------------------------")
println("Cluster: $clusterName, ActiveTime: ${activeTime.toHoursString()}, CoreCount: $clusterCoreCount")
var powerCost = (cpuSuspend + cpuActive + clusterPower) * activeTime
for ((speed, timeCostInJiffies) in clusterSpeedAndTimeCostInJiffies) {
val timeCostInHour = timeCostInJiffies.jiffiesToHours()
val speedExtraPower = clusterSpeedExtraPower[clusterSpeeds.indexOf(speed)]
val c = (timeCostInHour * speedExtraPower) * clusterCoreCount
println("Speed: ${speed.toCpuSpeedString()}, PowerCost: ${c.toPowerString()}, TimeCost: ${timeCostInHour.toHoursString()}")
powerCost += c
}
println("PowerCost: ${powerCost.toPowerString()}")
println("----------------------------------------------------------------")
return powerCost
}
val cluster0PowerCost = calculateClusterPowerCost(
clusterName = "Cluster0",
clusterCoreCount = 4,
clusterPower = cpuClusterPowerCluster0,
clusterSpeeds = cpuCoreSpeedsCluster0,
clusterSpeedExtraPower = cpuCorePowerCluster0,
clusterSpeedAndTimeCostInJiffies = cluster0SpeedAndTimeCostInJiffies
)
val cluster1PowerCost = calculateClusterPowerCost(
clusterName = "Cluster1",
clusterCoreCount = 2,
clusterPower = cpuClusterPowerCluster1,
clusterSpeeds = cpuCoreSpeedsCluster1,
clusterSpeedExtraPower = cpuCorePowerCluster1,
clusterSpeedAndTimeCostInJiffies = cluster1SpeedAndTimeCostInJiffies
)
val cluster2PowerCost = calculateClusterPowerCost(
clusterName = "Cluster2",
clusterCoreCount = 2,
clusterPower = cpuClusterPowerCluster2,
clusterSpeeds = cpuCoreSpeedsCluster2,
clusterSpeedExtraPower = cpuCorePowerCluster2,
clusterSpeedAndTimeCostInJiffies = cluster2SpeedAndTimeCostInJiffies
)
println("CpuPowerCost: ${(cluster0PowerCost + cluster1PowerCost + cluster2PowerCost).toPowerString()}")
}
最后我的 Demo
输出为:
text
Uptime: 91.37 H
----------------------------------------------------------------
Cluster: Cluster0, ActiveTime: 22.01 H, CoreCount: 4
Speed: 0.30 GHz, PowerCost: 149.01 mAh, TimeCost: 19.71 H
Speed: 0.57 GHz, PowerCost: 19.78 mAh, TimeCost: 0.80 H
Speed: 0.74 GHz, PowerCost: 24.49 mAh, TimeCost: 0.66 H
Speed: 0.93 GHz, PowerCost: 15.54 mAh, TimeCost: 0.27 H
Speed: 1.10 GHz, PowerCost: 6.55 mAh, TimeCost: 0.09 H
Speed: 1.20 GHz, PowerCost: 11.92 mAh, TimeCost: 0.14 H
Speed: 1.33 GHz, PowerCost: 7.75 mAh, TimeCost: 0.07 H
Speed: 1.40 GHz, PowerCost: 3.10 mAh, TimeCost: 0.03 H
Speed: 1.60 GHz, PowerCost: 5.12 mAh, TimeCost: 0.03 H
Speed: 1.70 GHz, PowerCost: 3.03 mAh, TimeCost: 0.02 H
Speed: 1.80 GHz, PowerCost: 46.84 mAh, TimeCost: 0.20 H
PowerCost: 790.79 mAh
----------------------------------------------------------------
----------------------------------------------------------------
Cluster: Cluster1, ActiveTime: 22.01 H, CoreCount: 2
Speed: 0.40 GHz, PowerCost: 160.53 mAh, TimeCost: 21.64 H
Speed: 0.55 GHz, PowerCost: 0.70 mAh, TimeCost: 0.06 H
Speed: 0.70 GHz, PowerCost: 0.41 mAh, TimeCost: 0.03 H
Speed: 0.80 GHz, PowerCost: 0.38 mAh, TimeCost: 0.02 H
Speed: 0.91 GHz, PowerCost: 0.47 mAh, TimeCost: 0.02 H
Speed: 1.02 GHz, PowerCost: 0.42 mAh, TimeCost: 0.01 H
Speed: 1.20 GHz, PowerCost: 0.72 mAh, TimeCost: 0.02 H
Speed: 1.33 GHz, PowerCost: 0.47 mAh, TimeCost: 0.01 H
Speed: 1.49 GHz, PowerCost: 6.25 mAh, TimeCost: 0.10 H
Speed: 1.66 GHz, PowerCost: 0.67 mAh, TimeCost: 0.01 H
Speed: 1.84 GHz, PowerCost: 0.55 mAh, TimeCost: 0.01 H
Speed: 2.00 GHz, PowerCost: 0.71 mAh, TimeCost: 0.01 H
Speed: 2.13 GHz, PowerCost: 0.59 mAh, TimeCost: 0.00 H
Speed: 2.25 GHz, PowerCost: 14.00 mAh, TimeCost: 0.09 H
PowerCost: 750.35 mAh
----------------------------------------------------------------
----------------------------------------------------------------
Cluster: Cluster2, ActiveTime: 22.01 H, CoreCount: 2
Speed: 0.50 GHz, PowerCost: 364.65 mAh, TimeCost: 21.81 H
Speed: 0.85 GHz, PowerCost: 0.48 mAh, TimeCost: 0.01 H
Speed: 0.98 GHz, PowerCost: 0.24 mAh, TimeCost: 0.01 H
Speed: 1.11 GHz, PowerCost: 0.31 mAh, TimeCost: 0.00 H
Speed: 1.28 GHz, PowerCost: 0.46 mAh, TimeCost: 0.01 H
Speed: 1.43 GHz, PowerCost: 0.73 mAh, TimeCost: 0.01 H
Speed: 1.58 GHz, PowerCost: 0.40 mAh, TimeCost: 0.00 H
Speed: 1.75 GHz, PowerCost: 1.85 mAh, TimeCost: 0.01 H
Speed: 1.83 GHz, PowerCost: 13.83 mAh, TimeCost: 0.10 H
Speed: 2.05 GHz, PowerCost: 1.90 mAh, TimeCost: 0.01 H
Speed: 2.19 GHz, PowerCost: 0.85 mAh, TimeCost: 0.00 H
Speed: 2.25 GHz, PowerCost: 0.42 mAh, TimeCost: 0.00 H
Speed: 2.40 GHz, PowerCost: 0.69 mAh, TimeCost: 0.00 H
Speed: 2.51 GHz, PowerCost: 0.67 mAh, TimeCost: 0.00 H
Speed: 2.63 GHz, PowerCost: 1.18 mAh, TimeCost: 0.00 H
Speed: 2.70 GHz, PowerCost: 1.09 mAh, TimeCost: 0.00 H
Speed: 2.80 GHz, PowerCost: 12.30 mAh, TimeCost: 0.02 H
PowerCost: 1025.18 mAh
----------------------------------------------------------------
CpuPowerCost: 2566.32 mAh
Demo
源码:github.com/Tans5/tPowe...