在工业控制和机器人项目中,单机测试通过并不意味着整套控制系统已经具备稳定运行的能力。一个在空载环境下运行良好的实时 Linux 系统,接入多个伺服驱动器、启动 EtherCAT 周期通信、运行运动学计算,再叠加上位机通信、日志记录和状态监测之后,仍然可能出现控制周期超时、数据更新不及时甚至偶发的设备异常。
这种现象并不一定意味着操作系统的实时性能突然变差,而可能是系统从简单测试环境进入真实业务环境后,暴露出了此前没有覆盖的资源竞争和时序依赖。
例如,单机测试只关注周期定时唤醒延迟,而真实控制任务还需要执行算法、交换过程数据并更新输出;单轴测试没有触发多轴同步时的计算压力;短时间测试没有覆盖内存增长、温度变化和长尾事件;空载测试也没有模拟上位机持续发送指令时带来的额外工作量。
因此,实时系统压力测试不能简单理解为让 CPU 长时间跑满,也不能只运行一个cyclictest就宣布测试完成。真正有价值的压力测试,需要围绕目标应用的时间约束,逐步增加系统复杂度,覆盖资源竞争、通信负载、任务执行和异常处理,并记录足够的信息来判断问题发生在哪里。
本文以工业控制、EtherCAT 多轴控制和机器人应用为例,介绍如何构建从单机基线到整机压力测试的工程方案。
一、先确定测试目标:不是把系统压满,而是验证关键任务
压力测试最常见的误区,是把 CPU 使用率当成主要测试目标。
例如,测试人员使用压力工具将 CPU 使用率提升到 90%,观察控制线程是否出现延迟。如果没有出现异常,就认为系统具有足够的实时能力。
这种方法可以帮助发现部分资源竞争问题,但不能完整代表真实控制系统的运行状态。实际系统的瓶颈可能来自中断分布、锁竞争、内存带宽、缓存争用、驱动路径、数据队列或者设备同步关系,而不是 CPU 总体使用率。
另一方面,即使 CPU 使用率只有 30%,如果关键实时线程被绑定在某个繁忙核心上,或者受到高优先级任务、IRQ 处理及内核活动的影响,也可能出现明显的长尾延迟。
因此,压力测试首先应当回答三个问题:
系统必须保证哪些任务在规定时间内完成?
在什么负载条件下,这些任务仍然必须满足时间约束?
出现异常时,需要采集哪些证据才能确定原因?
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、最大观测延迟、任务执行时间、端到端响应时间和截止时间违约次数,并结合应用层时间戳、内核追踪和设备状态分析异常原因。
最重要的是,压力测试的目标不是证明系统在某一次运行中表现良好,而是通过明确的测试条件、可复现的实验和可追踪的证据,判断系统是否满足实际控制任务的时间要求。