4、长时间运行测试:测试时长规划、压力场景设计(持续推理、数据流输入)、监控指标(内存、CPU、温度、功耗)与日志记录方法

TinyML 设备与云端 AI 推理的最大差异在于其部署环境的不可控性与资源受限性。一个在实验室环境下运行 10 分钟表现完美的模型,可能在连续工作 72 小时后因内存碎片化、温度漂移或看门狗误触发而崩溃。长时间运行测试(Soak Test / Endurance Test)的核心目的,是验证系统在持续负载下的稳定性、资源泄漏情况以及热/电参数的长期漂移趋势。本章将围绕测试时长如何科学规划、压力场景如何贴近真实部署、监控哪些关键指标以及如何建立有效的日志体系展开,帮助测试工程师构建一套可量化、可复现的长期稳定性验证方案。

长时间运行测试工程链路 ① 测试时长规划 · 目标时长推导 · 加速系数评估 · 循环周期设计 · 里程碑节点 ② 压力场景设计 · 持续推理模式 · 数据流输入模式 · 混合负载模式 · 边界触发模式 ③ 监控指标体系 · 内存 / CPU · 温度 / 功耗 · 推理延迟 / 准确率 · 外设状态 ④ 日志记录方法 · 分级日志策略 · 环形缓冲区 · 时间戳同步 · 崩溃转储 数据采集与反馈闭环 传感器数据注入 (真实/合成数据) 推理执行引擎 (TFLM/RTOS任务) 指标采集器 (内存/温度/功耗) 日志存储 (Flash/SD/串口) 分析 (上位机) 异常检测 → 自动重启/告警 → 回归验证 TP1 TP2 TP3

4.1 测试时长规划:从“拍脑袋”到工程化推导

长时间运行测试的时长不能简单设定为“跑 72 小时”或“跑一周”。科学的时长规划应基于产品部署场景、故障模式分析和统计置信度要求。首先需要明确测试的目标对象:是验证内存泄漏、验证热稳定性、验证看门狗可靠性,还是验证 Flash 磨损均衡?不同目标对应不同的最短测试时间。

核心原理:长时间运行测试的时间尺度应覆盖系统的最小“压力循环周期”的整数倍。例如,一个每 5 分钟执行一次数据采集+推理+上报的任务,其压力循环周期为 5 分钟。测试时长至少应包含 1000 个循环(约 83 小时),才能以较高置信度暴露与循环次数相关的资源泄漏问题。对于温度相关测试,时长需覆盖至少 3 个完整的“冷-热-冷”温度循环,且每个循环不少于 4 小时。

测试目标 最短时长建议 关键依据 加速方法
内存泄漏检测 ≥ 1000 个任务循环 每次循环泄漏 1KB,1000 次后泄漏 1MB 缩短任务周期,提高循环频率
热稳定性验证 ≥ 3 个温度循环(每循环 4-8h) 热膨胀/收缩导致的焊点应力累积 提高温度变化速率(但需注意冷凝风险)
看门狗/复位可靠性 ≥ 500 次异常注入+恢复 统计复位成功率的置信区间 通过软件注入故障,而非等待自然故障
Flash 磨损验证 写入量 ≥ 标称寿命的 10% 磨损均衡算法在低写入量下不触发 增大日志写入频率,使用磨损模拟

操作流程:第一步,梳理产品的典型任务时序图,确定最小压力循环周期;第二步,根据故障模式分析(FMEA)确定需要覆盖的失效类型;第三步,结合项目交付节点倒推测试窗口,必要时采用加速测试方法;第四步,在测试计划中设置里程碑检查点(如 24h、72h、168h),每个检查点执行一次完整的数据分析和中间评估,而非等到测试结束才看数据。

经验提醒:对于电池供电的 TinyML 设备,测试时长还应考虑电池充放电循环。建议至少覆盖 3 次完整的“满电-低电-充电-满电”循环,以验证电压波动对推理精度和存储写入的影响。

4.2 压力场景设计:持续推理与数据流输入的工程实现

压力场景的设计决定了测试的有效性。常见的误区是让设备以最高频率空转推理,但这并不能代表真实部署场景。TinyML 设备的压力应分为计算压力、IO 压力和存储压力三个维度,且需要组合施加。

持续推理模式:适用于唤醒词检测、振动监测等需要常开推理的场景。设计要点包括:推理频率设置为标称最大值的 1.2 倍(留出裕量);输入数据使用真实采集的“困难样本”库(如噪声环境下的语音、强振动信号),而非随机噪声;每次推理后执行一次结果校验(与预计算的金标准对比),确保精度不因长时间运行而漂移。

数据流输入模式:适用于传感器数据采集、事件触发型设备。设计要点包括:模拟真实的数据到达间隔(如泊松分布或固定周期+突发);在数据流中混入异常帧(如损坏的 I2C 读取、超范围值),验证错误处理路径的稳定性;定期切换数据源通道(如从传感器 A 切换到传感器 B),暴露多路复用相关的资源问题。

// 压力场景配置示例(伪代码)
pressure_scenario = {
  "mode": "mixed",  // 混合模式:推理+数据流
  "inference": {
    "frequency_hz": 12,  // 标称 10Hz,加压 20%
    "input_source": "hard_samples.bin",
    "verify_output": true
  },
  "data_stream": {
    "arrival_model": "poisson",
    "avg_interval_ms": 500,
    "burst_probability": 0.1,
    "burst_size": [5, 10],
    "inject_corrupt_frames": true,
    "corrupt_rate": 0.01
  },
  "storage": {
    "log_write_interval_s": 30,
    "flash_erase_trigger": "on_threshold"
  }
}

案例拆解:某工业振动监测节点,部署在注塑机旁,需连续运行 90 天。测试团队设计了 7 天加速压力测试:将推理频率从 1Hz 提升至 5Hz(模拟多台设备复用),数据流采用“正常振动 10 分钟 + 冲击脉冲 30 秒”的循环模式,同时每 15 分钟注入一次 CRC 错误帧。测试进行到第 3 天时,设备出现偶发重启。通过日志分析发现,错误帧注入触发了异常处理函数,该函数在连续调用 50 次后因栈溢出导致 HardFault。修复后重新测试,7 天无故障。此案例说明,压力场景必须包含异常路径的持续触发,否则无法暴露深层缺陷。

4.3 监控指标体系:内存、CPU、温度、功耗的量化采集

监控指标是判断系统健康状态的“仪表盘”。TinyML 设备资源有限,监控本身也会消耗资源,因此需要采用“轻量级采样 + 分级告警”的策略。

内存监控:关注三个数值——当前堆使用量、最大堆使用量(高水位线)、内存碎片率。对于 FreeRTOS 系统,可通过 heap_caps_get_free_size() 和 xPortGetFreeHeapSize() 获取。碎片率计算方式:1 - (最大空闲块 / 总空闲内存)。碎片率持续上升是内存碎片化的典型信号。

CPU 监控:使用 RTOS 的任务统计功能,记录每个任务的 CPU 占用率、栈高水位线。重点关注推理任务的执行时间抖动(Jitter),如果推理耗时从平均 45ms 逐渐增长到 60ms,可能意味着缓存命中率下降或内存访问变慢。

温度监控:片上温度传感器(如 ESP32 的内部温度传感器)精度有限,建议在 PCB 关键位置(CPU 背面、LDO 附近)增加外部 NTC 或数字温度传感器。采样频率 1Hz,记录温度变化曲线。温度每升高 10°C,电子迁移速率翻倍,因此温度趋势比值更重要。

功耗监控:使用高精度电流探头(如 INA219/INA226)串联在电源输入端,采样率 100Hz。记录平均电流、峰值电流、休眠电流三个值。功耗异常升高往往与外设未正确关闭、GPIO 浮空或无线模块重传有关。

指标类别 采样频率 告警阈值(示例) 采集方式
堆内存使用量 1 Hz 持续 > 80% 或碎片率 > 30% RTOS API 调用
任务栈高水位 0.1 Hz 剩余 < 20% 任务句柄查询
CPU 占用率 1 Hz 持续 > 95% 任务统计表
核心温度 1 Hz > 85°C 持续 5 分钟 外部 NTC + ADC
系统电流 100 Hz 平均电流 > 标称 1.5 倍 INA226 + I2C
推理延迟 每次推理 P99 延迟 > 2 倍基线 时间戳记录
风险提示:监控代码本身可能引入干扰。例如,频繁读取温度传感器会唤醒 I2C 总线,增加功耗。建议监控任务采用“批量采集、定时上报”模式,每 5 秒采集一组数据,打包后通过串口或日志系统输出,避免高频 IO 操作影响被测系统行为。

4.4 日志记录方法:从环形缓冲区到崩溃转储

长时间运行测试的日志系统需要解决三个矛盾:存储空间有限、数据量庞大、故障现场需要完整保留。有效的日志策略是“分级存储 + 环形覆盖 + 关键事件持久化”。

分级日志策略:将日志分为 DEBUG(调试)、INFO(常规)、WARN(警告)、ERROR(错误)、FATAL(致命)五级。正常运行期间只记录 INFO 及以上级别,DEBUG 日志通过编译宏关闭。当检测到异常时,自动将日志级别切换为 DEBUG,并持续记录 30 秒,以捕获故障前后的详细上下文。

环形缓冲区实现:在 RAM 中分配一块 8-16KB 的环形缓冲区,存储最近 2000 条日志。当系统发生崩溃时,通过看门狗或异常处理函数将缓冲区内容写入外部 Flash 的专用分区。这样既保证了正常运行时的低开销,又能在故障时保留现场。

// 环形日志缓冲区核心结构
typedef struct {
  uint8_t *buffer;
  uint16_t head;
  uint16_t tail;
  uint16_t count;
  uint16_t capacity;
  bool overwrite;
} ring_log_t;

// 崩溃时导出日志
void crash_handler(void) {
  // 1. 停止中断
  __disable_irq();
  // 2. 将环形缓冲区写入 Flash 崩溃分区
  flash_write_crash_dump(&ring_log, CRASH_PARTITION_ADDR);
  // 3. 记录崩溃原因(异常类型、PC、LR)
  crash_reason_t reason = get_crash_reason();
  flash_write_reason(&reason);
  // 4. 触发软复位
  NVIC_SystemReset();
}

时间戳同步:所有日志必须带有统一的时间基准。建议使用 RTC(实时时钟)作为主时间源,启动时通过 NTP 或上位机同步。日志格式采用 [时间戳][级别][模块] 内容 的结构,便于后续用脚本分析。对于无 RTC 的极简设备,可使用系统 Tick 计数 + 启动时间推算。

案例拆解:某智能门锁项目,在 200 小时长时间测试中,设备在第 173 小时出现一次死机。由于日志系统采用了环形缓冲区 + 崩溃转储机制,成功捕获了死机前 2 秒的完整日志。分析发现,死机前 1.5 秒有一条 WARN 日志“指纹匹配超时,重试第 3 次”,随后 DEBUG 日志显示内存分配失败(malloc(2048) failed)。进一步定位发现,指纹匹配模块在连续失败后未释放模板缓冲区,导致内存耗尽。修复后重新测试 200 小时无异常。如果没有崩溃转储机制,此类偶发问题几乎无法定位。

总结复盘:长时间运行测试不是简单的“挂机跑几天”,而是一个系统工程。测试时长需基于循环周期和故障模式推导;压力场景必须覆盖正常路径和异常路径;监控指标应聚焦内存、CPU、温度、功耗四个维度,并设置合理的告警阈值;日志系统要采用分级策略和环形缓冲区,确保故障现场可追溯。建议每次长时间测试结束后,输出一份包含趋势图、异常事件列表、资源使用摘要的测试报告,为后续版本迭代提供基线数据。