Android平台GPU负载计算、分析方法
11:42 am
Wednesday, 6 April 2022 (GMT+8)
Time in Guangzhou, Guangdong Province, China
简介
本文介绍Android平台(Android Q,MTK(CPU),Mali(GPU))通过kernel misc节点读取GPU负载的方法。介绍了通过watch
、awk
自动周期性间隔打印GPU利用率的命令。
读取负载
读取GPU负载;输出三列数据分别为Loading、Block、Idle
cat /sys/kernel/debug/ged/hal/gpu_utilization
其中Loading、Blocking、Idle可以分别从独立的节点中单独读取
1
2
3
4
5cat /sys/module/ged/parameters/gpu_loading
cat /sys/module/ged/parameters/gpu_block
cat /sys/module/ged/parameters/gpu_idlesys节点下kernel的dvfs挂载到了/d,因此通过
/d
访问是事实上等价的。cat /d/ged/hal/gpu_utilization
读取带百分号的GPU占用率
cat /d/ged/hal/gpu_utilization | awk '{print $1"%"}'
每隔1秒钟输出一次带百分号的GPU占用率
watch -tn1 "cat /d/ged/hal/gpu_utilization | awk ' {print \$1\"%\"}'"
实现
本节介绍MTK(with Mali) Android Q平台kernel中关于GPU Debug VFS(ged)的实现。下文内容均基于父目录kernel-4.14/drivers/misc/mediatek/gpu
(下文将简称父目录)。在该目录下,主要关注的是各级Makefile、Kconfig以及hal目录、ged(GPU Extension Device
)目录内的源码。
- DFS节点创建、注册处理函数
ged/*/*/ged_hal.c
。从代码可知,该节点向外输出信息是借助seq file interface的,节点本身实现为seq file。1
2
3
4
5
6
7
8
9
10
11/* Get GPU Utilization */
err = ged_debugFS_create_entry("gpu_utilization",
gpsHALDir, &gsDvfs_gpu_util_ReadOps, NULL, NULL,
&gpsDvfsGpuUtilizationEntry);
static const struct seq_operations gsDvfs_gpu_util_ReadOps = {
.start = ged_dvfs_gpu_util_seq_start,
.stop = ged_dvfs_gpu_util_seq_stop,
.next = ged_dvfs_gpu_util_seq_next,
.show = ged_dvfs_gpu_util_seq_show,
};seq file提供的接口让读取数据通过类似迭代器的形式实现:通过start接口拿到迭代器,并通过next移动迭代器、stop释放迭代器以及show接口获取迭代的信息。
- 阅读代码发现,该节点的start、stop、next节点字面上是空实现,或逻辑意义上是空实现/无业务逻辑实现,因此仅关注show接口即可。
- show接口代码实现清晰,可以看到节点读取出来的第一个值就是GPU负载了(百分比)
1
2
3
4
5
6
7
8
9
10
11
12
13static int ged_dvfs_gpu_util_seq_show(struct seq_file *psSeqFile, void *pvData)
{
if (pvData != NULL) {
unsigned int loading;
unsigned int block;
unsigned int idle;
mtk_get_gpu_loading(&loading);
mtk_get_gpu_block(&block);
mtk_get_gpu_idle(&idle);
seq_printf(psSeqFile, "%u %u %u\n", loading, block, idle);
}
return 0;
} - 这里我们已经看到了节点输出的三个数据的含义,进一步的,简单看看它如何计算得到数值的。阅读代码可知
mtk_get_gpu_loading
等三个函数通过策略模式实现了一层包装,真正的实现在gpu_mali/*/*/drivers/gpu/arm/midgard/backend/gpu/*
。这里有多个不同芯片的实现,对应许多不同目录,查找资料了解到G72属于彩虹桥架构,因此看对应的目录mali_bifrost
。Arm Mali-G72 was the second generation high performance GPU based on the Mali Bifrost architecture
- Bifrost彩虹桥内的代码:从驱动节点读取metrics,从metrics内根据耗时分别计算三大块的百分比——单位时间内工作时长对总时长的占比即使用率。
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
48static void kbase_pm_get_dvfs_utilisation_calc(struct kbase_device *kbdev,
ktime_t now)
{
ktime_t diff;
lockdep_assert_held(&kbdev->pm.backend.metrics.lock);
diff = ktime_sub(now, kbdev->pm.backend.metrics.time_period_start);
if (ktime_to_ns(diff) < 0)
return;
if (kbdev->pm.backend.metrics.gpu_active) {
u32 ns_time = (u32) (ktime_to_ns(diff) >> KBASE_PM_TIME_SHIFT);
kbdev->pm.backend.metrics.values.time_busy += ns_time;
if (kbdev->pm.backend.metrics.active_cl_ctx[0])
kbdev->pm.backend.metrics.values.busy_cl[0] += ns_time;
if (kbdev->pm.backend.metrics.active_cl_ctx[1])
kbdev->pm.backend.metrics.values.busy_cl[1] += ns_time;
if (kbdev->pm.backend.metrics.active_gl_ctx[0])
kbdev->pm.backend.metrics.values.busy_gl += ns_time;
if (kbdev->pm.backend.metrics.active_gl_ctx[1])
kbdev->pm.backend.metrics.values.busy_gl += ns_time;
if (kbdev->pm.backend.metrics.active_gl_ctx[2])
kbdev->pm.backend.metrics.values.busy_gl += ns_time;
} else {
kbdev->pm.backend.metrics.values.time_idle += (u32) (ktime_to_ns(diff)
>> KBASE_PM_TIME_SHIFT);
}
kbdev->pm.backend.metrics.time_period_start = now;
}
void kbase_pm_get_dvfs_metrics(struct kbase_device *kbdev,
struct kbasep_pm_metrics *last,
struct kbasep_pm_metrics *diff)
{
struct kbasep_pm_metrics *cur = &kbdev->pm.backend.metrics.values;
unsigned long flags;
spin_lock_irqsave(&kbdev->pm.backend.metrics.lock, flags);
kbase_pm_get_dvfs_utilisation_calc(kbdev, ktime_get());
memset(diff, 0, sizeof(*diff));
diff->time_busy = cur->time_busy - last->time_busy;
diff->time_idle = cur->time_idle - last->time_idle;
diff->busy_cl[0] = cur->busy_cl[0] - last->busy_cl[0];
diff->busy_cl[1] = cur->busy_cl[1] - last->busy_cl[1];
diff->busy_gl = cur->busy_gl - last->busy_gl;
*last = *cur;
spin_unlock_irqrestore(&kbdev->pm.backend.metrics.lock, flags);
} - 基于计数器(Counters)的性能观测/监控
这里原理实际上是驱动会将GPU工作状态以time_busy、time_idle的状态记录下来——它将GPU启动和停止的时间戳记录下来,并开放了dev接口允许外部读取。而dvfs除了将这些值读取出来,还进行了换算得到百分比作为占用率。在后端(backend,即GPU)记录工作状态,而由前端通过dvfs读取(dvfs读取backend数据并做了转换)。这种性能数据(metrics)记录方式属于打点计数——在某个时刻不处于空闲状态时则打点记录,达到一个触发点时,将所有点求和除以全体,即得到百分比。
进程的CPU(注意不是GPU)占用率正是采用了相同的原理——计数器,在Linux中,打点计数是四大性能观测基本原理之一。另外三个是跟踪(Tracing)、采样(Profiling)和监控(Monitoring)。在Kernel中,随处可见有大量的,专为性能观测、或实现功能的不完全是为了观测性能而设置了计数器。
其他
获取型号
- 通过sys节点(封装成一行命令了,实际原理是读sys节点下的gpuinfo)
1
find `find /sys/devices/platform -name *.mali` -name gpuinfo | xargs cat
1
Mali-G72 3 cores r0p3 0x6221
- 通过dumpsys
1
dumpsys SurfaceFlinger|grep GLES
1
GLES: ARM, Mali-G72 MP3, OpenGL ES 3.2 v1.r20p0-01rel0.a978c58d28f0d572d1d23ebd7162cf0d
读取当前频率
两个等价的节点(实际上是同一个节点):/sys/kernel/debug/ged/hal/current_freqency
、/d/ged/hal/current_freqency
读取CPU负载
1 | cat /sys/devices/system/cpu/rq-stats/cpu_normalized_load |
名词解释
名词 | English | 说明或/和例子 |
---|---|---|
占用率/使用率/利用率 | utilization | GPU utilization |
ged | GPU Extension Device | |
指标 | metrics |