Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/cgroup/iocost_coef_gen.py
26282 views
1
#!/usr/bin/env python3
2
#
3
# Copyright (C) 2019 Tejun Heo <[email protected]>
4
# Copyright (C) 2019 Andy Newell <[email protected]>
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}')
179
180