ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

单机测试明明合格,多轴控制为什么还是会抖?

单机测试明明合格,多轴控制为什么还是会抖?

在工业控制和机器人项目中,单机测试通过并不意味着整套控制系统已经具备稳定运行的能力。一个在空载环境下运行良好的实时 Linux 系统,接入多个伺服驱动器、启动 EtherCAT 周期通信、运行运动学计算,再叠加上位机通信、日志记录和状态监测之后,仍然可能出现控制周期超时、数据更新不及时甚至偶发的设备异常。

这种现象并不一定意味着操作系统的实时性能突然变差,而可能是系统从简单测试环境进入真实业务环境后,暴露出了此前没有覆盖的资源竞争和时序依赖。

例如,单机测试只关注周期定时唤醒延迟,而真实控制任务还需要执行算法、交换过程数据并更新输出;单轴测试没有触发多轴同步时的计算压力;短时间测试没有覆盖内存增长、温度变化和长尾事件;空载测试也没有模拟上位机持续发送指令时带来的额外工作量。

因此,实时系统压力测试不能简单理解为让 CPU 长时间跑满,也不能只运行一个cyclictest就宣布测试完成。真正有价值的压力测试,需要围绕目标应用的时间约束,逐步增加系统复杂度,覆盖资源竞争、通信负载、任务执行和异常处理,并记录足够的信息来判断问题发生在哪里。

本文以工业控制、EtherCAT 多轴控制和机器人应用为例,介绍如何构建从单机基线到整机压力测试的工程方案。

一、先确定测试目标:不是把系统压满,而是验证关键任务

压力测试最常见的误区,是把 CPU 使用率当成主要测试目标。

例如,测试人员使用压力工具将 CPU 使用率提升到 90%,观察控制线程是否出现延迟。如果没有出现异常,就认为系统具有足够的实时能力。

这种方法可以帮助发现部分资源竞争问题,但不能完整代表真实控制系统的运行状态。实际系统的瓶颈可能来自中断分布、锁竞争、内存带宽、缓存争用、驱动路径、数据队列或者设备同步关系,而不是 CPU 总体使用率。

另一方面,即使 CPU 使用率只有 30%,如果关键实时线程被绑定在某个繁忙核心上,或者受到高优先级任务、IRQ 处理及内核活动的影响,也可能出现明显的长尾延迟。

因此,压力测试首先应当回答三个问题:

  1. 系统必须保证哪些任务在规定时间内完成?

  2. 在什么负载条件下,这些任务仍然必须满足时间约束?

  3. 出现异常时,需要采集哪些证据才能确定原因?

1. 从业务需求中提取测试指标

以一套多轴运动控制设备为例,系统可能包含 EtherCAT 主站周期任务、控制算法线程、伺服状态监测、上位机通信、日志处理和故障管理线程。

这些任务不应采用完全相同的验收指标。

测试对象主要指标需要关注的问题
周期定时任务唤醒延迟、延迟分布是否存在调度长尾
控制算法线程执行时间、响应时间计算是否在预算内完成
EtherCAT 主站任务周期完成时间、通信状态是否满足主站时序要求
过程数据链路数据更新时间、数据年龄控制算法是否使用过期数据
多轴控制任务周期违约、同步误差轴间时序是否满足控制要求
后台业务线程CPU、内存、队列和 I/O 负载是否干扰关键控制路径
长时间运行系统延迟趋势、资源变化、故障计数是否存在随时间累积的问题

这里需要强调,通信状态正常、工作计数器正常或设备没有报告错误,都不能独立证明控制任务按时完成。系统必须同时验证任务执行时间、数据新鲜度以及实际输出时序。

2. 建立端到端测量链路

压力测试前,先明确需要记录哪些关键时间点。

对于一个典型的周期控制流程,可以设置以下测量点:

  • (t_0):本周期预期释放时刻。

  • (t_1):控制或主站线程实际开始执行的时刻。

  • (t_2):输入过程数据完成读取的时刻。

  • (t_3):控制算法完成计算的时刻。

  • (t_4):输出过程数据完成更新的时刻。

  • (t_5):指定的通信或设备输出事件发生的时刻。

根据实际架构,可以计算:

[
L_{\text{wake}}=t_1-t_0
]

[
T_{\text{control}}=t_3-t_2
]

[
T_{\text{path}}=t_5-t_0
]

其中,(L_{\text{wake}}) 是相对于预期释放时刻的启动迟到,(T_{\text{control}}) 是这里定义的算法计算时间,(T_{\text{path}}) 则表示从预期释放时刻到指定输出事件的时间。

这些公式只是测量口径的示例。实际项目应当根据任务模型定义每个时间点,并确认时钟来源和同步关系。如果 (t_5) 来自另一台设备,还需要考虑时钟同步误差和测量设备的时间分辨率。

如果只记录 (t_1) 和 (t_3),就可能遗漏数据在进入算法前的等待时间,以及输出更新后的通信和设备处理时间。

压力测试的第一个原则,是先确定测量链路,再决定如何施加负载。

二、从单机基线开始:逐步建立可对比的测试层级

如果一开始就把所有业务功能、多个轴和全部后台服务同时启动,即使测试失败,也很难判断真正的原因。

更合理的做法,是将压力测试拆分为多个阶段。每个阶段都保留上一阶段的测试条件,只增加一类主要变量,从而观察系统性能随负载变化的趋势。

1. 第一阶段:空载与基础调度测试

首先记录系统在尽可能简单的运行条件下的基础表现。

需要检查:

  • CPU 型号、逻辑核心拓扑及当前在线 CPU。

  • 内核版本、配置、启动参数和实时调度能力。

  • 关键线程的调度策略、优先级与 CPU 亲和性。

  • IRQ 分布和后台服务运行情况。

  • 基础唤醒延迟及其统计分布。

可以使用以下命令获取部分系统信息:

uname -a lscpu cat /proc/cmdline ps -eLo pid,tid,psr,cls,rtprio,pri,ni,stat,comm cat /proc/interrupts

随后,可以使用cyclictest建立基础唤醒延迟基线。例如:

sudo cyclictest -m -p 80 -i 1000 -D 60s

这条命令只是示例,实际参数需要根据工具版本、权限和测试需求调整。

此阶段的目的是建立可比较的基线,而不是直接给出整机实时性能结论。空载测试结果再好,也不能替代真实业务压力下的验证。

2. 第二阶段:单任务与单轴测试

接下来启动一个真实控制任务,或者先接入单个伺服轴。

在这一阶段,应当开始记录应用程序自身的时间戳,而不是只观察操作系统调度延迟。

例如,控制程序可以在每次周期执行时记录计划释放时刻、实际开始时刻、算法完成时刻和输出更新时刻。记录方式应尽量避免在实时路径中进行同步文件写入,可以采用预分配的固定大小缓冲区,再由后台线程异步导出数据。

测试时,应确认:

  • 目标周期是否稳定。

  • 控制算法执行时间是否符合预算。

  • 输出数据是否按预期更新。

  • 是否出现周期超时或数据过期。

  • 测试程序本身是否引入额外负载。

如果单轴测试已经出现明显异常,应当先解决单轴链路中的问题,再继续增加轴数。否则,多轴测试只会增加定位难度。

3. 第三阶段:增加轴数与通信负载

多轴控制增加的不仅是设备数量,还可能改变通信负载、控制算法计算量、数据处理开销和线程间依赖关系。

随着轴数增加,系统可能需要处理更多 PDO 数据、更多状态变量和更复杂的控制算法。实际开销取决于从站数量、数据映射、主站实现、控制策略以及设备同步方式,不能仅根据轴数进行线性推算。

这一阶段应当逐步增加轴数,例如从单轴扩展到少量轴,再扩展到目标配置。每次扩展后,都应重新测量周期完成时间、算法执行时间、数据新鲜度和截止时间违约情况。

对于 EtherCAT 系统,还应同步观察主站状态、工作计数器、数据交换结果以及从站同步配置。通信错误计数没有增加,并不代表所有过程数据都在预期时间内更新。

如果控制任务与主站周期线程运行在不同 CPU 上,还应评估跨线程数据交换开销和同步等待是否随负载增加而变化。

4. 第四阶段:叠加普通业务负载

真实设备通常还需要运行上位机通信、数据采集、参数管理、日志记录和远程维护服务。

这些业务可能与实时任务竞争 CPU、缓存、内存带宽和 I/O 资源。即使它们被安排在其他 CPU 上,也不能假设其对实时控制完全没有影响。

建议分别增加以下负载:

  • 上位机持续发送状态查询和参数请求。

  • 后台线程持续采集和处理运行数据。

  • 日志队列持续产生记录。

  • 存储设备承担持续写入任务。

  • 普通网络服务在高流量条件下运行。

  • 其他计算任务在指定 CPU 上持续工作。

每次增加一种负载后,都应观察实时任务的延迟分布和违约情况。

如果增加日志写入后最大观测延迟明显增加,可以进一步检查是否存在同步日志、共享锁、内存分配或者 I/O 竞争。如果增加网络负载后延迟发生变化,则应检查 IRQ 分布、网络处理路径、CPU 亲和性和相关驱动行为。

这些现象只是排查线索,不能仅凭相关性就认定根因。应通过关闭某项负载、改变其运行位置或调整数据处理方式,进行对照测试。

三、如何设计压力场景:覆盖 CPU、IRQ、内存和设备链路

系统压力测试不应只有一个“高负载”场景。不同负载会触发不同类型的资源竞争,因此需要构建具有明确目的的测试矩阵。

1. CPU 竞争场景

CPU 压力测试用于观察其他计算任务对实时控制的影响。

可以使用stress-ng等工具生成受控的 CPU 负载,但必须确认负载运行在哪些 CPU 上。如果压力任务运行在普通业务核心上,而实时线程绑定在隔离核心上,那么测试得到的结果只能说明当前条件下的表现,不能代表所有 CPU 竞争场景。

建议至少覆盖:

  • 普通业务核心上的计算负载。

  • 实时核心附近或共享硬件资源上的负载。

  • 多个核心同时运行计算任务。

  • 实际应用自身的峰值计算负载。

不要为了追求 CPU 使用率达到 100% 而忽略真实业务特征。某些控制系统的主要风险并不是计算密集型负载,而是短时间内集中出现的中断、锁竞争或数据处理任务。

2. IRQ 与网络负载场景

中断分布不合理时,可能导致某些 CPU 长时间处理设备事件,影响实时线程获得 CPU 的时机。

测试期间可以对比不同负载条件下的/proc/interrupts:

cat /proc/interrupts

如果需要进一步定位,可以在系统支持相应事件时使用trace-cmd跟踪调度和 IRQ 事件。记录之前应检查内核支持的事件,并控制跟踪范围,避免采集本身引入过多开销。

网络和现场总线相关测试还应考虑设备驱动的具体实现。不能假设普通网卡、EtherCAT 主站网卡和其他设备具有相同的中断行为。

在修改 IRQ 亲和性之前,应保存原始配置,并确认驱动、内核和中断控制器支持预期的操作。调整后还需要重新验证通信稳定性和控制任务时序。

3. 内存和存储压力场景

实时任务的延迟可能受到缺页、内存回收、动态分配和存储 I/O 等因素影响。

可以在受控条件下逐步增加内存使用和数据写入负载,同时记录:

  • 系统可用内存和进程内存变化。

  • 主要缺页统计。

  • 队列长度和缓冲区占用。

  • 日志写入速率与存储延迟。

  • 控制任务的执行时间与响应时间。

例如:

vmstat 1 cat /proc/meminfo cat /proc/vmstat

这些命令可以帮助观察系统状态,但不能直接证明某次延迟由内存问题引起。如果需要关联具体异常,应当结合应用层时间戳、调度轨迹以及内核中可用的内存相关事件进行分析。

内存压力测试也必须设置边界,避免测试工具直接耗尽系统资源,使控制系统进入未定义或无法安全恢复的状态。对于具有安全要求的设备,压力测试应在专门测试环境中进行,并制定明确的中止条件。

4. 长时间运行与组合负载场景

一些问题只有在长时间运行后才会出现。例如,数据队列逐步积压、内存占用缓慢增加、后台任务产生持续竞争,或者处理器温度和频率状态随运行条件发生变化。

因此,测试计划中应包含长时间运行场景,而不仅是几分钟的短时测试。

还应逐步组合多个负载,例如多轴控制、上位机通信、日志记录和后台计算同时运行。组合测试比单项测试更接近真实应用,但应当在各单项测试结果已经明确的基础上开展。

需要特别注意,压力测试并不是越极端越好。测试负载应当与产品实际工作范围、目标环境和合理的故障场景对应。超出系统设计范围的测试可以用于探索边界,但不能与正常工作范围内的验收测试混为一谈。

四、怎样判断压力测试通过:不要只看平均值和最大值

压力测试最重要的结果,不是生成一张 CPU 使用率曲线,而是判断系统是否满足预先定义的时间约束。

1. 统计指标必须与截止时间关联

假设一个周期任务每隔 1 ms 释放一次,但实际验收要求是在释放后 700 μs 内完成某个关键处理步骤。那么,验收重点就应当是这个步骤的响应时间是否满足 700 μs 的要求,而不是简单地检查系统平均唤醒延迟是否小于 700 μs。

这只是一个假设示例。实际截止时间必须根据控制系统的需求和时序模型确定。

测试报告至少应当包含:

指标需要回答的问题
平均延迟常规运行水平如何?
P95、P99、P99.9尾部延迟是否明显恶化?
最大观测延迟本次测试中最严重的观测值是多少?
任务执行时间算法与关键处理路径是否超出预算?
端到端延迟从指定输入到指定输出经历了多久?
截止时间违约次数是否存在任务未按时完成?
数据过期次数是否有任务使用了不符合时效要求的数据?
通信异常统计是否存在设备或过程数据层面的异常?

其中,百分位指标适合描述分布,但不能替代截止时间判定。最大观测延迟也不能直接视为系统的理论最坏延迟。

如果验收要求不允许任何截止时间违约,那么即使测试期间的违约率很低,也不能直接判定通过。应根据项目要求进一步分析异常原因、覆盖范围和剩余风险。

2. 测试前就应定义通过与失败条件

验收标准应在测试开始之前确定,而不是测试结束后根据结果临时调整。

例如,可以明确:

  • 目标控制周期与允许的响应时间。

  • 哪些任务不允许出现截止时间违约。

  • 哪些通信异常属于不可接受的失败。

  • 数据过期时允许采用什么处理策略。

  • 需要覆盖的轴数、业务负载和运行时间。

  • 测试过程中出现哪些现象必须中止。

  • 测试后需要保存哪些日志和配置证据。

对于安全相关的控制设备,还需要遵循系统的安全设计与验证要求。一般性能压力测试不能代替功能安全验证,也不能仅凭实时指标判断设备是否具备安全能力。

3. 建立压力测试矩阵

下面是一份可以用于项目规划的示例矩阵。实际场景和阈值应当由项目需求确定。

测试阶段主要负载主要观察指标
A:基础基线空载、基础实时任务唤醒延迟、系统配置
B:单轴控制单轴算法与通信执行时间、周期违约
C:多轴扩展逐步增加轴数周期完成时间、数据新鲜度
D:CPU 压力受控计算负载长尾延迟、截止时间违约
E:通信压力上位机与网络负载IRQ 分布、数据更新时间
F:内存与存储压力数据采集、日志写入内存变化、队列积压、延迟
G:组合压力多轴与后台业务同时运行全链路响应时间、异常次数
H:长期运行目标业务持续执行资源趋势、延迟漂移、稳定性

每个阶段都应保留测试配置、负载参数、测试时长、样本数量和结果文件。这样既能判断某项优化是否有效,也能在软件版本或硬件发生变化后进行回归测试。

五、把测试结果变成可复现的工程证据

实时系统的压力测试最终应形成一套能够重复执行的测试流程,而不是只保留一次测试截图或一组漂亮的数字。

1. 保存环境、配置和原始数据

测试记录应当至少包括硬件型号、内核版本与配置、驱动和应用版本、CPU 亲和性、实时调度策略、IRQ 分配、负载条件、测试时长、样本数量以及统计口径。

对于关键异常,还应保留相应的应用时间戳、调度追踪、设备状态和通信记录。只有将这些信息关联起来,才能分析异常是在任务唤醒阶段、算法执行阶段、数据交换阶段还是设备响应阶段出现的。

2. 每次优化都应进行回归测试

例如,调整核心隔离配置后,不能只确认实时线程的最大观测延迟下降了,还需要检查:

  • IRQ 是否按预期分布。

  • 普通业务是否受到不必要的影响。

  • 多轴控制时的周期任务是否仍然满足时间预算。

  • 组合负载下是否出现新的长尾异常。

  • 长时间运行时是否存在资源积累。

  • 数据交换和故障处理机制是否保持正确。

同样,修改线程优先级、日志策略、数据队列或驱动配置后,也应重新执行相关测试场景。

对于望获OS或其他实时 Linux 平台,建议在相同硬件、相同驱动、相同应用、相同负载和相同测量口径下进行测试。这样才能更有意义地比较不同系统配置在目标业务中的表现,而不是仅比较不同环境下的单一延迟数字。

3. 测试报告应当能够回答三个问题

第一,系统在什么条件下完成了测试?包括硬件、内核、驱动、负载、运行时间和测试范围。

第二,系统是否满足预先规定的实时要求?包括任务执行时间、端到端延迟、截止时间违约以及通信和数据时效要求。

第三,如果发生异常,是否能够定位原因并复现?包括异常发生时的任务时间线、相关中断和系统状态,以及优化前后的对照证据。

如果测试报告不能回答这三个问题,那么即使其中的平均延迟很低,也很难作为整机实时性能的充分依据。

总结

从单机测试走向多轴控制,真正增加的不只是 CPU 负载,还有通信数据量、任务依赖、设备同步、资源竞争和故障处理的复杂度。

一套有效的实时系统压力测试方案,应当先明确任务的时间约束和测量链路,再从基础调度测试逐步扩展到单轴控制、多轴通信、CPU 压力、内存与存储压力、组合负载以及长时间运行。

测试过程中,需要同时关注平均延迟、P99、P99.9、最大观测延迟、任务执行时间、端到端响应时间和截止时间违约次数,并结合应用层时间戳、内核追踪和设备状态分析异常原因。

最重要的是,压力测试的目标不是证明系统在某一次运行中表现良好,而是通过明确的测试条件、可复现的实验和可追踪的证据,判断系统是否满足实际控制任务的时间要求。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进