CUT(cpu utilization test)负载率测试
1. 前言
目前在开发过程中,需要检测任务的CPU占用率,而很多的系统中是不支持像FreeRTOS、RT-Thread等操作系统可以支持CPU占用率的统计,因此实现了CUT去实现该需求。
2. CUT概述
2.1 功能介绍
- 在一定时间内任务的运行时间统计
- 在一定时间内任务的运行次数统计
- 在一定时间内任务的负载率计算
- 在一定时间内任务的平均运行时间计算
- 统计数据打印
- 命令行方式控制测试开始/结束
需要资源:
- 定时器:提供时间
- CUT组件:运行时间计算
- SEGGERT RTT:日志打印
- Letter Shell:命令行交互
2.2 适用场景
适用于前后台的系统(任务 + 中断)。
适用于优先级高的任务发生后完全执行完毕,没有类似信号量、互斥锁等导致任务运行一半中途跳转的情况(如第三部分流程图所示)。
3. 工作原理
-
时间统计
由于我们的任务运行时间基本都是us、ms级别,在一般的系统中,系统时间都是以ms为单位计算的,因此不足以满足我们的任务运行时间。因此使用定时器的
Counter
来用作时间点计算(例如4MHz的时钟频率,那么一个Tick值就是0.25us)。 -
单次任务运行时间计算(非嵌套)
在进入任务的时候记录Counter(A),任务结束的时候记录Counter(B),通过B和A计算出运行时间。
注意:定时器的定时周期要大于任务的执行时间,比如定时周期10ms,我的任务执行时间要小于10ms。如果定时为5ms,执行11ms,那么只能计算出任务时间为6ms,中间会丢失5ms,因此这里需要估算一个大概的范围,例如我定时器时钟频率4MHz,那么我计数周期就设定4000000,定时周期1s,足以覆盖任务执行时间。
-
单次任务运行时间计算(嵌套)
如下图所示,任务A会被任务B打断。当任务A开始,在任务A的数据缓存中记录Counter,此时将任务A压栈(为了任务B结束后,能知道是回到A任务),任务B开始,获取当前Counter值,先将当前A任务运行时间结算累加,然后在任务B的数据缓存中记录Counter并将任务B压栈,任务B执行完毕,获取当前Counter值,计算出任务B的运行时间累加,任务B出栈,然后获取当前Counter值更新到任务A中,任务A继续执行,任务A执行完毕,结算剩下运行时间累加,任务A出栈。
-
时间补偿
时间补偿就是在计算的时候除去测试代码消耗掉的时间,该部分时间通过测试验证得出一个大致的范围:
任务空跑测试 时间 负载率 总运行时间 10s 10ms任务 1079.750us 0.01% 500ms任务 22.250us 0.00% 10000ms任务 11.000us 0.00% 空跑(非嵌套)测试下来执行一次测试代码 ≈ 1us
任务空跑嵌套测试 时间 负载率 总运行时间 10s 10ms任务 2579.000us 0.025% 500ms任务 2602.250us 0.026% 10000ms任务 1071.000us 0.010% 空跑(非嵌套)测试下来执行一次测试代码 ≈ 2.5us
因此时间补偿有两个点,一个是任务被打断的时候做一次时间补偿1.5us(2.5 - 1),一次是任务结束的时候做一次时间补偿1us
4. 测试数据
下面一个是通过手动测试,IO口输出高低电平,通过示波器采集数据的方式统计的数据,一个是使用CUT组件进行测试统计的数据:
手动测试数据 | 时间 | 负载率 | 次数 | 平均值 |
---|---|---|---|---|
运行时间 | 13.98s | |||
2ms | 7.2% | 7000 | ||
5ms | 4.73% | 2800 | ||
10ms | 34.36% | 1399 | ||
LPIT_ISR | 11.61% | |||
FTM_ISR | 0.26% |
代码测试数据 | 时间 | 负载率 | 次数 | 平均值 |
---|---|---|---|---|
运行时间 | 27775000us | |||
2ms | 1659762.920us | 6.491% | 14000 | 118.554us |
5ms | 1292408.448us | 5.054% | 5601 | 230.746us |
10ms | 8987586.647us | 35.150% | 2801 | 3208.746us |
LPIT_ISR | 3054445.647us | 11.945% | 277750 | 10.997us |
FTM_ISR | 71475.496us | 0.279% | 6998 | 10.213us |
5. 使用方式
如下面代码所示,在任务的开始和结束增加CUT_TASK_ENTRY
/CUT_TASK_LEAVE
,传入定义好的任务名称:
void Task_10ms(void)
{
extern void shellTask(void *param);
shellTask(&shell);
CUT_TASK_ENTRY(TASK_10ms);
// code...
CUT_TASK_LEAVE(TASK_10ms);
}
如下图所示,通过命令行的方式控制测试的开始/关闭,关闭后会输出测试结果:
打印方式:
SEGGERT RTT
命令行交互:
Letter Shell
将上述两个组件配合CUT使用实现下图效果: