2026年8月18日
Android 通过 ADB 获取 GPU / NPU / CPU 当前使用率:命令与厂商节点全解
Android 上的性能工作,成败都押在一个问题上:**活儿到底是哪块芯片在干?**你以为跑在 NPU 上的模型,可能正静悄悄地回落到 CPU;卡顿的屏幕可能是 GPU 饿着了,也可能是 CPU 拖死了——症状一样,药方相反。而答案很少在文档写的地方等着你,因为 Android 的硬件版图按 SoC 厂商碎片化分布,每家暴露性能计数器的方式各不相同——或者压根不暴露。
这篇文章就是”纯 ADB 能读到什么”的地图,尽量不 root、不装厂商 SDK。CPU 是成熟且标准化的部分:dumpsys cpuinfo、top、/proc/stat 在几乎所有设备上都能用。GPU 是碎片化的起点——高通 Adreno、ARM Mali、通用 devfreq 接口各有各的 sysfs 节点——三套我们全讲。NPU 是这篇指南里最需要坦诚的部分:没有标准接口、多数厂商什么都不暴露,实践路线是间接信号加专用 Profiler。下文所有命令都走 adb shell,所以 USB 也好、Wi-Fi 也好,坐在工位上对着量产真机,全都成立。
准备:USB 调试与设备确认#
手机上:设置 → 关于手机 → 连点七次版本号解锁开发者选项,然后设置 → 系统 → 开发者选项 → 打开USB 调试。插上 USB 线,设备会弹出授权确认——接受你这台电脑的 RSA 指纹,否则后续每条命令都会撞在”unauthorized”上。
确认连接:
adb devices
正常的一行长这样:ZX1G22B7QK device。显示 unauthorized 就去看手机、点允许;列表为空的话,两大元凶是只能充电的 USB 线(没有数据线芯——多得让人抓狂)和 PC 侧驱动问题。
无线场景(Android 11+):开发者选项 → 无线调试 → 使用配对码配对设备,屏幕上会给出 IP、端口和六位配对码:
adb pair 192.168.1.42:37851 # 然后按提示输入配对码
adb connect 192.168.1.42:35577 # 用主界面显示的端口,不是配对端口
配对端口和连接端口是两个不同的数字——配对弹窗一个、无线调试主界面一个,别搞混。连上之后,下文所有命令走 Wi-Fi 的表现和 USB 完全一致。
CPU 使用率:最成熟的部分#
三个工具、三种高度:top 用来实时揪进程,dumpsys cpuinfo 拿可信的聚合数字,/proc/stat 自己动手算整机使用率。
top:实时进程视图#
adb shell top -m 10
-m 10 只显示按 CPU 排序的前 10 个进程;再加 -n 1 输出一次快照就退出(否则 top 会一直刷新——交互时你要的正是这个,脚本里要的恰恰不是),-d 5 把刷新间隔设成 5 秒。值得盯的列:%CPU(按进程计,可以超过 100%——它是各核心的总和,8 核设备上的 400% 意味着半台机器在干活)、VIRT/RES(内存)、线程数。“现在是什么在吃我的电”这类问题,就靠它。
两个限制要先知道。Android 的 top 在不同版本上旗标不一样(老的认 -m,新的对 -n/-d 有不同语义——某个旗标报错就跑一次 top --help,读一读你这台设备的方言)。另外部分内核对内核态时间的按进程归账比较粗放,把 top 的数字当线索用,然后用 dumpsys 坐实。
dumpsys cpuinfo:可以写进报告的数字#
adb shell dumpsys cpuinfo
它从内核的记账里取滚动时间窗内的 CPU 时间,按进程打印用户态/内核态拆分——还有一个对照核心数折算的 TOTAL 行。它比单次 top 快照可信,因为它在窗口上取平均而不是抽一个瞬间,按进程的归账也来自平台自家工具用的同一批计数器。当你要一个可复现的数字写进 bug 报告——“我们的应用在设备 X 上空闲时占 3% CPU”——命令就是它。
/proc/stat:自己算整机使用率#
要整机使用率的最终事实——包括 top 归不进任何进程的那部分内核工作——读内核的累计计数器:
adb shell cat /proc/stat
第一行 cpu 是所有核心的合计;后面的 cpu0…cpuN 是逐核。每行的前四个数依次是:user、nice、system、idle(内核版本会在后面追加更多字段——irq、softirq、steal——算不算都行,前后一致即可)。
这些是开机以来累计的 jiffy 数,所以单次读数说明不了当前速率。使用率要从两次读数的差值里算:
busy = (user2 - user1) + (nice2 - nice1) + (system2 - system1)
total = busy + (idle2 - idle1)
utilization% = 100 * busy / total
读一次,等一个固定间隔(比如 5 秒),再读一次,配对字段相减,比值就是恰好这个窗口内的平均使用率。两个实践提醒:如果内核报告了附加字段(irq、softirq、steal),要把它们计入 total——那也是 CPU 没闲着的时间;另外记住结果是平均值——5 秒窗口里一次 200 ms 的 100% 尖峰,读出来只有 4%。逐核使用率就是同一套算术作用在 cpu0…cpuN 行上,而且值得做:Android 按核心调度线程,“大核打满、小核全闲”和”均匀摊开”是完全不同的两个诊断结论。
锁定单进程:top -p 与查 PID#
adb shell pidof com.example.app
adb shell ps -A | grep example
ps -A 列出全部进程,grep 收窄。老的 Android 构建上裸 ps 就已经列全部——-A 是在任何现代设备上都成立的 POSIX 写法。拿到 PID 之后:
adb shell top -p 12345 -d 2 -n 3
间隔 2 秒、采样 3 次、只看一个进程。配合文末的循环技巧,就能对单个进程连续记录几分钟。
逐核频率#
adb shell cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
频率单位是 kHz(1900800 就是 1.9 GHz)。通配符写法一次读所有核:
adb shell 'for c in /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq; do echo "$c: $(cat $c)"; done'
频率和使用率并排看,才能区分”忙”和”忙且在加速”:最高频率上的 30% 占用是散热或调度问题;待机频率上的 30% 是一台安静的机器。同一批目录里还有 scaling_max_freq 和 cpuinfo_max_freq,分别给出每个核当前被允许的上限和硬件极限——两者分道扬镳的时刻就是温控降频的时刻,也正是你最该来读它们的时候。
GPU 使用率:碎片化的部分#
没有 dumpsys gpuinfo 这种东西。GPU 计数器住在各厂商不同的 sysfs 节点里,因为每家的驱动各自暴露各自的仪表。三个家族覆盖市面上几乎所有设备。
高通 Adreno:kgsl 节点#
高通的 Adreno 驱动通过 KGSL 接口暴露自己,绝大多数骁龙设备上:
adb shell cat /sys/class/kgsl/kgsl-3d0/gpubusy
输出两个数,例如:
43 128
第一个是 busy tick,第二个是窗口内的总 tick——使用率就是二者的比值:43/128 ≈ 34%。这就是 Adreno 的标准 GPU 使用率读数,骁龙设备上各种系统监视器读的也是它。
频率和型号就在隔壁:
adb shell cat /sys/class/kgsl/kgsl-3d0/gpuclk
adb shell cat /sys/class/kgsl/kgsl-3d0/max_gpuclk
adb shell cat /sys/class/kgsl/kgsl-3d0/gpu_model
部分固件构建会改名或挪位置(devfreq/gpuclk、clock_mhz);哪条路径报错,就 ls /sys/class/kgsl/kgsl-3d0/ 看看你的设备到底提供什么——这个习惯在本节全文都管用,因为 sysfs 布局连同一厂商内部都随内核版本漂移。
ARM Mali:随设备而定的节点#
Mali(联发科、Exynos、麒麟,以及不少其他平台)通过设备节点暴露计数器,但路径在驱动世代之间搬过家。两个地方找:
adb shell cat /sys/class/misc/mali0/device/utilisation
adb shell ls /sys/class/misc/mali0/device/devfreq/
提供 utilisation 的内核会直接打印一个现成百分比。更普适的是,Mali 驱动会向内核的 devfreq 框架注册——那是频率可调设备的标准接口——它的目录里就有计数器(见下)。
通用 devfreq 接口:多数 SoC 都认#
devfreq 是内核的通用频率调节框架,几乎每一家现代 SoC 的 GPU 都在它那里注册——这也让 devfreq 成了跨厂商最可移植的路子:
adb shell ls /sys/class/devfreq/
你会看到以硬件命名的设备节点,比如 1c00000.qcom,kgsl-3d0(Adreno)、ff9a0000.gpu(瑞芯微 Mali)、13000000.mali(联发科)。按名字里的 mali、kgsl、gpu 认出 GPU,然后读它的目录:
adb shell cat /sys/class/devfreq/ff9a0000.gpu/cur_freq
adb shell cat /sys/class/devfreq/ff9a0000.gpu/available_frequencies
adb shell cat /sys/class/devfreq/ff9a0000.gpu/load
cur_freq 是当前频率;available_frequencies 列出 GPU 能爬的频率梯子;load(部分内核里是 governor 子目录下的 busy_time/total_time 一对)就是使用率。拿到 busy_time 和 total_time 时,相邻两次读数的比值——按内核实现或是清零重计或是持续累加——就是使用率;和 /proc/stat 一样的两次读数取差值逻辑在这里同样适用,因为多数实现里它们同样是累计计数器。
dumpsys gfxinfo:帧统计,不是使用率#
adb shell dumpsys gfxinfo com.example.app framestats
Android 6.0+ 提供逐应用的帧统计:渲染耗时分布、掉帧计数,framestats 还给逐帧时间戳,精度足够剖析渲染管线。它不是 GPU 使用率——帧慢可能因为 CPU 侧的布局计算、shader 编译,也可能因为 GPU 打满——但它是结果指标:用户感受到的是帧时间,不是使用率。纯 ADB 能做的最强诊断动作,就是把 gfxinfo 和 GPU busy 节点放在一起读:帧慢加 GPU 闲,指向 CPU/布局;帧慢加 GPU 饱和,指向 shader 或分辨率。
NPU 使用率:需要坦诚的部分#
故事到这里开始变得不舒服,但装作没有这回事只会让这篇指南失去价值。**Android 没有 NPU 使用率的标准接口。**NPU(或 DSP,或任何营销名义下的”AI 加速器”)是厂商私有的芯片加厂商私有的驱动,而厂商们不通过 sysfs 或 dumpsys 暴露使用率计数器:
- 高通 Hexagon DSP(跑大部分 QNN 推理的 cDSP):没有公开的使用率节点。部分内核在
cdsp/dspclk路径下露出一些时钟相关条目,但绝大多数量产设备什么都不给读。 - 联发科 APU:没有公开接口。计数器存在——联发科自家的工具读得到——但没有开放给第三方走 ADB 读。
- 三星 NPU(Exynos 的 NPU 栈):同样关闭。
- 华为达芬奇 NPU:没有公开的计数器接口。
结论就是:纯 ADB 在任何量产设备上都读不到 NPU 使用率。能做的是用间接信号推理,而这些信号的信息量其实不小:
- 推理期间的 CPU 占用(跑负载时看
dumpsys cpuinfo):模型运行时 CPU 持续高企,说明推理跑在 CPU 上——回落路径——而不是你瞄准的加速器。“为什么我的模型这么慢”的头号原因就是回落到 CPU,而 CPU 占用正是它最干净的指纹。 - 推理期间的 GPU busy(上文节点):模型运行时 Adreno/Mali 占用高,说明模型跑在 GPU delegate 上,不在 NPU 上。
- native 内存增长(
adb shell dumpsys meminfo <package>):加速器运行时真正调用硬件时会分配大块 DMA/ion 缓冲;内存曲线蹿到几百 MB 是”NPU 路径被启用”的软证据——不过它证明的是推理上下文的分配,不是持续的使用率。
需要真计数器的时候,就要请出厂商的 Profiler——这里点名,方便你知道去搜什么。Android GPU Inspector(AGI),谷歌官方的 Profiler,在受支持的 Adreno 和 Mali 设备子集上能读 GPU 内部状态和帧时序。Snapdragon Profiler 是高通的工具,在骁龙平台上能看见 Hexagon/cDSP 的活动。ARM Streamline 覆盖 Mali 系 SoC。要吃透的模式是:这些工具靠安装特权组件或使用厂商私有接口,才够到了纯 ADB 够不到的计数器——碎片化不是等着被绕过的疏忽,它就是当前生态的形状。
组合监控:采样循环与记录#
单次读取回答”它现在在干嘛”。性能问题要的是趋势,而 adb shell 给你的是标准 shell 工具,搭起来就是。
一行监视器——每秒读一次节点、永不停止:
adb shell 'while true; do cat /sys/class/kgsl/kgsl-3d0/gpubusy; sleep 1; done'
单引号里的所有内容都在设备上执行;sleep 1 定节奏,把 cat 的目标一换,它就变成了 CPU 频率监视器或任何别的东西。toybox 里带 watch 的设备(很多都带)可以更直接:
adb shell 'watch -n 1 cat /sys/class/kgsl/kgsl-3d0/gpubusy'
带时间戳、重定向到 PC 上的文件,就是一份能画图的日志:
adb shell 'while true; do echo "$(date +%H:%M:%S) $(cat /sys/class/kgsl/kgsl-3d0/gpubusy)"; sleep 2; done' > gpu-log.txt
注意引号的分工:while 循环在设备上跑,重定向发生在 PC 上,文件落在你电脑里,省掉一步 pull。推理基准测试的实用工作流:起一个记录 GPU busy 的日志循环、一个记录 /proc/stat 的,跑模型 60 秒,两个都停——并排的两列数据直接告诉你这颗模型跑在哪块芯片上、跑得多满。想让时间戳由设备计算(不受 USB 延迟抖动影响),就像上面这样把 date 放进循环里,而不是在 PC 侧补打。
FAQ#
我的设备上没有 /sys/class/kgsl 目录,为什么?#
因为它不是高通设备。KGSL 是 Adreno 驱动的接口,只存在于骁龙 SoC 上,别处没有。联发科或 Exynos 设备直接去列 devfreq(ls /sys/class/devfreq/),找名字里带 mali 或 gpu 的节点,或者查 GPU 一节里的 Mali 路径。近十年造的每一颗 GPU 都在 devfreq 注册——这就是为什么在不熟悉的硬件上,devfreq 是可移植的第一动作。
读这些需要 root 吗?#
多数不需要,但要按节点单独说。dumpsys cpuinfo、top、/proc/stat、dumpsys gfxinfo、dumpsys meminfo 在任何设备上免 root 可用——它们就是标准的调试面。sysfs 节点则由内核构建者逐个定权限:kgsl 的 gpubusy 在多数量产骁龙上全局可读,一些 devfreq 条目被收紧,个别厂商计数器仅 root 可读。你这台设备的策略只能实测:cat 一下,空结果或权限错误就说明问题。为了读一个计数器去 root 几乎从来不值——dumpsys 一族加上全局可读的那批 sysfs 节点,覆盖了真实问题里的绝大多数。
模型在跑,使用率读数却是 0,什么情况?#
绝大多数时候:模型没在你以为的地方执行。推理期间 GPU busy 为 0,说明 GPU delegate 没有接上——运行时回落到了 CPU(看 dumpsys cpuinfo:推理时 CPU 高企即可确认)——而 NPU 没有可读计数器这件事,意味着你无法直接区分”在 NPU 上”和”在某条没有计数器的路径上”。这正是间接信号那一节存在的意义:CPU 活动、GPU 活动、native 内存增长,在无法直读时靠这三样三角定位答案。推理运行时里的 delegate 配置(NNAPI/GPU/CPU 的选择)是第一个要核对的东西。
帧率 vs GPU 使用率,区别在哪?#
帧率是产出的速率:显示管线每秒交出多少帧。GPU 使用率是 GPU 的算力资源有多忙——忙不一定有产出。两者相关但不可互换:一台设备可以用近乎空闲的 GPU 渲染 60 fps(场景简单),也可以 GPU 90% 打满还够不着 60 fps(场景重);GPU 使用率可以居高不下的同时帧率往下掉——这正是 GPU 瓶颈型卡顿的标准指纹。帧率告诉你用户经历了什么;使用率告诉你该怪哪个部件、还有没有余量。dumpsys gfxinfo 量前者,sysfs 节点量后者,诊断要两个一起读:“GPU 太慢”和”GPU 被 CPU 饿着”这两种病因,帧率一样,使用率签名截然相反。
结语#
三块硬件的”可读程度”天差地别,把预期对齐到这个现实,本身就是这项技能的大半。CPU:标准化、归账清晰,dumpsys cpuinfo 加 /proc/stat 差值算术,在任何设备上都能拿到可发表级别的数字。GPU:碎片化但读得到——记住三套接口(Adreno 的 kgsl、Mali 的节点、万金油 devfreq),再记住路径 404 时先 ls 的习惯。NPU:没有标准接口,专业的做法不是许愿,而是三角定位——CPU 和 GPU 使用率当回落探测器,内存增长当接入证据,真需要加速器计数器时上厂商 Profiler。用一条带日志的采样循环把这些串起来,一根 USB 线就是一间像模像样的性能实验室。