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负载的方法。介绍了通过watchawk自动周期性间隔打印GPU利用率的命令。

读取负载

  • 读取GPU负载;输出三列数据分别为Loading、Block、Idle
    cat /sys/kernel/debug/ged/hal/gpu_utilization

  • 其中Loading、Blocking、Idle可以分别从独立的节点中单独读取

    1
    2
    3
    4
    5
    cat /sys/module/ged/parameters/gpu_loading

    cat /sys/module/ged/parameters/gpu_block

    cat /sys/module/ged/parameters/gpu_idle
  • sys节点下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
    13
    static 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
    48
    static 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中,随处可见有大量的,专为性能观测、或实现功能的不完全是为了观测性能而设置了计数器。

其他

获取型号

  1. 通过sys节点(封装成一行命令了,实际原理是读sys节点下的gpuinfo)
    1
    find `find /sys/devices/platform -name *.mali` -name gpuinfo | xargs cat
    1
    Mali-G72 3 cores r0p3 0x6221
  2. 通过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
2
3
4
5
6
7
8
9
10
11
12
cat /sys/devices/system/cpu/rq-stats/cpu_normalized_load
cpu(0): load(rel/abs) = 1/0
cpu(1): load(rel/abs) = 7/2
cpu(2): load(rel/abs) = 4/1
cpu(3): load(rel/abs) = 7/2
cpu(4): load(rel/abs) = 0/0
cpu(5): load(rel/abs) = 0/0
cpu(6): load(rel/abs) = 0/0
cpu(7): load(rel/abs) = 0/0
htask_threshold=920, total_htask#=0
cluster0: current_htask#=0
cluster1: current_htask#=0

名词解释

名词 English 说明或/和例子
占用率/使用率/利用率 utilization GPU utilization
ged GPU Extension Device
指标 metrics

参考

  1. The seq interface
  2. ARM Developer Mali G72 Specifications