当前位置: 首页 > 工具软件 > Cyclictest > 使用案例 >

cyclictest 测试以及原理浅谈

柳逸春
2023-12-01

1. cyclictest 简介以及安装

1.1 cyclictest 简介

      cyclictest 是什么? 看名字应该就能大致猜出来它是一种 test 程序,Cyclictest的维基主页这么介绍它“Cyclictest is a high resolution test program, written by User:Tglx, maintained by User:Clark Williams ”,也就是它是一个高精度的测试程序,Cyclictest 是 rt-tests 下的一个测试工具,也是rt-tests 下使用最广泛的测试工具,一般主要用来测试使用内核的延迟,从而判断内核的实时性。

1.2 cyclictest 安装

1.2.1 基于包管理软件安装

Debian / Ubuntu 系统下可以直接使用apt-get install rt-tests 来安装cyclictest。

1.2.2 git 仓库源码安装

      使用Linux最大的好处就是我们可以下载软件的源码,学习、编译以及使用,所以如果使用上述方法直接安装使用,如果你觉得有的问题不懂或者出现问题你也没办法解决,所以从开发者的角度而言,下载安装软件还是下载源码包编译后使用比较好。

(1) 首先拷贝cyclictest的Git 仓库

#  git clone git://git.kernel.org/pub/scm/linux/kernel/git/clrkwllms/rt-tests.git

(2) 进入git仓库

#  cd rt-tests

(3)创建一个分支,比如我们起名叫testing

#  git branch testing    

(4)转到testing分支,之后我们做的步骤都不会对主分支有影响,这是我们在电脑上使用 git 仓库的常用方法

#  git checkout testing  

(5)查看我们当前在哪个分支

#  git branch     

  master
* testing

(6)在次我们使用make编译

#  make         

编译时我们会遇到缺失numa.h 的错误提示,在此我建议童鞋们安装使用apt-file 来解决此类错误(有了apt-file,遇到这类错误我们就知道如何解决而不是一味的上网找别人的解决方法),主要步骤如下:
    #  sudo apt-get install apt-file                   // 安装apt-file
    #  apt-file update                                      // 类似于apt-get ,apt-file也需要根据系统配的源来更新一个库
    #  apt-file search numa.h                         // 使用apt-file search 搜索我们缺失的文件

libhwloc-dev: /usr/include/hwloc/linux-libnuma.h         
libnuma-dev: /usr/include/numa.h                                // 在搜索到的结果中,我们发现这个包叫做 libnuma-dev 应该就是我们需要安装的包
linux-headers-3.2.0-4-amd64: /usr/src/linux-headers-3.2.0-4-amd64/include/config/acpi/numa.h
linux-headers-3.2.0-4-amd64: /usr/src/linux-headers-3.2.0-4-amd64/include/config/amd/numa.h

.... 
    #  apt-get install libnuma-dev                   // 使用apt-get 安装libnuma-dev 包

(7) 安装完libnuma-dev 后再编译,成功。

#  make          

(8) 查看编译后的rt-tests 目录

ls查看我们编译好的rt-tests仓库,我们可以看到make 之后生成了很多可执行文件(绿色的),这些都是一个个测试工具,在以后使用中如果遇到我会再讲解。

#  ls
COPYING       hackbench         Makefile      pmqtest          rt-get_cpu.o      sendme        sigwaittest.d
cyclictest    hackbench.d       pip_stress    pmqtest.d        rt-migrate-test    sendme.d      sigwaittest.o
cyclictest.d  hackbench.o       pip_stress.d  pmqtest.o        rt-migrate-test.d  sendme.o      src
cyclictest.o  hwlatdetect       pip_stress.o  ptsematest       rt-migrate-test.o  signaltest    svsematest
debug         librttest.a       pi_stress     ptsematest.d     rt-tests.spec-in   signaltest.d  svsematest.d
doc           logfile           pi_stress.d   ptsematest.o     rt-utils.o         signaltest.o  svsematest.o
error.o       logfile_10000000  pi_stress.o   README.markdown  scripts            sigwaittest   testininitS


2. cyclictest 的使用及参数简介

对于Cyclictest的使用我们得先了解它的各个参数的含义,所以在你开始使用之前,请你看一下 cyclictest --help 中提到的各个参数!这将对你使用有很大的帮助。

2.1 cyclictest的简单使用及结果分析

如果你只是想玩玩这个工具,那么对于维基主页上提到的tglx 使用的测试命令来测测你的电脑性能:

#  sudo ./cyclictest -t1 -p 80 -n -i 10000 -l 10000

注:在rt-tests的路径下,我们可以使用 ./cyclictest 来运行cyclictest, 而在别的目录下,我们就需要指定 cyclictest的路径来使用,比如说 /home/long/rt-tests/cyclictest ,或者你也可以直接将 rt-tests的路径下的 cyclictest 拷贝到 /bin/ 下,以后就可以直接使用 cyclictest 而不需要指定路径了!!

比如在我的电脑上,我使用这个命令测试的结果如下:

# /dev/cpu_dma_latency set to 0us
policy: fifo: loadavg: 0.38 0.29 0.26 1/381 5595    

T: 0 ( 5592) P:80 I:10000 C:  10000 Min:      2 Act:   15 Avg:   15 Max:     195

输出结果含义:
T: 0 序号为0的线程
P: 0 线程优先级为0
C: 9397 计数器。线程的时间间隔每达到一次,计数器加1
I: 1000 时间间隔为1000微秒(us)
Min: 最小延时(us)
Act: 最近一次的延时(us)
Avg:平均延时(us)
Max: 最大延时(us)

所以我们当前的机器上最小延时为2,平均为15,最大的为 195。

$uname -a               // 我们可以使用 “ uname -a ” 看到我们系统目前使用的内核版本
Linux wheezy 3.2.51-trace #8 SMP Thu Nov 21 12:34:04 CST 2013 x86_64 GNU/Linux

$ cat /boot/config-3.2.51-trace |grep CONFIG_PREEMPT_RT    // 我们再打开 /boot 下面的当前内核的config信息查看目前这个内核是否打上实时补丁,结果显示并没有。所以在一个普通的内核下测的 Min:      2 Act:   15 Avg:   15 Max:     195 这样的数据算是不错的了!

$ cat /boot/config-3.10.17-trace-rt12 |grep CONFIG_PREEMPT_RT    // 而在我 /boot 目录下的另外一个打好实时补丁的内核中
CONFIG_PREEMPT_RT_BASE=y
# CONFIG_PREEMPT_RTB is not set
CONFIG_PREEMPT_RT_FULL=y             // 判断一个内核是否是实时内核,请看config 下有没有此项

我得到的cyclictest 运行结果是这样的:  

T: 0 ( 5592) P:80 I:10000 C:  10000 Min:      1 Act:  1 Avg:   2 Max:     9

:-),运行的结果有目共睹,在以后的博客中我会介绍关于Linux 内核的实时补丁。

2.2 cyclictest 的参数介绍 

      关于cyclictest 的各个参数具体含义建议大家还是用时间具体看看 cyclictest --help 的信息(参考资料【2】为我的师兄对--help下的每个参数的解释,大家也可以看看!)我这只介绍几个常用的。
-p PRIO --prio=PRIO       最高优先级线程的优先级  使用时方法为: -p 90 /  --prio=90
-m       --mlockall        锁定当前和将来的内存分配
-c CLOCK --clock=CLOCK     选择时钟  cyclictest -c 1
                           0 = CLOCK_MONOTONIC (默认)
                           1 = CLOCK_REALTIME
-i INTV  --interval=INTV  基本线程间隔,默认为1000(单位为us),下面介绍原理的时候会提到
-l LOOPS --loops=LOOPS     循环的个数,默认为0(无穷个),与 -i 间隔数结合可大致算出整个测试的时间,比如 -i 1000  -l 1000000 ,总的循环时间为1000*1000000=1000000000 us =1000s ,所以大致为16分钟多。
-n       --nanosleep       使用 clock_nanosleep
-h  HISTNUM    --histogram=US    在执行完后在标准输出设备上画出延迟的直方图(很多线程有相同的权限)US为最大的跟踪时间限制,这个在下面介绍实例时可以用到,结合gnuplot 可以画出我们测试的结果图。
                       
-q       --quiet         使用-q 参数运行时不打印信息,只在退出时打印概要内容,结合-h HISTNUM参数会在退出时打印HISTNUM 行统计信息以及一个总的概要信息。
-f
      --ftrace          ftrace函数跟踪(通常与-b 配套使用,其实通常使用 -b 即可,不使用 -f )
-b USEC  --breaktrace=USEC 当延时大于USEC指定的值时,发送停止跟踪。USEC,单位为谬秒(us)。


2.3 推荐参数以及结果实例

dslab@wheezy:~$ sudo cyclictest -p 90 - m -c 0 -i 200 -n -h 100 -q -l 1000000
我们使用 -p 90给cyclictest 赋优先级90,使用-m参数锁定内存分配,使用  -c 0指定使用默认的MONOTONIC 时钟, -i 200 指定一个循环为200us,结合 -l 1000000为总共1000000个循环,此外-n 为使用nanosleep 而不是简单的sleep,-q为在运行时不打印即时信息,-h 100 为总共统计100个信息在最后的结果中。 
# /dev/cpu_dma_latency set to 0us
---------------------------------------------------(下面都是结束测试/终端测试后打印的信息,这就是 -q 的功效!)
# Histogram
000000 000000
000001 111448              -- 延时为1us的在1000000次循环中占111448次(下面每行都是这个意思)
000002 060272
000003 000714
000004 000344
000005 000231
000006 013170
000007 155289
000008 601393
000009 044880
000010 005348
000011 001821
000012 001444
000013 000945
000014 000538
000015 000376
000016 000344
.....
000096 000002
000097 000002
000098 000002
000099 000002              -- 我们使用 -h 100 ,所以在结果中记录了延时为 0us ~ 99us 的次数
# Total: 000999888
# Min Latencies: 00001       -- 最小延时 1 us
# Avg Latencies: 00006       -- 平均延时 6us
# Max Latencies: 00463      -- 最大延时463 us,那么我们指定histogram = 100也就是只记录了0us~99us的值而最大延时为463 也就是说肯定有很多此延时超过99 us,那么记录到哪了?答案是,没有记录具体的超过99us的延时值,只在下面记录了超过99us 的延时次数(记录在Overflows),以及第几次超过了(记录在Thread 0)。
# Histogram Overflows: 00112     -- 超过99 us的次数
# Histogram Overflow at cycle number:
# Thread 0: 02985 06044 06107 08644 08683 12048 18136 30164 33172 33757 36214 48208 54138 58822 61284 83843 83876 86382 92351 92352 96306 108937 108941 111443 117367 129130 129131 146426 155069 155070 159058 161563 171486 184200 186614 209260 211606 221606 223526 223527 234275 234321 236827 241705 241706 246766 266826 296886 321946 334644 336979 337006 359705 367066 384765 392126 412186 437221 442246 462306 472306 484921 487366 497366 507426 509981 512448 512488 522426 542486 567546 587606 610305 617666 635365 637704 637726 660425 672686 692846 710379 710463 717806 735443 737919 742886 760582 763088 767946 785515 785642 788149 793086 806776 808146 810703 813146 835661 835847 838172 # 00012 others  //这里记录的是第几次循环的延时超过了99us。

3. cyclictest 的原理浅谈 

如果看了上面第2.3 关于测试以及结果的介绍,我相信大家对 cyclictest 的测试也知道了个大概。下面我们结合源代码来学习 cyclictest 的具体运行。

从rt-tests/src/cyclictest/cyclictest.c 的 main函数入手:

int main(int argc, char **argv)
{	...
	stat->min = 1000000;
    stat->max = 0;
    stat->avg = 0.0;
    stat->threadstarted = 1;
    status = pthread_create(&stat->thread, &attr, timerthread, par);
	...
}

所以我们看到在main函数中只是定义了相关的变量之类的,而具体测试是通过创建线程,在线程中进行测试以及记录,下面我们打开测试线程:

void *timerthread(void *param)
{	...
    interval.tv_sec = par->interval 	// 首先将参数中的间隔数赋给函数中的间隔数
    interval.tv_nsec = (par->interval % USEC_PER_SEC) * 1000;	
	...
    /* Get current time  */
    clock_gettime(par->clock, &now);	// 获取当前时间,存在 now 中
	
    next = now;				//\
    next.tv_sec += interval.tv_sec;	// = 这三行是将当前时间(now 的值)加上间隔数(interval)算出下次间隔的时间,存在next 
    next.tv_nsec += interval.tv_nsec;	///
    tsnorm(&next);
	...
	/* Wait for next period */ 等到下次循环
		... 
	if ((ret = clock_gettime(par->clock, &now))) {	//下次循环中记录循环时的时间到now 中,此时now 值中存的数是真实的下次循环的值,而上面存在next 的值是上次循环加上间隔值所以是理论上的下个循环的值。
            if (ret != EINTR)
                warn("clock_getttime() failed. errno: %d\n", errno);
            goto out;
        }
	
   if (use_nsecs)
        diff = calcdiff_ns(now, next);	        // 上面已经说过了,now 中是下次循环的真值,而next是理论的值,所以两者的差就是延时!延时赋值给diff
    else
        diff = calcdiff(now, next);
    if (diff < stat->min)			// 假如延时比min 小,将min 改为这个更小的延时值diff
        stat->min = diff;
    if (diff > stat->max) {			// 假如延时比max 大,将max 改为这个更大的延时值diff
        stat->max = diff;
        if (refresh_on_max)
            pthread_cond_signal(&refresh_on_max_cond);
    }
    stat->avg += (double) diff;		// 计算新的平均延时
	...
    /* Update the histogram */		// 更新histogram中存的延时统计数据
    if (histogram) {
        if (diff >= histogram) {	// 假如延时比histogram大,添加一次溢出
            stat->hist_overflow++;	
                            if (stat->num_outliers < histogram)
                stat->outliers[stat->num_outliers++] = stat->cycles;
        }
        else						// 如果没有溢出,将histogram 中的相应值加1
            stat->hist_array[diff]++;
    }

    stat->cycles++;					// 循环加1

    next.tv_sec += interval.tv_sec;	// 继续计算下次循环的值 ...
    next.tv_nsec += interval.tv_nsec;
	...
}


从代码中,我们可以学习到cyclictest的计算延时的原理!很简单吧,o(∩∩)o...哈哈,认识到了开源软件的好处了吧!

==============================

参考资料:

【1】cyclictest 的维基主页 https://rt.wiki.kernel.org/index.php/Cyclictest        

【2】我师兄翻译的cyclictest维基主页 http://blog.csdn.net/ganggexiongqi/article/details/5841347 

 类似资料: