3 种软件方法获取 Android 设备的功耗

硬件方法获取功耗虽然准确,但需要功耗版/power monitor,成本较高,且不方便。Android 系统中也有软件测功耗的方法,下面分别介绍 BatteryManager 类, adbPerfetto 方法。

BatteryManager 类

适用于应用上层,在 SDK 21 及以上可用。

1
2
3
BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
int currentNow = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW); // 当前电流,单位mA
}

Adb 方法

1
2
3
4
shell:/ $ find -name current_now 2>/dev/null # adb shell 中输入
./sys/devices/platform/soc/a90000.i2c/i2c-4/4-0036/power_supply/maxfg/current_now
shell:/ $ cat ./sys/devices/platform/soc/a90000.i2c/i2c-4/4-0036/power_supply/maxfg/current_now
312 # 当前电流,整数表示充电,单位为mA

Perfetto 测器件功耗

瞬时功耗波动很大,以上两个接口获取的数据很可能并不准确。使用 Perfetto 可以监控并获取一段时间的功耗。

Wattson 测 CPU 每个核心的功耗

CPU 性能功耗相关数据获取

Capacity:

1
cat /sys/devices/system/cpu/cpu*/cpu_capacity

获得最高频的capacity,其他频率的可以直接折算。

Power:

芯片的dtsi中会有,可以直接在内核中dts文件夹进行寻找

https://github.com/torvalds/linux/blob/master/arch/arm64/boot/dts/

如果没有找到,可以重新编译内核,在energy_model初始化时加一句printk打印出来。

1
2
3
4
5
6
7
8
9
10
11
12
diff --git a/kernel/power/energy_model.c b/kernel/power/energy_model.c
index c2c858c6d56d..db95d74f00d7 100644
--- a/kernel/power/energy_model.c
+++ b/kernel/power/energy_model.c
@@ -149,6 +149,7 @@ static int em_create_perf_table(struct device *dev, struct em_perf_domain *pd,

table[i].power = power;
table[i].frequency = prev_freq = freq;
+ dev_info(dev, "#%d: freq:%lu power:%lu\n", i, freq, power);
}

/* Compute the cost of each performance state. */

就可以打印出这样的信息:

1
2
3
cpu cpu0: #0: freq:300000 power:10
cpu cpu0: #1: freq:441600 power:15
......

capacity/power,就可以得到能效比(energy efficiency ratio,EER)

如骁龙 8+gen1 sm8475 的EER表

使用Wattons插件

有了频点对应的power表后,可以使用perfetto的wattson插件预估功耗,这个工具会基于每个核频点和运行时间计算功耗。使用前,需要把energy_model中的 power 数据导入到perfetto的 sql 中。

先将perfetto源码clone下来

1
git clone https://android.googlesource.com/platform/external/perfetto/

然后将刚刚的power数据加到src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql 中的_device_curves_1d表中,由于我们只知道运行时功耗,将 static, idle0, idle1 功耗都填0,active功耗填em的power。

在src/trace_processor/perfetto_sql/stdlib/wattson/device_info.sql 中

  • _wattson_device_map表中将CPU和手机的型号对应,手机型号应该在 adb shell 时会打印在 shell 输入的左边,如k60是mondrian。
  • 在 _device_cpu_deep_idle_offsets 表写每个核心的idle时间,每个核心直接写0
  • 在_cpu_to_policy_map 表写每个 cpu 核对应的调频policy
  • 在 _device_min_volt_vote 表中填小核最低频点

如 sm8475 的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql
index be56de6d32..c8a0ef5e21 100644
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql
@@ -95,7 +95,58 @@ WITH data(device, policy, freq_khz, static, active, idle0, idle1) AS (
("neo", 0, 1113600, 5.65, 32.33, 0.99, 0),
("neo", 0, 1497600, 7.19, 45.89, 1.51, 0),
("neo", 0, 1804800, 11.7, 99.98, 2.22, 0),
- ("neo", 0, 1996800, 12.5, 101.04, 2.62, 0)
+ ("neo", 0, 1996800, 12.5, 101.04, 2.62, 0),
+ ("s8pg1", 0, 300000, 0, 10, 0, 0),
+ ("s8pg1", 0, 441600, 0, 15, 0, 0),
+ ("s8pg1", 0, 556800, 0, 19, 0, 0),
+ ("s8pg1", 0, 691200, 0, 26, 0, 0),
+ ("s8pg1", 0, 806400, 0, 31, 0, 0),
+ ("s8pg1", 0, 940800, 0, 38, 0, 0),
+ ("s8pg1", 0, 1056000, 0, 45, 0, 0),
+ ("s8pg1", 0, 1132800, 0, 51, 0, 0),
+ ("s8pg1", 0, 1228800, 0, 58, 0, 0),
+ ("s8pg1", 0, 1324800, 0, 66, 0, 0),
+ ("s8pg1", 0, 1440000, 0, 78, 0, 0),
+ ("s8pg1", 0, 1555200, 0, 90, 0, 0),
+ ("s8pg1", 0, 1670400, 0, 103, 0, 0),
+ ("s8pg1", 0, 1804800, 0, 120, 0, 0),
+ ("s8pg1", 4, 633600, 0, 58, 0, 0),
+ ("s8pg1", 4, 768000, 0, 75, 0, 0),
+ ("s8pg1", 4, 883200, 0, 90, 0, 0),
+ ("s8pg1", 4, 998400, 0, 107, 0, 0),
+ ("s8pg1", 4, 1113600, 0, 124, 0, 0),
+ ("s8pg1", 4, 1209600, 0, 140, 0, 0),
+ ("s8pg1", 4, 1324800, 0, 164, 0, 0),
+ ("s8pg1", 4, 1440000, 0, 189, 0, 0),
+ ("s8pg1", 4, 1555200, 0, 216, 0, 0),
+ ("s8pg1", 4, 1651200, 0, 242, 0, 0),
+ ("s8pg1", 4, 1766400, 0, 273, 0, 0),
+ ("s8pg1", 4, 1881600, 0, 306, 0, 0),
+ ("s8pg1", 4, 1996800, 0, 338, 0, 0),
+ ("s8pg1", 4, 2112000, 0, 379, 0, 0),
+ ("s8pg1", 4, 2227200, 0, 415, 0, 0),
+ ("s8pg1", 4, 2342400, 0, 461, 0, 0),
+ ("s8pg1", 4, 2496000, 0, 514, 0, 0),
+ ("s8pg1", 7, 787200, 0, 138, 0, 0),
+ ("s8pg1", 7, 921600, 0, 173, 0, 0),
+ ("s8pg1", 7, 1036800, 0, 202, 0, 0),
+ ("s8pg1", 7, 1171200, 0, 244, 0, 0),
+ ("s8pg1", 7, 1286400, 0, 274, 0, 0),
+ ("s8pg1", 7, 1401600, 0, 314, 0, 0),
+ ("s8pg1", 7, 1536000, 0, 361, 0, 0),
+ ("s8pg1", 7, 1651200, 0, 411, 0, 0),
+ ("s8pg1", 7, 1766400, 0, 465, 0, 0),
+ ("s8pg1", 7, 1881600, 0, 524, 0, 0),
+ ("s8pg1", 7, 1996800, 0, 593, 0, 0),
+ ("s8pg1", 7, 2131200, 0, 666, 0, 0),
+ ("s8pg1", 7, 2246400, 0, 731, 0, 0),
+ ("s8pg1", 7, 2361600, 0, 808, 0, 0),
+ ("s8pg1", 7, 2476800, 0, 880, 0, 0),
+ ("s8pg1", 7, 2592000, 0, 975, 0, 0),
+ ("s8pg1", 7, 2707200, 0, 1057, 0, 0),
+ ("s8pg1", 7, 2822400, 0, 1153, 0, 0),
+ ("s8pg1", 7, 2918400, 0, 1257, 0, 0),
+ ("s8pg1", 7, 2995200, 0, 1347, 0, 0)
)
select * from data;

diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql
index e5a37d1431..c2fb9c6b7b 100644
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql
@@ -43,7 +43,15 @@ WITH data(device, cpu, offset_ns) AS (
("neo", 0, 100000),
("neo", 1, 100000),
("neo", 2, 100000),
- ("neo", 3, 100000)
+ ("neo", 3, 100000),
+ ("s8pg1", 0, 0),
+ ("s8pg1", 1, 0),
+ ("s8pg1", 2, 0),
+ ("s8pg1", 3, 0),
+ ("s8pg1", 4, 0),
+ ("s8pg1", 5, 0),
+ ("s8pg1", 6, 0),
+ ("s8pg1", 7, 0)
)
select * from data;

@@ -55,7 +63,8 @@ WITH data(device, wattson_device) AS (
("raven", "Tensor"),
("bluejay", "Tensor"),
("eos", "monaco"),
- ("aurora", "monaco")
+ ("aurora", "monaco"),
+ ("mondrian", "s8pg1")
)
select * from data;

@@ -110,7 +119,15 @@ WITH data(device, cpu, policy) AS (
("neo", 0, 0),
("neo", 1, 0),
("neo", 2, 0),
- ("neo", 3, 0)
+ ("neo", 3, 0),
+ ("s8pg1", 0, 0),
+ ("s8pg1", 1, 0),
+ ("s8pg1", 2, 0),
+ ("s8pg1", 3, 0),
+ ("s8pg1", 4, 4),
+ ("s8pg1", 5, 4),
+ ("s8pg1", 6, 4),
+ ("s8pg1", 7, 7)
)
select * from data;

@@ -132,7 +149,8 @@ WITH data(device, policy, freq) AS (
("monaco", 0, 614400),
("Tensor", 4, 400000),
("Tensor G4", 0, 700000),
- ("neo", 0, 691200)
+ ("neo", 0, 691200),
+ ("s8pg1", 0, 614400)
)
select * from data;

保存后编译perfetto:

1
2
3
4
5
# Install build dependencies
tools/install-build-deps --ui

# This will automatically build the UI. There is no need to manually run# ui/build before running ui/run-dev-server.
ui/run-dev-server

服务器会在 localhost:10000 中执行,按照教程抓一个trace,应该就能看到功耗了。

注意事项:因为energy_model中power单位不确定,两个不同设备的power不可直接用于比较。

Battery counters 测电池消耗的电量

需要设备上存在电源管理硬件。大多数 Google Pixel 智能手机都支持

此功能。注意需要断开USB测试。

img

Sample config:

1
2
3
4
5
6
7
8
9
10
11
12
data_sources: {
config {
name: "android.power"
android_power_config {
battery_poll_ms: 250
battery_counters: BATTERY_COUNTER_CAPACITY_PERCENT
battery_counters: BATTERY_COUNTER_CHARGE
battery_counters: BATTERY_COUNTER_CURRENT
battery_counters: BATTERY_COUNTER_VOLTAGE
}
}
}

perfetto的batt.charge_uah是当前电量,前后数值一减就是消耗的电量。

img

ODPM 测元器件的功耗

pixel 6 以上(含6)的pixel手机支持On-Device Power Rails Monitor (ODPM),在硬件层面做了更细粒度电源监控的支持,可以测量不同元器件的功耗。在平台级别,此数据是通过轮询 Android IPowerStats HAL 获得的。

img

Sample config:

1
2
3
4
5
6
7
8
9
10
11
data_sources: {
config {
name: "android.power"
android_power_config {
battery_poll_ms: 250
collect_power_rails: true
# Note: it is possible to specify both rails and battery counters
# in this section.
}
}
}

下面介绍如何使用ODPM对pixel 8功耗进行测量。

config.pbtx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
buffers: {
size_kb: 63488
fill_policy: DISCARD
}
buffers: {
size_kb: 2048
fill_policy: DISCARD
}
data_sources: {
config {
name: "android.power"
android_power_config {
battery_poll_ms: 1000
collect_power_rails: true
}
}
}

duration_ms: 10000

修改duration_ms设置测量时间,修改battery_poll_ms设置间隔。

执行命令得到trace文件

1
perfetto --txt -c /data/local/tmp/config.pbtx -o /data/local/tmp/trace.perfetto-trace

img

在perfetto web界面左侧导航栏选择Metrics,选择android_powrails,得到详细的功耗信息(如下图)。

img

其中,name对应元器件名称,每个元器件都有energy_dataavg_used_power_mwenergy_data中包含了每个间隔battery_poll_ms的采样能耗,avg_used_power_mwduration_ms时间内的平均功率。

在最末尾得到设备在duration_ms时间内的总平均功率avg_total_used_power_mw

Reference

[1] https://perfetto.dev/docs/data-sources/battery-counters

[2] https://lpc.events/event/18/contributions/1842/attachments/1476/3126/LPC%202024%20-%20Wattson.pdf

[3] https://android-developers.googleblog.com/2024/04/how-to-effectively-ab-test-power-consumption-for-your-android-app-features.html

[4] https://developer.android.com/studio/profile/power-profiler