聊聊 Android 中的 power_profile 文件

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
    CPUsuspend 状态时的功耗。
  • cpu.idle
    CPUidle 状态时的额外功耗。
  • cpu.active
    CPUactive 状态时的额外功耗。
  • 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 (当 CPUidle 状态才加) + cpu.active(当 CPUactive 状态时才加)+ 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...

相关推荐
抛砖者3 小时前
9. Flink的性能优化
android·性能优化·flink
一直在路上的码农8 小时前
ES时序数据库的性能优化
运维·elasticsearch·性能优化·时序数据库
IT、木易9 小时前
大白话html第八章HTML 与新兴技术的结合、更高级的性能优化
前端·性能优化·html
m0_7482509317 小时前
SQL Server Management Studio的使用
数据库·oracle·性能优化
Jourwneyy1 天前
Day.js:轻量级的时间日期处理库
性能优化
_.Switch1 天前
高效API开发:FastAPI中的缓存技术与性能优化
python·缓存·性能优化·负载均衡·fastapi
Black.Spider1 天前
C++性能优化常用技巧
开发语言·c++·性能优化·多线程编程·指令优化·编译器优化·缓存命中率
名之以父1 天前
【AI Coding】Windsurf:【Prompt】全局规则与项目规则「可直接使用」
前端·javascript·vscode·低代码·chatgpt·性能优化·aigc
m0_748229991 天前
全面指南:使用JMeter进行性能压测与性能优化(中间件压测、数据库压测、分布式集群压测、调优)
jmeter·中间件·性能优化