24、稳定性与压力测试:Monkey测试、CTS/VTS认证测试、长时间稳定性测试、Crash分析与解决

各位同学,今天我们来聊聊一个让所有嵌入式工程师又爱又恨的话题——稳定性测试。

说实话,我在做i.MX平台移植的头两年,最怕听到的就是测试同事喊“又Crash了”。那时候我就在想,为什么明明代码逻辑没问题,跑着跑着就挂了?后来我才明白,稳定性不是测出来的,是设计出来的。但话说回来,没有系统性的测试手段,你连问题在哪都找不到。

24.1 稳定性测试的整体框架

先给大家画个图,看看我们今天要聊的这几个测试维度之间的关系。

Android稳定性测试体系 Monkey测试 随机事件流压力 CTS/VTS认证 兼容性合规验证 长时间稳定性测试 72h+ 持续运行 Crash分析与解决 tombstone/dmesg 目标:系统不Crash、不死机、不ANR

你看,这四个维度其实是层层递进的。Monkey测试帮你快速暴露问题,CTS/VTS保证兼容性,长时间测试验证耐力,最后Crash分析是收尾的硬功夫。我个人的习惯是,先跑Monkey,再跑CTS,最后挂机跑长时间。

24.2 Monkey测试:暴力但有效

Monkey测试,说白了就是让一只猴子在手机上乱点。Android自带的adb monkey工具会生成随机事件流——触摸、滑动、按键、音量调节,什么都来。

我在项目中遇到过最夸张的一次,Monkey跑了不到5分钟,系统直接黑屏重启。后来一查,是某个GPU驱动在特定频率下触发了死锁。

24.2.1 基本用法

# 最简单的Monkey测试,发送500个随机事件
adb shell monkey -p com.android.car 500

# 带详细日志,方便定位
adb shell monkey -p com.android.car -v -v -v 10000

# 指定事件间隔(毫秒),模拟真实操作节奏
adb shell monkey -p com.android.car --throttle 200 5000
我的小技巧: 别一上来就跑10万事件。先跑1000个看看水温,没问题再逐步加量。我曾经见过有人直接跑100万,结果系统卡死,日志都来不及抓。

24.2.2 关键参数详解

参数 作用 推荐值
-p 指定包名,只测试目标应用 com.android.car / com.android.systemui
--throttle 事件间隔(毫秒) 200-500
-s 随机种子,复现相同序列 固定值如12345
--pct-touch 触摸事件百分比 50-70%
--pct-motion 手势事件百分比 10-20%
--pct-appswitch 应用切换百分比 5-10%
注意: 在Automotive系统上,千万别把--pct-syskeys设太高。Home键、Back键在车载场景下行为特殊,按多了可能导致SystemUI崩溃。我曾经吃过这个亏,一晚上跑了8次,每次都在30分钟左右SystemUI挂掉。

24.2.3 车载场景的Monkey定制

车载系统和手机不一样。你不能让Monkey乱切应用,因为中控屏上同时显示导航、音乐、空调三个窗口。我建议这样配置:

# 针对车载Launcher的Monkey测试
adb shell monkey -p com.android.car.launcher \
  --pct-touch 60 \
  --pct-motion 15 \
  --pct-appswitch 5 \
  --pct-nav 5 \
  --pct-majornav 5 \
  --pct-syskeys 2 \
  --pct-anyevent 8 \
  --throttle 300 \
  -s 20240101 \
  50000

为什么这么配?你想想看,用户在车上主要就是点按和滑动,很少会疯狂按音量键。所以触摸事件占比要高,系统按键要压低。

24.3 CTS/VTS认证测试

CTS(Compatibility Test Suite)和VTS(Vendor Test Suite)是Google的官方认证测试。说白了,就是Google给你发的一张“合格证”。没过CTS,你的系统就不能叫Android。

我记得第一次跑CTS的时候,整整跑了3天3夜。中间断了一次电,全部重来。那种感觉,嗯,不想再体验第二次。

24.3.1 CTS测试要点

  • 测试范围: 涵盖所有Android API的兼容性,包括多媒体、传感器、网络、存储等
  • 运行方式: 通过cts-tradefed命令行工具执行
  • 结果判定: 所有测试项必须为PASS,FAIL项必须提供合理解释
# 启动CTS测试
./cts-tradefed
run cts --plan CTS

# 只测试某个模块,比如车载相关
run cts -m CtsCarTestCases

# 查看结果
l r  # 列出所有测试结果
核心经验: CTS失败90%的原因不是代码逻辑错,而是配置问题。比如某个权限没声明、某个feature flag没打开、或者so库版本不匹配。别一上来就改代码,先检查manifest和BoardConfig.mk。

24.3.2 VTS测试要点

VTS主要测HAL层和内核接口。对于i.MX平台来说,VTS比CTS更容易出问题。为什么?因为HAL层直接跟硬件打交道,而i.MX的某些外设驱动可能没完全适配。

# 运行VTS
./vts-tradefed
run vts -m VtsHalCameraProviderV2_4TargetTest

# 查看详细失败日志
run vts -m VtsHalCameraProviderV2_4TargetTest --log-level VERBOSE
常见VTS失败项 可能原因 解决方案
Camera HAL测试失败 ISP驱动未正确初始化 检查dmesg中camera相关错误,确认v4l2节点存在
Audio HAL测试失败 ALSA配置与HAL期望不匹配 对比audio_policy_configuration.xml和实际声卡设备
Sensor HAL测试失败 传感器数据上报频率不达标 检查i2c通信速率,可能需要调整设备树中的时钟配置

24.4 长时间稳定性测试

Monkey测试跑几个小时,CTS跑几天,但长时间稳定性测试——我建议至少跑72小时。为什么是72小时?因为很多内存泄漏问题,48小时内才会暴露出来。

我曾经遇到过一个问题:系统运行到第50个小时左右,蓝牙开始断连。查了三天,最后发现是蓝牙协议栈里有个定时器没释放,每连接一次就泄漏4字节。50小时刚好把内存池耗尽。

24.4.1 测试方案设计

# 长时间稳定性测试脚本(简化版)
#!/bin/bash

# 循环执行测试场景
for i in {1..1000}; do
  echo "=== Round $i ==="
  
  # 1. 启动导航
  am start -n com.android.car.navigation/.MainActivity
  sleep 30
  
  # 2. 播放音乐
  am start -n com.android.car.media/.MainActivity
  sleep 60
  
  # 3. 调节空调
  am broadcast -a com.android.car.action.SET_TEMP --ei temp 24
  sleep 10
  
  # 4. 检查系统状态
  dumpsys meminfo | grep "Total RAM"
  cat /proc/meminfo | grep MemAvailable
  
  # 5. 抓取关键日志
  logcat -d | grep -E "Error|Fatal|ANR|CRASH" >> /data/stable_test.log
  
  sleep 120
done
我的监测清单:
  • 每30分钟记录一次可用内存,看是否有持续下降趋势
  • 每1小时检查一次dmesg,看是否有驱动报错
  • 每2小时执行一次top,看哪个进程CPU占用异常
  • 每6小时重启一次蓝牙/WiFi,模拟用户日常使用

24.4.2 自动化监控

手动盯着跑72小时不现实。我写了个简单的Python脚本,配合adb做自动化监控:

import subprocess
import time
import datetime

def check_system_health():
    # 检查是否ANR
    result = subprocess.run(
        ['adb', 'shell', 'logcat', '-d', '-b', 'events', '|', 'grep', 'ANR'],
        capture_output=True, text=True
    )
    if result.stdout:
        print(f"[{datetime.now()}] ANR detected!")
        # 触发dump
        subprocess.run(['adb', 'shell', 'dumpsys', 'dropbox', '--print', 'data_app_anr'])
    
    # 检查内存
    result = subprocess.run(
        ['adb', 'shell', 'cat', '/proc/meminfo'],
        capture_output=True, text=True
    )
    for line in result.stdout.split('\n'):
        if 'MemAvailable' in line:
            print(f"[{datetime.now()}] {line.strip()}")

while True:
    check_system_health()
    time.sleep(300)  # 每5分钟检查一次

24.5 Crash分析与解决

好了,前面跑了那么多测试,现在该处理问题了。Crash分析是嵌入式工程师的必修课,也是最能体现功力的地方。

24.5.1 抓取Crash信息

当系统Crash时,Android会生成tombstone文件。这是我们的第一手资料。

# 查看tombstone文件
adb shell ls /data/tombstones/
adb shell cat /data/tombstones/tombstone_00

# 抓取完整logcat
adb logcat -b crash -d > crash_log.txt
adb logcat -b system -d > system_log.txt

# 抓取kernel日志
adb shell dmesg > dmesg_log.txt

24.5.2 常见Crash类型与定位

Crash类型 典型特征 定位方法
NullPointerException Java层空指针,常见于UI线程 查看logcat中的stack trace,定位到具体行号
Native Crash (SIGSEGV) tombstone中显示signal 11 使用addr2line将PC地址转换为源码位置
ANR (Application Not Responding) logcat中显示"ANR in ..." 查看/data/anr/traces.txt中的线程堆栈
Kernel Panic 系统完全死机,串口输出panic信息 分析dmesg最后几行,通常有Call Trace

24.5.3 实战:分析一个Native Crash

假设我们拿到一个tombstone,关键信息如下:

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'android/i.mx8qm/i.mx8qm:12/SP1A.210812.001/123456:userdebug/test-keys'
Revision: '0'
ABI: 'arm64'
Timestamp: 2024-01-15 14:23:45.123456789+0800
pid: 1234, tid: 1235, name: HwBinder:1234_1
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
    x0  0000000000000000  x1  0000007f8c123456  x2  0000000000000001
    x3  0000000000000000  x4  0000000000000000  x5  0000000000000000
    ...
    pc  0000007f8b123abc  /vendor/lib64/hw/android.hardware.camera.provider@2.4-impl.so
    lr  0000007f8b123a98  /vendor/lib64/hw/android.hardware.camera.provider@2.4-impl.so

看到fault addr 0x0了吗?这是典型的空指针解引用。PC指针指向了camera.provider的so库。接下来我们用addr2line定位:

# 将PC地址转换为源码位置
arm-linux-androideabi-addr2line -e android.hardware.camera.provider@2.4-impl.so 0x7f8b123abc

# 输出结果
# /home/user/android/vendor/nxp/camera/impl/CameraProvider.cpp:256

打开CameraProvider.cpp第256行,发现是一个指针在调用前没有判空。嗯,典型的HAL层bug。

避坑指南: 我曾经遇到过一个情况,addr2line给出的行号是错的。为什么?因为so库是debug版本,但tombstone里记录的是release版本的地址。所以一定要确保你分析的so库和手机上跑的是同一个版本。否则你会浪费一整天。

24.5.4 系统性Crash预防

光会分析Crash还不够,更重要的是预防。我总结了几个原则:

  • 所有HAL接口必须判空: 硬件可能在任何时候断开连接
  • Binder调用必须加try-catch: 远程服务可能挂掉
  • 异步回调必须检查生命周期: Activity可能已经销毁
  • 日志要打全: 关键路径上加上Log.d,方便事后追溯

最后说一句,稳定性测试不是一锤子买卖。每次合入新代码,都应该跑一轮Monkey和CTS。我团队里有个规矩:谁合入的代码导致CTS失败,谁负责修复并请全组喝奶茶。嗯,效果还不错。


公众号:蓝海资料掘金营,微信deep3321