fio是平时常用的工具之一,然而它有很多参数用于定制不同的压力模式,输出也包含很多信息,之前没有仔细研究。本文搞清楚一些常用参数所定制的行为,并且详细解读一下输出的信息。
安装最新的fio (1)
1 | yum install -y libaio libaio-devel |
job definition和job instance (2)
1 | cat jobfile1 |
- job definition: jobfile1中的randwrite_job和seqread_job;它们是静态的,描述I/O的特征(顺序写还是随机写,ioengine,大小等等),可以产生多个job instance;
- job instance: 运行时产生的job实例(fork)。运行上面的jobfile1会产生5个job instance,其中randwrite_job产生3个(numjobs=3),seqread_job产生2个(numjobs=2);同一个job definition产生的job实例有着同样的I/O特征;
1 | 终端A: |
值得一提的是,上面一共有6个fio进程;因为其中一个是主控进程(30566),其他5个才是job实例进程;
常用的fio选项 (3)
size选项(3.1)
它的值有两种形式:
- 绝对大小,例如10M,20G;
- 百分比,例如20%;需要文件事先存在;
无论哪种形式都是指定一个job实例读写的空间(多个job实例的情况下每个job实例都读写这么大的空间);fio运行时间取决于–runtime指定的时间和读写这么多空间所需时间二者中的最小者。
bs 选项(3.2)
I/O单元的大小。
rw选项(3.3)
- write: 顺序写;
- randwrite: 随机写;
- read: 顺序读;
- randread: 随机读;
- rw: 顺序混合读写;
- randrw: 随机混合读写;
ioengine选项 (3.4)
libaio (3.4.1)
即linux native asynchronous I/O,也就是使用io_submit提交I/O请求,然后再异步地使用io_getevents获取结果。可以通过strace来看实际的系统调用。
1 | 终端A: |
sync (3.4.2)
这个名字有点误导,它虽然叫做sync,但并不意味着文件以O_SYNC的方式打开,也不意味着每个write之后会调用fsync/fdatasync操作。实际上,这个sync和libaio是相对的概念,不是先提交I/O请求(io_submit)再异步获取结果(io_getevents),而是使用read/write这样的系统调用来完成I/O。到底write之后会不会调用fsync,要看–fsync或者–fdatasync的设置;
psync (3.4.3)
和sync类似,不同之处在于使用pread和pwrite来进行I/O。
iodepth选项 (3.5)
简单来说,就是一个job实例在一个文件上的inflight的I/O的数。考虑–ioengine=libaio的情景:把I/O请求通过io_submit发出去然后通过io_getevents获取结果,这样一个job实例就可以保持有多个inflight I/O。但是对于–ioengine=sync或者psync,一个job实例只能顺序地调用read/write(pread/pwrite),也就是只能保持一个I/O inflight,所以对于–ioengine=sync或者–ioengine=psync设置iodepth为大于1的值不起作用。对比一下:
- libaio
1 | 终端A: |
IO depths: 1=0.1%, 2=100.0%
表明iodepth为2。iostat显示avgqu-sz接近6。我们有3个job实例,故每个job实例的iodepth接近2;
- sync
1 | 终端A: |
IO depths: 1=100.0%, 2=0.0%
表明iodepth为1;iostat显示avgqu-sz接近3。我们有3个job实例,故每个job实例的iodepth接近1;
direct选项 (3.6)
这个选项决定:打开文件时带不带O_DIRECT标记;
1 | strace -f ./fio --name=seqwrite \ |
值得注意的是,ioengine=libaio时,一定要direct=1。因为目前(2018年),libaio只支持direct I/O:libaio只在O_DIRECT的情况下是异步的,在没有O_DIRECT的情况下可能会blocking;
sync选项 (3.7)
和direct选项类似,它决定的是:打开文件时带不带O_SYNC标记;
1 | strace -f ./fio --name=seqwrite \ |
fsync和fdatasync选项 (3.8)
如第3.4.2节所说,ioengine=sync或psync,有点误导,因为它不会调用fsync或fdatasync操作。要想在write后调用fsync或者fdatasync,需要加水–fsync=N或者–fdatasync=N。这里的N表示每N个write之后调用fsync或者fdatasync一次。
1 | strace -f ./fio --name=seqwrite \ |
关于fsync/fdatasync需要注意:fsync/fdatasync的耗时不包含在write latency之内。新版本的fio(例如fio-3.13和fio-3.14)单独显示fsync/fdatasync的耗时,而老版本不显示fsync/fdatasync的耗时(这有点使人迷惑:write latency很小——因为不包含fsync/fdatasync耗时——但IOPS又不高,你会疑惑时间花在哪里了)。
有关sync的选项 (3.9)
从前几节可以看到,和sync相关的选项有:
- –direct,
- –sync,
- –fsync(fdatasync);
而常用的ioengine有两种: - libaio
- sync(psync)。
如何组合使用呢?
- ioengine=libaio: –direct=1是必须的(见3.6节),所以,–fsync(fdatasync)就不需要了;而–sync可以和–direct组合使用,但一般测试裸盘性能直接用–direct就可以了。
- ioengine=sync(psync): 可以选择–direct或者–fsync(fdatasync),选择–direct时可以和–sync组合使用。
所以共有下面几种组合:
- –ioengine=libaio –direct=1
- –ioengine=sync(psync) –direct=1
- –ioengine=sync(psync) –direct=1 –sync=1
- –ioengine=sync(psync) –fsync=1
解析fio的输出 (4)
运行时输出 (4.1)
1 | ./fio --name=randwrite \ |
第一个方括号:
- w: running, doing random writes
- W: running, doing sequential writes
- r: running, doing random reads
- R: running, doing sequential reads
- m: running, doing mixed random reads/writes
- M: running, doing mixed sequential reads/writes
- C: thread created
- f: thread finishing
其他字段比较直观;
运行后输出 (4.2)
1 | 终端A: |
如jobfile2所示,我们有两个job definition:randrw_job和seqwrite_job:
randrw_job有2个job实例:
- groupid=0 pid=32197: 记作g0-j0
- groupid=0 pid=32198: 记作g0-j1
seqwrite_job有3个job实例;
- groupid=1 pid=32199: 记作g1-j0
- groupid=1 pid=32200: 记作g1-j1
- groupid=1 pid=32201: 记作g1-j2
一个job实例的输出 (4.2.1)
我们以g0-j0为例,看输出结果:
- job头
1 | randrw_job: (groupid=0, jobs=1): err= 0: pid=32197: Thu Jun 20 21:00:52 2019 |
显示了所属group,pid,运行时间等;
- job read统计信息
1 | read: IOPS=25, BW=102KiB/s (105kB/s)(3072KiB/30069msec) |
BW表示平均带宽,KiB/s是按1K=1024计算的,kB/s是按1K=1000计算的;别的无须多说;
1 | slat (nsec): min=4625, max=47380, avg=8140.65, stdev=3172.10 |
提交延迟,s代表submission;min, max, avg分别代表最小、最大和平均值。stdev表示标准差(standard deviation),越大代表波动越大。注意slat只在–ioengine=libaio的时候才会出现,因为对于–ioengine=sync/psync没有所谓的提交延迟。
1 | clat (msec): min=6, max=702, avg=70.09, stdev=50.63 |
完成延迟,c代表completion;和slat一样,min, max, avg分别代表最小,最大和平均值,stdev表示标准差(standard deviation)。对于–ioengine=libaio,clat表示从提交到完成的延迟。对于–ioengine=sync/psync,fio文档中说clat等于或非常接近于0(因为提交就相当于完成)。但从实验上看,不是这样的:对于–ioengine=sync/psync,不显示slat,clat接近总延迟。
1 | lat (msec): min=6, max=702, avg=70.10, stdev=50.63 |
总延迟,即从I/O被创建到完成的延迟。大致是这样(对吗?):
- 对于–ioengine=libaio: lat = latency(创建到提交) + slat + clat
- 对于–ioengine=sync/psync: lat = latency(创建到开始) + clat
1 | clat percentiles (msec): |
clat的百分位数。1%的clat在11ms以内;5%在16ms以内;10%的在21ms以内;99.9%在701ms以内;99.99%在701ms以内;
1 | bw ( KiB/s): min= 48, max= 208, per=50.59%, avg=102.20, stdev=30.54, samples=60 |
基于采样得到的带宽统计(可以看见,与前面的BW=102KiB/s接近)。min, max, avg分别表示最小、最大和平均值;stdev表示标准差。samples表示采样数。per表示当前job实例(g0-j0)的带宽在组里所占的百分比,即g0-j0在groupid=0的组里(g0-j0和g0-j1), 读带宽占总读带宽的50.59%(g0-j1占49.83%)
1 | iops : min= 12, max= 52, avg=25.50, stdev= 7.66, samples=60 |
基于采样得到的iops统计(可以看见,与前面的IOPS=25接近)。和bw一样。
- job write统计信息
和read一样,不赘述;
- job 总体统计信息
1 | lat (usec) : 250=0.64%, 500=1.41%, 750=0.32%, 1000=0.19% |
所有I/O的总体延迟分布,包含read和write。
- lat在[0us,250us)区间内的I/O占0.64%;
- lat在[250us,500us)区间内的I/O占1.41%;
- lat在[500us,750us)区间内的I/O占0.32%;
- lat在[750us,1ms)区间的I/O占0.19%;
- lat在[1ms, 2ms)区间的I/O占1.28%;
- 以此类推;
注意和百分位数的区别。
1 | cpu : usr=0.07%, sys=0.06%, ctx=1293, majf=0, minf=36 |
CPU使用情况。usr表示用户态占比;sys表示内核态占比;ctx表示这个job实例/进程(g0-j0)经历的context switch数;majf和minf分别表示major and minor page faults。
1 | IO depths : 1=0.1%, 2=99.9%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0% |
IO depth的分布。
- 1=表示1-2的占比;
- 2=表示2-4的占比;
- 4=表示4-8的占比;
- 以此类推;
1 | submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% |
在一个submit调用里,提交了多少I/O。4=表示0-4区间内的占比;8=表示4-8区间内的占比;以此类推;不清楚。
1 | complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% |
和submit类似;不清楚。
1 | issued rwts: total=768,792,0,0 short=0,0,0,0 dropped=0,0,0,0 |
总共发出了多少read/write/trim/x请求,有多少short(read, write, send, recv等返回大小小于请求大小),有多少被dropped。这一行需要和后面的Disk stats (read/write)
里的sdd: ios=1637/4947
对照来看:在本例中,g0-j0的读是768;g0-j1的读是757;g1-j0,g1-j1,g1-j2没有读操作,所以加在一起是1525,接近sdd: ios=1637/4947
里面的1637。
注意:1. 写操作加起来(g0-j0,g0-j1,g1-j0,g1-j1,g1-j2)并不相等,因为g1写的是1m大小,可能分成多个I/O进行的;2. 另外,在文件系统中测试时,这个关系也不一样,可能有元数据操作;
1 | latency : target=0, window=0, percentile=100.00%, depth=3 |
和latency_target相关,暂忽略。
group统计 (4.2.2)
1 | Run status group 0 (all jobs): |
group 0 READ:
- bw=203KiB/s (208kB/s):所有job实例(g0-j0,g0-j1)的聚合带宽(分别以1K=1024和1K=1000计算);
- 101KiB/s-102KiB/s (103kB/s-105kB/s):所有job实例中的最小带宽和最大带宽(分别以1K=1024和1K=1000计算);从前面输出可以看出,g0-j0的带宽是102KiB/s;g0-j1的带宽是101KiB/s;
- io=6100KiB (6246kB):所有job实例的聚合传输量(分别以1K=1024和1K=1000计算);
- run=30069-30080msec:所有job实例中最短和最长的运行时间;从前面全部输出中可以看出,g0-j0的运行时间是30069msec,g0-j1的运行时间是30080msec;
group 1 WRITE:
- bw=18.9MiB/s (19.8MB/s):所有job实例(g1-j0,g1-j1,g1-j2)的聚合带宽(分别以1M=1024*1024和1M=1000*1000计算);
- 6428KiB/s-6500KiB/s (6582kB/s-6656kB/s):所有job实例中的最小带宽和最大带宽(分别以1K=1024和1K=1000计算);从前面输出可以看出,g1-j2的带宽是6428KiB/s,最小;g1-j0的带宽是6500KiB/s,最大;
- io=570MiB (598MB):所有job实例的聚合传输量(分别以1M=1024*1024和1M=1000*1000计算);
- run=30091-30110msec:所有job实例中最短和最长的运行时间;从前面全部输出中可以看出,g1-j0的运行时间是30091msec,最小;g1-j1和g1-j2都是30110msec,最大;
磁盘统计 (4.2.3)
1 | Disk stats (read/write): |
- ios=1637/4947:iostat的
r/s
和w/s
在运行时间上的累积; - merge=0/140809:iostat的
rrqm/s
和wrqm/s
在运行时间上的累积; - ticks=107529/282374:disk忙着的ticks数;
- in_queue=391910:所有I/O在disk queue中花费的ticks总数;
- util:iostat的util;
说明:上例中,为了看清各个job实例的统计信息,没有加group_reporting;实际使用中,同一个job definition产生的job实例特征相同,所以,把它们的统计聚合起来更容易看。new_group=1为每个job definition创建一个分组(它的job实例都属于次分组);group_reporting=1按组聚合统计;
小结 (5)
记录了fio常用参数以及输出解读。