一、简介
fio是一个I/O标准测试和硬件压力验证工具,它支持多种不同类型的I/O引擎,支持对块设备和文件系统测试,广泛用于标准测试、QA、验证测试等,支持Linux, FreeBSD, NetBSD,OS X, OpenSolaris, AIX, HP-UX, Windows等操作系统。
对于性能测试,fio能够测试存储系统(磁盘、阵列、文件系统)的最大IO能力、网络带宽,不同的IO负载能力测试,支持几乎所有的存储描述参数,可定制多种测试模式。
fio的特性:
- 支持应用的并发线程数目、IO队列深度设置
- 支持自定义IO引擎,实现在不同平台、不同应用测试的IO定制
- 支持顺序或随机读、写数据比例调整
- 支持测试末制定,可设置读写块大小、读写时间
- 支持读、写数据的一致性校验
- 支持自定义IO调度器(cfq、noop等IO调度算法)
- 可测试分布式集群文件系统(ceph、hdfs)
- 大量对CPU,内存,进程/线程,文件,IO特性的配置
二、在CentOS上安装fio测试工具
解压fio-2.1.10.tar
tar –xvf fio-2.1.10.tar
需要安装libaio作为io engine,只有libaio还不够,还需要安装libaio-devel
yum install libaio*
编译安装(需要先安装libaio,然后才能够编译fio,否则编译出的fio不会识别libaio引擎)
./configure && make && make install
三、使用fio进行测试
fio最初是用来节省为特定负载专门写测试程序,或者进行性能测试,或者找到和重现bug。
典型的fio工作过程
1)写一个job文件来描述要仿真的io负载,一个job文件可以控制产生任意数目的线程和文件。典型的job文件有一个global端(定义共享参数),一个或者多个job段(描述具体要产生的job)
2)运行时,fio从文件读取这些参数,作出处理,并根据这些参数描述,启动仿真线程/进程
运行方式:
fio job_file
fio会根据job_file文件中的内容来运行,可以在命令行中指定多个job file。fio会串行运行这些文件。
如果某个job file只包含一个job,可以在命令行中直接给出参数运行,不需要job file。命令行参数和job file参数的格式是一样的,在job file中的参数iodepth=2,在命令行中可以写成-iodepth 2或者-iodepth=2
fio不需要使用root来执行,除非使用到的文件或者设备需要root权限,一些选项可能会被限制。如内存锁、io调度器、nice value降级等。
job_file格式采用经典的ini文件,[]中的内容表示job name,可以采用任意的ascii字符,global除外,global有特殊的意义,global section描述了job file中各个job的默认配置项,一个job section可以覆盖global section中的参数,一个job file中可以包含几个global section,一个job只会受它上面的global section的影响。“;”和“#”用于注释。
两个进程,分别从一个128M文件中,随机读的job file
;–start job file–
[global]
rw=randread
size=128m
[job1]
[job2]
;–end job file–
job1和job2的section都是空的,因为所有的描述参数都是共享的,没有给出filename选项,fio会为每一个job创建一个文件名,如果用命令写,是:
fio -name=global -rw=randread -size=128M -name=job1 -name=job2
多个进程随机写文件
;–start job file —
[random-writers]
ioengine=libaio
iodepth=4
rw=randwrite
bs=32k
direct=0
size=64m
numjobs=4
;–end job file–
没有global section,只有一个job section。
此实例解释为:采用libaio引擎,io深度为4,随机写,块大小为32k,非direct io,四个进程每个进程随机写64M文件,也可以采用以下命令
fio -name=random-writers -ioengine=libaio -iodepth-4 -rw=randwrite -bs=32k -direct=0 -size=64m -numjobs=4
保留keywords
fio有一些保留keywords,在内部将其替换成合适的值,这些keywords是:
$pagesize 当前系统的页大小
$mb_memory 系统的总内存的大小,以MB为单位
$ncpus 在线有效的cpu数
这引起在命令行中和job file中都可以用,当job运行的时候,会自动的用当前系统的徝进行替换。支持简单的数学计算,如:
size=8*$mb_memory
四、fio选项说明
常用选项:
--direct=1:direct=1表示绕过机器自带的缓存,0表示使用缓存
--ioengine:表示能够使用的io引擎,可以通过fio --enghelp查看能够使用的io引擎
sync:基本的read,write。lseek定位
psync:基本的pread、pwrite
vsync:基本的readv,writev
libaio:linux专用异步io
posxaio:glibc posix异步io
solarisaio:solaris特有的异步io
mmap:文件通过内存映射到用户空间,使用memcpy写入和读取数据
splice:使用splice和vmsplice在用户空间和内核空间传输数据
syslet-rw 使用syslet 系统调用来构造普通的read/write异步IO
sg SCSI generic sg v3 io.可以是使用SG_IO ioctl来同步,或是目标是一个sg字符设备,我们使用read和write执行异步IO
null 不传输任何数据,只是伪装成这样。主要用于训练使用fio,或是基本debug/test的目的。
net 根据给定的host:port通过网络传输数据。根据具体的协议,hostname,port,listen,filename这些选项将被用来说明建立哪种连接,协议选项将决定哪种协议被使用。
netsplice 像net,但是使用splic/vmsplice来映射数据和发送/接收数据。
cpuio:不传输任何数据,但是要根据cpuload和cpucycle选项占用cpu周期,cpuload=85将使用job不做任何io,但是占用85%的cpu周期,在smp机器上,需要使用numjobs=来获取cpu,因为cpuload只会载入单个cpu
guasi GUASI IO引擎是一般的用于异步IO的用户空间异步系统调用接口
rdma RDMA I/O引擎支持RDMA内存语义(RDMA_WRITE/RDMA_READ)和通道主义(Send/Recv)用于InfiniBand,RoCE和iWARP协议
external 指明要调用一个外部的IO引擎(二进制文件)。e.g. ioengine=external:/tmp/foo.o将载入/tmp下的foo.o这个IO引擎
--thread:表示使用pthread_create代替fork,pthread_create是用来创建线程的,而fork是用来创建进程的。区别能够通过top和top -H看出来,如果不加--thread的话,numjobs设置多个的时候使用top能够看到存在多个fio进程,使用--thread之后,numjobs设置多个的时候使用top只能够看到一个fio进程,但是与此同时使用top -H能够看到存在多个fio线程
--filename:需要测试的文件名称,可以是一个指定的文件也可以是待测试的磁盘,如果不指定filename则fio会设定一个默认的名字,如果ioengine是net的话,文件名以host,port,protocol命令,如果ioengine是基于文件的话,可以通过:来分割一系列文件,如filename=/dev/sda:/dev/sdb打开两个文件
例如fio --name=global --rw=randread --size=128m --name=job1 --name=job2
会生成job1.0.0和job2.0.0两个文件
--name:用于输出信息的名字,如果不设置,fio输出信息将会采用job name,如果设置将会使用设置的名字
--rw:设置读写测试模式,可选randwrite表示测试随机写IO,randread表示测试随机读IO,randrw表示测试随机读写IO,read表示顺序读,write表示顺序写,rw/readwrite表示顺序混合读写
对于混合io类型,默认是50%的读,50%的写.
如果在rw的参数后面添加上一个整数,则有两种情况:
1)对于随机io读写而言,表示在下一次随机io之前进行读写的次数,具体情况需要配合rw_sequencer参数使用,例如
-rw=randread:8 -rw_sequencer=sequential
表示每8次io才会产生一个新的随机io,默认是每次io之后都会产生一个随机io,现在是执行8次的连续io之后才会产生一次随机io
-rw=randread:8 -rw_sequencer=identical
表示会产生8次相同的offset,然后才会生成一个新的offset,就是8次相同的偏移之后才产生下一次随机
2)对于顺序io而言,表示offset的大小
-rw=write:4k
表示每次写之后会跳过4k,将顺序的io转换为带洞的io
--rw_sequencer:控制rw后面offset的含义,可以接收的值是:sequential表示产生顺序offset,identical表示产生相同offset
--bs:单次IO的块文件大小,默认为4k。如果是一个值的话,表示对读写都生效,如果使用一个逗号写两个值的话,则前面一个表示读的大小,后面一个表示写的大小
bs=4k,8k表示读采用4k,写采用8k
--bsrange=512-2048:单次IO块文件大小范围
--size:测试io的数据大小,fio将会执行到size大小的数据都传输完成,除非设定了运行时间runtime,除非有设定nrfiles和filesize选项,fio将会在job定义的文件中平分这个大小,如果这个值不设置,fio将会使用这个文件或者设备的总大小,如果文件不存在,size选项一定要给出,也可以给出一个1到100的百分比,表示fio将会使用给定文件或设备的百分比空间。
--iodepth:如果io引擎是异步的,我们需要设定io的队列深度,默认是1,可以设置一个更大的值来提供高并发,iodepth大于1不会影响同步io
--direct=bool:true表示采用no buffered io。ZFS和Solaris不支持direct io,在windows同步IO引擎不支持direct io
--buffered=bool:true表示采用buffered io,默认是true,是direct的反义词
--nrfile=int:用于job的文件数目,默认是1
--openfiles=int:在同一时间内可以同时打开的文件数目,默认是nrfiles一致的,可以设置小一些来限制同时打开的文件数目
--numjobs=int:创建job数目,可以创建大量的进程/线程执行
--description=str:job的说明信息,只在输出文件描述信息的时候才有作用
--directory=str:使用文件的路径前缀,默认是.
--opendir=str:fio递归添加目录下和子目录下的所有文件
--lockfile=str:fio在文件上执行io之前默认是不加文件锁的,这样的话,当多个线程在这个文件上执行io,会造成结果的不一致,这个选项可以用来共享文件的负载,支持的锁类型:
none 默认不使用锁
exclusive:排它锁
readwrite:读写锁
在后面可以添加一个数字后缀,表示每个线程将会执行这个数字指定的io之后才会放弃锁,因为锁的开销是很大的,这种方式可以加速io
--kb_base=int:size转换单位,1000或1024,默认是1024
--fallocate=str:如何准备测试文件,
none 不执行预分配空间
posix 通过posix_fallocate()预分配空间
keep 通过fallocate()预分配空间
0 none的别名,出于兼容性
1 posix的别名,出于兼容性
并不是在所有的平台上都有效,‘keep’仅在linux上有效,ZFS不支持。默认为‘posix’
--filesize:单个文件的大小,可以是一个范围,在这种情况下,fio将会在一个范围内选择一个大小来决定单个文件大小,如果没有设置,所有文件将会是相同的大小
--file_device=bool,fill_fs=bool:填满空间直到终止条件ENOSPC,只对顺序写有意义,对于读,首先要填满挂载点然后才启动io,对于裸设备没有意义,因为它的大小系统已经知道了。如果写超出文件不会返回ENOSPC。
--blockalign=int,ba=int:配置随机io的对齐边界,默认是和blocksize一致的,对于随机map来说没有作用。
--bssplit=str:可以更加精确的控制block size,可以用来定义各个块大小所占的权重,格式是
bssplit=blocksize/percentage;blocksize/percentage
bssplit=4k/10:64k/50:32k/40
表示产生50% 54k的块,10% 4k的块,40% 32k的块。
可以分别为读写设置
bssplit=2k/50:4k/50,4k/90:8k/10
产生这样的负载:读(50% 64k的块,50% 4k的块),写(90% 4k的块, 10% 8k的块)
--blocksize_unaligned,bs_unaligned:如果这个选项被设置的,在bsrange范围内的大小都可以产生,这个选项对于direct io没有作用,因为对于direct io至少需要扇区对齐
--zero_buffers:如果这个选项设置的话,IO buffer全部位将被初始为0,如果没有置位的话,将会是随机数.
--refill_buffers:如果这个选项设置的话,fio将在每次submit之后都会将重新填满IO buffer,默认都会在初始是填满,以后重复利用。这个选项只有在zero_buffers没有设置的话,这个选项才有作用。
--rwmixread=int:混合读写中,读占的百分比
--rwmixwrite=int:混合读写中,写占的百分比;如果rwmixread=int和rwmixwrite=int同时使用的话并且相加不等于100%的话,第二个值将会覆盖第一个值。这可能要干扰比例的设定,如果要求fio来限制读和写到一定的比率。在果在这种情况下,那么分布会的有点的不同。
--nice=int:根据给定的nice值来运行这个job
--prio=int:设置job的优先级,linux将这个值限制在0-7之间,0是最高的。
--thinktime=int:上一个IO完成之后,拖延x毫秒,然后跳到下一个。可以用来访真应用进行的处理。
--thinktime_blocks:只有在thinktime设置时才有效,控制在等等‘thinktime’的时间内产生多少个block,如果没有设置的话,这个值将是1,每次block后,都会将等待‘thinktime’us。
--rate=int:限制job的带宽。
e.g.rate=500k,限制读和写到500k/s
e.g.rate=1m,500k,限制读到1MB/s,限制写到500KB/s
e.g.rate=,500k , 限制写到500kb/s
e.g.rate=500k, 限制读到500KB/s
--ratemin=int:告诉fio尽最在能力来保证这个最小的带宽,如果不能满足这个需要,将会导致程序退出。
--rate_iops=int:将带宽限制到固定数目的IOPS,基本上同rate一样,只是独立于带宽.
--rate_iops_min=int:如果fio达不到这个IOPS的话,将会导致job退出。
--cpumask=int:设置job使用的CPU.给出的参数是一个掩码来设置job可以运行的CPU。所以,如果要允许CPU在1和5上的话,可以通过10进制数来设置(1
--cpus_allowed=str:功能同cpumask一样,但是允许通过一段文本来设置允许的CPU。e.g.上面的例子可是这样写cpus_allowed=1,5。这个选项允许设置一个CPU范围,如cpus_allowed=1,5,8-15
--startdelay=time:o启动几秒后再启动job。只有在job文件包含几个jobs时才有效,是为了将某个job延时几秒后执行。
--time_based:如果设置的话,即使file已被完全读写或写完,也要执行完runtime规定的时间。它是通过循环执行相同的负载来实现的。
--ramp_time=time:设定在记录任何性能信息之前要运行特定负载的时间。这个用来等性能稳定后,再记录日志结果,因此可以减少生成稳定的结果需要的运行时间。
--exitall:当一个job退出时,会终止运行其它的job,默认是等待所有的job都完成,FIO才退出,但有时候这并不是我们想要的。
--unlink=bool: 完成后将删除job产生的文件。默认是not,如果设置为true的话,将会花很多时间重复创建这些文件。
--loops=int:重复运行某个job多次,默认是1
--write_bw_log=str:在job file写这个job的带宽日志。可以在他们的生命周期内存储job的带宽数据。内部的fio_generate_plots脚本可以使用gnuplot将这些文本转化成图。
--write_lat_log=str:同write_bw_log类似,只是这个选项可以存储io提交,完成和总的响应时间。如果没有指定文件名,默认的文件名是jobname_type.log。即使给出了文件名,fio也会添加两种类型的log。
e.g.如果我们指定write_lat_log=foo
实际的log名将是foo_slat.log,foo_slat.log和foo_lat.log.这会帮助fio_generate_plot来自动处理log
--write_iops_log=str:类似于write_bw_log,但是写的是IOPS.如果没有给定文件名的话,默认的文件名是jobname_type.log。
--log_avg_msec=int:默认,fio每完成一个IO将会记录一个日志(iops,latency,bw log)。当向磁盘写日志的时候,将会很快变的很大。设置这个选项的话,fio将会在一定的时期内平均这些值,指少日志的数量,默认是0
--disk_util=bool:产生磁盘利用率统计信息。默认是打开的
--disable_lat=bool:是否统计延迟
--disable_clat=bool
--disable_slat_bool
--disable_bw=bool
--clat_percentiles=bool:允许报告完成完成响应时间的百分比
--uid=int:不是使用调用者的用户来执行,而是指定用户ID
--runtime=1000:测试时间为1000秒,如果不写则一直将size大小文件分bs大小的块写完为止.控制fio在执行设定的时间后退出执行。很难来控制单个job的运行时间,所以这个参数是用来控制总的运行时间。
--rwmixwrite=30:在混合读写的模式下,写占30%
--group_reporting:显示结果汇总每个进程的信息
--lockmem=1g:只使用1g内存进行测试
随机性选项:
--randrepeat:在随机读写的时候指定,如果指定了这个参数表示在随机读、随机写产生的位置是可重复的,默认设置是true
randrepeat & randseed=XX : 如果randseed设置了自定义的值,randrepeat将强制变成False,如果randseed没有设置值(默认137)randrepeat=1,那么这个设置不言而喻,相当于每次都用同样的randseed,当然就是randrepeatable的测试。
randrepeat=0 & randseed=None: 如果测试的时候randrepeat设置为false,同时也没有指定seed,这个时候randseed就是去读/dev/urandom文件获取一个随机的seed
--norandommap:一般情况下,fio做随机IO将会覆盖文件的每一个block,如果设置这个选项,fio在选择新的block的时候不会查询历史,也就意味着可能有一些块没有读或者写,有一些块会读写很多次
--softrandommap:默认是disable,如果设置为1,表示:因为randommap是需要使用内存来做历史记录的,但是在某些情况下可能会出现allocate bit map内存失败的情况,那么测试会自动变为norandommap,并且不会报错,softrandommap如果设置为disable,fio在分配内存失败之后会报错退出。
--random_distribution:随机分布参数,fio可以模拟不同类型的随机分布,可以使某些区域的随机概率高,某些区域的随机概率低,模拟随机数据的冷热性。
可以取值:
random:均匀分布
zipf:zipf分布
pareto:帕累托分布
zoned:直接某个区域内指定io多少
narmal:高斯分布
后面这几个分布各有不同,其实达到的目的都很类似,如果使用了非均匀分布的参数,会强制指定norandommap=1,这些非均匀分布和randommap要实现的目的是冲突的。
--group_reporting:如果‘numjobs’设置的话,我们感兴趣的可能是打印group的统计值,而不是一个单独的job。这在‘numjobs’的值很大时,一般是设置为true的,可以减少输出的信息量。如果‘group_reporting’设置的话,fio将会显示最终的per-groupreport而不是每一个job都会显示
--use_os_rand=bool:fio可以使用操作系统的随机数生成器,也可以使用fio内部的随机数生成器,默认采用fio内部的生成器,质量更好,速度更快
fio测试结果中各项含义:
bw:带宽 kb/s
iops:每秒IO数目
runt:总运行时间
lat(msec):延迟(毫秒)
当fio完成的时候(或是通过ctrl-c终止的时候),将会打印每一个线程的数据,每个group的数据,和磁盘数据。
io= 执行了多少M的IO
bw= 平均IO带宽
iops= IOPS
runt= 线程运行时间
slat 提交延迟
clat 完成延迟
lat响应时间
bw 带宽
cpu利用率
IO depths=io队列
IO submit=单个IO提交要提交的IO数
IO complete= Like the above submit number, but for completions instead.
IO issued= The number of read/write requests issued, and how many
of them were short.
IO latencies=IO完延迟的分布
io= 总共执行了多少size的IO
aggrb= group总带宽
minb= 最小.平均带宽.
maxb= 最大平均带宽.
mint= group中线程的最短运行时间.
maxt= group中线程的最长运行时间.
ios= 所有group总共执行的IO数.
merge= 总共发生的IO合并数.
ticks= Number of ticks we kept the disk busy.
io_queue= 花费在队列上的总共时间.
util= 磁盘利用率
fio混合随机读写测试
fio -filename=/dev/sdb -direct=1 -iodepth 1 -thread -rw=randrw -rwmixread=70 -ioengine=psync -bs=16k -size=200G -numjobs=30 -runtime=100 -group_reporting -name=mytest1
测试解读:
测试磁盘/dev/sdb,忽略系统buffer,io深度为1,使用pthread_create替代fork,测试模式为随机读写,测试中读比例占70%,io引擎使用psync,测试单块大小为16k,测试总大小为200G,线程数为30,运行时间为100s,采用合并report,测试范例名称为mytest1
mytest1: (g=0): rw=randrw, bs=16K-16K/16K-16K/16K-16K, ioengine=psync, iodepth=1
...
fio-2.1.10
Starting 30 threads
Jobs: 30 (f=30): [mmmmmmmmmmmmmmmmmmmmmmmmmmmmmm] [100.0% done] [291.1MB/126.7MB/0KB /s] [18.7K/8106/0 iops] [eta 00m:00s]
mytest1: (groupid=0, jobs=30): err= 0: pid=76884: Wed Mar 13 11:26:47 2019
read : io=29423MB, bw=301284KB/s, iops=18830, runt=100003msec
clat (usec): min=32, max=7585, avg=1540.58, stdev=781.56
lat (usec): min=33, max=7585, avg=1540.66, stdev=781.56
clat percentiles (usec):
| 1.00th=[ 772], 5.00th=[ 876], 10.00th=[ 916], 20.00th=[ 980],
| 30.00th=[ 1004], 40.00th=[ 1048], 50.00th=[ 1144], 60.00th=[ 1384],
| 70.00th=[ 1640], 80.00th=[ 2256], 90.00th=[ 2960], 95.00th=[ 3088],
| 99.00th=[ 3504], 99.50th=[ 3728], 99.90th=[ 4192], 99.95th=[ 4448],
| 99.99th=[ 5728]
bw (KB /s): min= 7040, max=17824, per=3.34%, avg=10051.32, stdev=853.11
write: io=12631MB, bw=129340KB/s, iops=8083, runt=100003msec
clat (usec): min=33, max=2385, avg=119.53, stdev=94.54
lat (usec): min=33, max=2385, avg=119.75, stdev=94.54
clat percentiles (usec):
| 1.00th=[ 57], 5.00th=[ 64], 10.00th=[ 71], 20.00th=[ 77],
| 30.00th=[ 82], 40.00th=[ 87], 50.00th=[ 91], 60.00th=[ 97],
| 70.00th=[ 106], 80.00th=[ 125], 90.00th=[ 207], 95.00th=[ 274],
| 99.00th=[ 628], 99.50th=[ 700], 99.90th=[ 908], 99.95th=[ 1032],
| 99.99th=[ 1352]
bw (KB /s): min= 2804, max= 7456, per=3.34%, avg=4315.92, stdev=454.09
lat (usec) : 50=0.13%, 100=19.22%, 250=9.00%, 500=1.58%, 750=0.61%
lat (usec) : 1000=18.06%
lat (msec) : 2=36.37%, 4=14.91%, 10=0.13%
cpu : usr=0.20%, sys=0.89%, ctx=2691503, majf=0, minf=447
IO depths : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
issued : total=r=1883084/w=808399/d=0, short=r=0/w=0/d=0
latency : target=0, window=0, percentile=100.00%, depth=1
Run status group 0 (all jobs):
READ: io=29423MB, aggrb=301284KB/s, minb=301284KB/s, maxb=301284KB/s, mint=100003msec, maxt=100003msec
WRITE: io=12631MB, aggrb=129339KB/s, minb=129339KB/s, maxb=129339KB/s, mint=100003msec, maxt=100003msec
Disk stats (read/write):
sdb: ios=1881566/807801, merge=0/0, ticks=2884240/91080, in_queue=2976440, util=100.00%
结果主要看上面红色部分,表示带宽和iops.