Android 7系统异常问题排查(八)系统追踪—Trace机制与性能诊断

系列目录 :第一篇:异常机制全景图 | [第二篇:Kernel Panic 与系统重启](#第二篇:Kernel Panic 与系统重启) | [第三篇:Tombstone 机制](#第三篇:Tombstone 机制) | [第四篇:System Server Watchdog](#第四篇:System Server Watchdog) | [第五篇:System Server 崩溃](#第五篇:System Server 崩溃) | [第六篇:ANR 机制](#第六篇:ANR 机制) | [第七篇:Java 层崩溃](#第七篇:Java 层崩溃) | [第八篇:Trace 机制](#第八篇:Trace 机制) | 第九篇:日志系统 | 第十篇:实战方法论


一、为什么需要 Trace 机制?

你可能遇到过这些场景:

  • 用户反馈 App 滑动卡顿,但 logcat 里没有任何异常日志
  • 系统启动慢,但不知道卡在哪个阶段
  • 掉帧严重,但无法定位是渲染慢还是 CPU 调度问题
  • 系统服务调用响应慢,但不知道延迟发生在哪一端

前几篇文章介绍的 tombstone、ANR、Watchdog 都是事后分析------异常已经发生,我们只能从日志中还原现场。但很多问题(如系统卡顿、掉帧、响应慢)并不会触发明确的异常,却严重影响用户体验。Trace 机制正是为了解决这类"没有硬错误,但有严重问题"的场景。

Trace 能回答的问题

问题类型 Trace 能提供的信息
UI 卡顿 每个帧的渲染耗时、哪些线程在竞争 CPU
启动慢 启动过程中各阶段耗时、Binder 调用延迟
掉帧 SurfaceFlinger 合成时间线、VSYNC 信号
系统服务慢 Binder 调用耗时、锁竞争、线程调度
功耗异常 CPU 频率分布、各进程的 CPU 时间片
IO 阻塞 磁盘读写耗时、I/O 调度延迟

二、Trace 体系全景

在 AOSP 7 中,trace 体系分为三层:

复制代码
┌──────────────────────────────────────────────┐
│  用户态工具层                                 │
│  ├─ systrace (systrace.py)                   │
│  ├─ atrace 命令行工具                         │
│  └─ perfetto(AOSP 7 后引入,暂不展开)        │
├──────────────────────────────────────────────┤
│  Framework 打点层                            │
│  ├─ Trace.java (android.os.Trace)            │
│  ├─ ATrace (native 层)                       │
│  └─ 各系统服务的 trace tag                   │
├──────────────────────────────────────────────┤
│  内核追踪层                                   │
│  ├─ ftrace (tracefs)                         │
│  ├─ tracepoint / kprobe / uprobe            │
│  └─ trace_marker (用户态写入内核 trace buffer) │
└──────────────────────────────────────────────┘

三、ftrace ------ 内核追踪基础

3.1 ftrace 是什么

ftrace 是 Linux 内核内置的追踪框架,通过 tracefs 文件系统暴露接口。它是 Android 所有 trace 工具的底层基础。

3.2 挂载与结构

bash 复制代码
# Android 中 ftrace 挂载在
mount -t tracefs none /sys/kernel/tracing
# 或旧版本
mount -t debugfs none /sys/kernel/debug

# 关键目录结构
/sys/kernel/tracing/
├─ available_tracers    # 可用的追踪器
├─ current_tracer       # 当前使用的追踪器
├─ tracing_on           # 1=开启 0=暂停
├─ trace                # 追踪结果输出
├─ trace_marker         # 用户态写入标记
├─ events/              # tracepoint 事件
│   ├─ sched/           # 调度事件
│   ├─ binder/          # Binder 事件
│   ├─ power/           # 电源管理事件
│   └─ ...
└─ options/             # 追踪选项

3.3 trace_marker 用户态打点

这是连接用户态和内核态的重要桥梁------用户态通过向 trace_marker 文件写入特定格式的数据,将标记注入内核 trace buffer:

c 复制代码
// 用户态写入 trace 标记
// frameworks/native/libs/utils/Trace.cpp
static void traceInit() {
    // 打开 trace_marker 文件
    atrace_marker_fd = open("/sys/kernel/tracing/trace_marker", O_WRONLY);
}

void ATrace_begin(const char* name) {
    // 写入 'B|<pid>|<name>' 到 trace_marker
    char buf[1024];
    snprintf(buf, sizeof(buf), "B|%d|%s", getpid(), name);
    write(atrace_marker_fd, buf, strlen(buf));
}

void ATrace_end() {
    // 写入 'E' 到 trace_marker
    write(atrace_marker_fd, "E", 1);
}

关键设计 :通过简单的文件写入操作(write()),用户态代码就能将打点信息注入内核 trace buffer,实现跨进程的精确时间同步。格式 B|<pid>|<name> 中的 B 表示 begin,E 表示 end。

3.4 关键 tracepoint

类别 tracepoint 用途
调度 sched/sched_switch 线程切换,找出 CPU 竞争
调度 sched/sched_wakeup 线程被唤醒,追踪唤醒延迟
频率 power/cpu_frequency CPU 频率变化,分析性能瓶颈
Binder binder/binder_transaction Binder 调用耗时
Binder binder/binder_transaction_received Binder 接收时间
图形 mdss/mdp_vsync_* 显示 VSYNC 信号
IO block/block_rq_issue IO 请求发起
内存 kmem/kmalloc 内存分配追踪

四、atrace ------ Android 用户态追踪

4.1 atrace 是什么

atrace 是 Android 提供的命令行工具,封装了 ftrace 的配置和数据采集。

bash 复制代码
# 查看支持的 trace category
adb shell atrace --list_categories

# 典型输出:
 gfx - Graphics
 input - Input
 view - View System
 webview - WebView
 wm - Window Manager
 am - Activity Manager
 sm - Sync Manager
 audio - Audio
 video - Video
 camera - Camera
 hal - Hardware Modules
 app - Application
 res - Resource Loading
 dalvik - Dalvik VM
 rs - RenderScript
 bionic - Bionic C Library
 power - Power Management
 pm - Package Manager
 sched - CPU Scheduling
 freq - CPU Frequency
 idle - CPU Idle
 load - CPU Load
 disk - Disk I/O
 mmc - eMMC commands
 ...

4.2 常用 atrace 命令

bash 复制代码
# 基础用法:追踪 10 秒,抓取 sched 和 gfx 类别
adb shell atrace -t 10 sched gfx > trace.out

# 异步模式:后台启动追踪
adb shell atrace --async_start sched freq gfx
# ... 执行测试操作 ...
adb shell atrace --async_stop > trace.out

# 追加 App 自定义 trace(需要 App 代码中使用 Trace API)
adb shell atrace -t 10 -a com.example.app sched gfx

# 输出压缩格式(减少传输大小)
adb shell atrace -t 10 -z sched gfx > trace.out

五、systrace ------ 可视化分析

5.1 systrace 是什么

systrace 是 Android SDK 提供的 Python 脚本,能够:

  1. 调用 atrace 收集原始数据
  2. 将数据转换为 HTML 可视化报告
  3. 提供交互式分析界面
bash 复制代码
# 使用 systrace.py(位于 sdk/platform-tools/systrace/)
python systrace.py -t 10 -o trace.html sched freq gfx input view

5.2 Systrace 报告解读

报告分为多个 Section:

CPU 调度区域:

复制代码
每个 CPU 核心一行,显示在该核心上运行的线程:
CPU 0: [surfaceflinger] [app] [idle] [surfaceflinger] ...
CPU 1: [Binder:1234] [idle] [app] [Binder:1234] ...

解读:
- 线程名可以看出谁在占用 CPU
- 颜色块表示不同的线程
- 空白 = idle(CPU 空闲)

进程/线程区域:

复制代码
每个进程一行,显示该进程各线程的状态:
com.example.app (12345)
  └─ main (12345):  [RUNNING][SLEEPING][RUNNING][BLOCKED]...
  └─ RenderThread (12346): [RUNNING][RUNNING]...
  └─ Binder_1 (12347): [RUNNING]...

线程状态颜色:
- 绿色 = Running
- 蓝色 = Runnable(等待 CPU)
- 白色 = Sleeping
- 橙色 = Uninterruptible Sleep(IO 等待)
- 紫色 = Blocked(等待锁)

VSYNC 与渲染管线:

复制代码
VSYNC:  |   |   |   |   |   |   |
SurfaceFlinger:  ====  ====  ====
App Render:      ======  ======

每个竖线表示 VSYNC 信号,每个横条表示渲染操作所占的时间。理想情况下,App 的渲染操作应在 16.7ms(60fps)内完成。

Binder 调用:

复制代码
App → Binder Transaction → System Server
|---- request ----| |---- processing ----| |---- reply ----|

Binder 区域显示了跨进程调用的完整时间线,可以直观看出哪个方向的延迟最大。

5.3 关键快捷键

快捷键 功能
W/S 放大/缩小
A/D 左右平移
M 标记当前选择的时间范围
F 聚焦当前选择
1-4 切换不同细节级别
? 显示所有快捷键

六、API 打点:在代码中埋点

6.1 Java 层

源码路径 :frameworks/base/core/java/android/os/Trace.java

java 复制代码
public final class Trace {
    // ...
    public static final long TRACE_TAG_APP = 1L << 12;
    private static final int MAX_SECTION_NAME_LEN = 127;

    // 通过 native 方法写入 trace_marker
    private static native void nativeTraceBegin(long tag, String name);
    private static native void nativeTraceEnd(long tag);

    // App 开发者使用的便捷 API
    public static void beginSection(String sectionName) {
        if (isTagEnabled(TRACE_TAG_APP)) {
            if (sectionName.length() > MAX_SECTION_NAME_LEN) {
                throw new IllegalArgumentException("sectionName is too long");
            }
            nativeTraceBegin(TRACE_TAG_APP, sectionName);
        }
    }

    public static void endSection() {
        if (isTagEnabled(TRACE_TAG_APP)) {
            nativeTraceEnd(TRACE_TAG_APP);
        }
    }
}

关键设计 :beginSection/endSection 使用 TRACE_TAG_APP tag,需要 atrace 命令中加 -a <package> 参数才能采集到。MAX_SECTION_NAME_LEN 限制名称最长 127 字符。

使用示例:

java 复制代码
import android.os.Trace;

public void doHeavyWork() {
    Trace.beginSection("doHeavyWork");
    try {
        processData();
        Trace.beginSection("processData_sub");
        // 子步骤
        Trace.endSection();
    } finally {
        Trace.endSection();
    }
}

6.2 Native 层

cpp 复制代码
#include <utils/Trace.h>

void doHeavyWork() {
    ATRACE_BEGIN("doHeavyWork");
    // 耗时操作
    ATRACE_END();
}

七、结合异常定位的实战案例

案例 1:UI 卡顿分析

复制代码
问题:用户反馈 App 滑动卡顿

分析步骤:
1. 抓取 systrace:systrace.py -t 5 -o trace.html sched freq gfx input view
2. 打开 trace.html,找到 App 的 main 线程
3. 观察 VSYNC 信号和渲染完成时间
4. 发现:某个帧的渲染耗时 80ms(远超 16.7ms)
5. 放大该帧时间范围,发现 RenderThread 在等待 CPU
6. 查看 CPU 调度区域,发现同时有后台线程在密集计算
7. 结论:后台任务与渲染线程抢 CPU,导致掉帧
8. 修复:将后台任务移到 cpuset 后台组,降低优先级

案例 2:ANR 前兆分析

复制代码
问题:App 偶尔 ANR,但 traces.txt 中主线程没有明显阻塞

分析步骤:
1. 抓取 systrace + CPU 调度
2. 发现 ANR 发生前,所有 CPU 核心 100% 占用
3. 主线程处于 Runnable 状态(等待 CPU),而非 Blocked
4. 结论:系统整体 CPU 过载,主线程分不到 CPU 时间片
5. 修复:优化后台任务,降低 CPU 使用率

案例 3:System Server 慢

复制代码
问题:系统服务调用响应慢

分析步骤:
1. systrace 中开启 binder 类别
2. 观察 Binder 调用链路
3. 发现 AMS 的某个方法耗时 500ms
4. 进一步查看 AMS 线程的 trace
5. 发现 AMS 在等待 PackageManager 的锁
6. 结论:PackageManager 持有锁时间过长,导致连锁反应

八、使用技巧与最佳实践

8.1 选择合适的 trace 类别

bash 复制代码
# 性能分析(最小集)
sched freq

# UI 卡顿
sched freq gfx input view

# Binder 问题
sched freq binder

# 启动速度
sched freq am wm

# 内存问题
sched freq memreclaim

# 全盘分析(注意数据量)
sched freq gfx input view wm am binder disk

8.2 减小 trace 开销

复制代码
- 限制追踪时间:-t 5(5 秒通常足够)
- 只追踪必要的 App:-a com.example.app
- 使用压缩格式:-z
- 减少不相关的 category
- 在非关键路径上减少 Trace.beginSection/endSection 调用

8.3 脚本化收集

bash 复制代码
#!/bin/bash
# 一键收集异常现场的 trace 数据
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# 1. 抓取 atrace
adb shell atrace -t 10 -z sched freq gfx input view binder \
    > trace_${TIMESTAMP}.trace

# 2. 导出 logcat
adb logcat -d > logcat_${TIMESTAMP}.txt

# 3. 导出 dmesg
adb shell dmesg > dmesg_${TIMESTAMP}.txt

# 4. 导出 ANR traces
adb pull /data/anr/traces.txt traces_${TIMESTAMP}.txt

echo "All logs collected with timestamp: ${TIMESTAMP}"

九、总结

  1. Trace 是"事前监控"能力:不等异常发生,而是持续观察系统运行状态,发现性能瓶颈。

  2. 三层架构:ftrace(内核)→ atrace(Android 封装)→ systrace(可视化分析)。

  3. trace_marker 是用户态与内核态的桥梁 :通过 ATrace_begin/end 或 Trace.beginSection/endSection 将应用级打点注入内核 trace buffer。

  4. systrace HTML 报告是核心分析工具:按时间轴展示 CPU 调度、线程状态、Binder 调用、渲染管线。

  5. 结合异常定位:systrace 可以补充 ANR/Watchdog 分析中缺失的"时间线上下文",帮助判断是锁阻塞还是 CPU 竞争。

下一篇将介绍 Android 日志系统全景------从 logcat 到 bugreport。注意:新一代 trace 工具 perfetto 在 Android 10+ 中成为默认,AOSP 7 以 atrace/systrace 为主。


本文基于 AOSP 7(Android Nougat)源码编写。

相关推荐
三少爷的鞋11 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)15 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码17 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程17 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程20 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程21 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓1 天前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer