3 # Copyright (C) 2019 Tejun Heo <tj@kernel.org>
4 # Copyright (C) 2019 Andy Newell <newella@fb.com>
5 # Copyright (C) 2019 Facebook
8 Generate linear IO cost model coefficients used by the blk-iocost
9 controller. If the target raw testdev is specified, destructive tests
10 are performed against the whole device; otherwise, on
11 ./iocost-coef-fio.testfile. The result can be written directly to
12 /sys/fs/cgroup/io.cost.model.
14 On high performance devices, --numjobs > 1 is needed to achieve
17 See Documentation/admin-guide/cgroup-v2.rst and block/blk-iocost.c
32 parser
= argparse
.ArgumentParser(description
=desc
,
33 formatter_class
=argparse
.RawTextHelpFormatter
)
34 parser
.add_argument('--testdev', metavar
='DEV',
35 help='Raw block device to use for testing, ignores --testfile-size')
36 parser
.add_argument('--testfile-size-gb', type=float, metavar
='GIGABYTES', default
=16,
37 help='Testfile size in gigabytes (default: %(default)s)')
38 parser
.add_argument('--duration', type=int, metavar
='SECONDS', default
=120,
39 help='Individual test run duration in seconds (default: %(default)s)')
40 parser
.add_argument('--seqio-block-mb', metavar
='MEGABYTES', type=int, default
=128,
41 help='Sequential test block size in megabytes (default: %(default)s)')
42 parser
.add_argument('--seq-depth', type=int, metavar
='DEPTH', default
=64,
43 help='Sequential test queue depth (default: %(default)s)')
44 parser
.add_argument('--rand-depth', type=int, metavar
='DEPTH', default
=64,
45 help='Random test queue depth (default: %(default)s)')
46 parser
.add_argument('--numjobs', type=int, metavar
='JOBS', default
=1,
47 help='Number of parallel fio jobs to run (default: %(default)s)')
48 parser
.add_argument('--quiet', action
='store_true')
49 parser
.add_argument('--verbose', action
='store_true')
56 if args
.verbose
and not args
.quiet
:
59 # determine ('DEVNAME', 'MAJ:MIN') for @path
61 # find the block device the current directory is on
62 devname
= subprocess
.run(f
'findmnt -nvo SOURCE -T{path}',
63 stdout
=subprocess
.PIPE
, shell
=True).stdout
64 devname
= os
.path
.basename(devname
).decode('utf-8').strip()
66 # partition -> whole device
67 parents
= glob
.glob('/sys/block/*/' + devname
)
69 devname
= os
.path
.basename(os
.path
.dirname(parents
[0]))
70 rdev
= os
.stat(f
'/dev/{devname}').st_rdev
71 return (devname
, f
'{os.major(rdev)}:{os.minor(rdev)}')
73 def create_testfile(path
, size
):
76 if os
.path
.isfile(path
) and os
.stat(path
).st_size
== size
:
79 info(f
'Creating testfile {path}')
80 subprocess
.check_call(f
'rm -f {path}', shell
=True)
81 subprocess
.check_call(f
'touch {path}', shell
=True)
82 subprocess
.call(f
'chattr +C {path}', shell
=True)
83 subprocess
.check_call(
84 f
'pv -s {size} -pr /dev/urandom {"-q" if args.quiet else ""} | '
85 f
'dd of={path} count={size} '
86 f
'iflag=count_bytes,fullblock oflag=direct bs=16M status=none',
89 def run_fio(testfile
, duration
, iotype
, iodepth
, blocksize
, jobs
):
92 eta
= 'never' if args
.quiet
else 'always'
93 outfile
= tempfile
.NamedTemporaryFile()
94 cmd
= (f
'fio --direct=1 --ioengine=libaio --name=coef '
95 f
'--filename={testfile} --runtime={round(duration)} '
96 f
'--readwrite={iotype} --iodepth={iodepth} --blocksize={blocksize} '
97 f
'--eta={eta} --output-format json --output={outfile.name} '
98 f
'--time_based --numjobs={jobs}')
100 dbg(f
'Running {cmd}')
101 subprocess
.check_call(cmd
, shell
=True)
102 with
open(outfile
.name
, 'r') as f
:
103 d
= json
.loads(f
.read())
104 return sum(j
['read']['bw_bytes'] + j
['write']['bw_bytes'] for j
in d
['jobs'])
106 def restore_elevator_nomerges():
107 global elevator_path
, nomerges_path
, elevator
, nomerges
109 info(f
'Restoring elevator to {elevator} and nomerges to {nomerges}')
110 with
open(elevator_path
, 'w') as f
:
112 with
open(nomerges_path
, 'w') as f
:
116 args
= parser
.parse_args()
119 for cmd
in [ 'findmnt', 'pv', 'dd', 'fio' ]:
120 if not shutil
.which(cmd
):
121 print(f
'Required command "{cmd}" is missing', file=sys
.stderr
)
127 devname
= os
.path
.basename(args
.testdev
)
128 rdev
= os
.stat(f
'/dev/{devname}').st_rdev
129 devno
= f
'{os.major(rdev)}:{os.minor(rdev)}'
130 testfile
= f
'/dev/{devname}'
131 info(f
'Test target: {devname}({devno})')
133 devname
, devno
= dir_to_dev('.')
134 testfile
= 'iocost-coef-fio.testfile'
135 testfile_size
= int(args
.testfile_size_gb
* 2 ** 30)
136 create_testfile(testfile
, testfile_size
)
137 info(f
'Test target: {testfile} on {devname}({devno})')
139 elevator_path
= f
'/sys/block/{devname}/queue/scheduler'
140 nomerges_path
= f
'/sys/block/{devname}/queue/nomerges'
142 with
open(elevator_path
, 'r') as f
:
143 elevator
= re
.sub(r
'.*\[(.*)\].*', r
'\1', f
.read().strip())
144 with
open(nomerges_path
, 'r') as f
:
145 nomerges
= f
.read().strip()
147 info(f
'Temporarily disabling elevator and merges')
148 atexit
.register(restore_elevator_nomerges
)
149 with
open(elevator_path
, 'w') as f
:
151 with
open(nomerges_path
, 'w') as f
:
154 info('Determining rbps...')
155 rbps
= run_fio(testfile
, args
.duration
, 'read',
156 1, args
.seqio_block_mb
* (2 ** 20), args
.numjobs
)
157 info(f
'\nrbps={rbps}, determining rseqiops...')
158 rseqiops
= round(run_fio(testfile
, args
.duration
, 'read',
159 args
.seq_depth
, 4096, args
.numjobs
) / 4096)
160 info(f
'\nrseqiops={rseqiops}, determining rrandiops...')
161 rrandiops
= round(run_fio(testfile
, args
.duration
, 'randread',
162 args
.rand_depth
, 4096, args
.numjobs
) / 4096)
163 info(f
'\nrrandiops={rrandiops}, determining wbps...')
164 wbps
= run_fio(testfile
, args
.duration
, 'write',
165 1, args
.seqio_block_mb
* (2 ** 20), args
.numjobs
)
166 info(f
'\nwbps={wbps}, determining wseqiops...')
167 wseqiops
= round(run_fio(testfile
, args
.duration
, 'write',
168 args
.seq_depth
, 4096, args
.numjobs
) / 4096)
169 info(f
'\nwseqiops={wseqiops}, determining wrandiops...')
170 wrandiops
= round(run_fio(testfile
, args
.duration
, 'randwrite',
171 args
.rand_depth
, 4096, args
.numjobs
) / 4096)
172 info(f
'\nwrandiops={wrandiops}')
173 restore_elevator_nomerges()
174 atexit
.unregister(restore_elevator_nomerges
)
177 print(f
'{devno} rbps={rbps} rseqiops={rseqiops} rrandiops={rrandiops} '
178 f
'wbps={wbps} wseqiops={wseqiops} wrandiops={wrandiops}')