]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/blob - tools/cgroup/iocost_coef_gen.py
Merge tag 'for-linus' of git://git.kernel.org/pub/scm/virt/kvm/kvm
[mirror_ubuntu-hirsute-kernel.git] / tools / cgroup / iocost_coef_gen.py
1 #!/usr/bin/env python3
2 #
3 # Copyright (C) 2019 Tejun Heo <tj@kernel.org>
4 # Copyright (C) 2019 Andy Newell <newella@fb.com>
5 # Copyright (C) 2019 Facebook
6
7 desc = """
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.
13
14 On high performance devices, --numjobs > 1 is needed to achieve
15 saturation.
16
17 See Documentation/admin-guide/cgroup-v2.rst and block/blk-iocost.c
18 for more details.
19 """
20
21 import argparse
22 import re
23 import json
24 import glob
25 import os
26 import sys
27 import atexit
28 import shutil
29 import tempfile
30 import subprocess
31
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')
50
51 def info(msg):
52 if not args.quiet:
53 print(msg)
54
55 def dbg(msg):
56 if args.verbose and not args.quiet:
57 print(msg)
58
59 # determine ('DEVNAME', 'MAJ:MIN') for @path
60 def dir_to_dev(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()
65
66 # partition -> whole device
67 parents = glob.glob('/sys/block/*/' + devname)
68 if len(parents):
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)}')
72
73 def create_testfile(path, size):
74 global args
75
76 if os.path.isfile(path) and os.stat(path).st_size == size:
77 return
78
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',
87 shell=True)
88
89 def run_fio(testfile, duration, iotype, iodepth, blocksize, jobs):
90 global args
91
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}')
99 if args.verbose:
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'])
105
106 def restore_elevator_nomerges():
107 global elevator_path, nomerges_path, elevator, nomerges
108
109 info(f'Restoring elevator to {elevator} and nomerges to {nomerges}')
110 with open(elevator_path, 'w') as f:
111 f.write(elevator)
112 with open(nomerges_path, 'w') as f:
113 f.write(nomerges)
114
115
116 args = parser.parse_args()
117
118 missing = False
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)
122 missing = True
123 if missing:
124 sys.exit(1)
125
126 if args.testdev:
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})')
132 else:
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})')
138
139 elevator_path = f'/sys/block/{devname}/queue/scheduler'
140 nomerges_path = f'/sys/block/{devname}/queue/nomerges'
141
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()
146
147 info(f'Temporarily disabling elevator and merges')
148 atexit.register(restore_elevator_nomerges)
149 with open(elevator_path, 'w') as f:
150 f.write('none')
151 with open(nomerges_path, 'w') as f:
152 f.write('1')
153
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)
175 info('')
176
177 print(f'{devno} rbps={rbps} rseqiops={rseqiops} rrandiops={rrandiops} '
178 f'wbps={wbps} wseqiops={wseqiops} wrandiops={wrandiops}')