xref: /openbmc/linux/tools/cgroup/iocost_coef_gen.py (revision 976e3645923bdd2fe7893aae33fd7a21098bfb28)
1*8504dea7STejun Heo#!/usr/bin/env python3
2*8504dea7STejun Heo#
3*8504dea7STejun Heo# Copyright (C) 2019 Tejun Heo <tj@kernel.org>
4*8504dea7STejun Heo# Copyright (C) 2019 Andy Newell <newella@fb.com>
5*8504dea7STejun Heo# Copyright (C) 2019 Facebook
6*8504dea7STejun Heo
7*8504dea7STejun Heodesc = """
8*8504dea7STejun HeoGenerate linear IO cost model coefficients used by the blk-iocost
9*8504dea7STejun Heocontroller.  If the target raw testdev is specified, destructive tests
10*8504dea7STejun Heoare performed against the whole device; otherwise, on
11*8504dea7STejun Heo./iocost-coef-fio.testfile.  The result can be written directly to
12*8504dea7STejun Heo/sys/fs/cgroup/io.cost.model.
13*8504dea7STejun Heo
14*8504dea7STejun HeoOn high performance devices, --numjobs > 1 is needed to achieve
15*8504dea7STejun Heosaturation.
16*8504dea7STejun Heo
17*8504dea7STejun HeoSee Documentation/admin-guide/cgroup-v2.rst and block/blk-iocost.c
18*8504dea7STejun Heofor more details.
19*8504dea7STejun Heo"""
20*8504dea7STejun Heo
21*8504dea7STejun Heoimport argparse
22*8504dea7STejun Heoimport re
23*8504dea7STejun Heoimport json
24*8504dea7STejun Heoimport glob
25*8504dea7STejun Heoimport os
26*8504dea7STejun Heoimport sys
27*8504dea7STejun Heoimport atexit
28*8504dea7STejun Heoimport shutil
29*8504dea7STejun Heoimport tempfile
30*8504dea7STejun Heoimport subprocess
31*8504dea7STejun Heo
32*8504dea7STejun Heoparser = argparse.ArgumentParser(description=desc,
33*8504dea7STejun Heo                                 formatter_class=argparse.RawTextHelpFormatter)
34*8504dea7STejun Heoparser.add_argument('--testdev', metavar='DEV',
35*8504dea7STejun Heo                    help='Raw block device to use for testing, ignores --testfile-size')
36*8504dea7STejun Heoparser.add_argument('--testfile-size-gb', type=float, metavar='GIGABYTES', default=16,
37*8504dea7STejun Heo                    help='Testfile size in gigabytes (default: %(default)s)')
38*8504dea7STejun Heoparser.add_argument('--duration', type=int, metavar='SECONDS', default=120,
39*8504dea7STejun Heo                    help='Individual test run duration in seconds (default: %(default)s)')
40*8504dea7STejun Heoparser.add_argument('--seqio-block-mb', metavar='MEGABYTES', type=int, default=128,
41*8504dea7STejun Heo                    help='Sequential test block size in megabytes (default: %(default)s)')
42*8504dea7STejun Heoparser.add_argument('--seq-depth', type=int, metavar='DEPTH', default=64,
43*8504dea7STejun Heo                    help='Sequential test queue depth (default: %(default)s)')
44*8504dea7STejun Heoparser.add_argument('--rand-depth', type=int, metavar='DEPTH', default=64,
45*8504dea7STejun Heo                    help='Random test queue depth (default: %(default)s)')
46*8504dea7STejun Heoparser.add_argument('--numjobs', type=int, metavar='JOBS', default=1,
47*8504dea7STejun Heo                    help='Number of parallel fio jobs to run (default: %(default)s)')
48*8504dea7STejun Heoparser.add_argument('--quiet', action='store_true')
49*8504dea7STejun Heoparser.add_argument('--verbose', action='store_true')
50*8504dea7STejun Heo
51*8504dea7STejun Heodef info(msg):
52*8504dea7STejun Heo    if not args.quiet:
53*8504dea7STejun Heo        print(msg)
54*8504dea7STejun Heo
55*8504dea7STejun Heodef dbg(msg):
56*8504dea7STejun Heo    if args.verbose and not args.quiet:
57*8504dea7STejun Heo        print(msg)
58*8504dea7STejun Heo
59*8504dea7STejun Heo# determine ('DEVNAME', 'MAJ:MIN') for @path
60*8504dea7STejun Heodef dir_to_dev(path):
61*8504dea7STejun Heo    # find the block device the current directory is on
62*8504dea7STejun Heo    devname = subprocess.run(f'findmnt -nvo SOURCE -T{path}',
63*8504dea7STejun Heo                             stdout=subprocess.PIPE, shell=True).stdout
64*8504dea7STejun Heo    devname = os.path.basename(devname).decode('utf-8').strip()
65*8504dea7STejun Heo
66*8504dea7STejun Heo    # partition -> whole device
67*8504dea7STejun Heo    parents = glob.glob('/sys/block/*/' + devname)
68*8504dea7STejun Heo    if len(parents):
69*8504dea7STejun Heo        devname = os.path.basename(os.path.dirname(parents[0]))
70*8504dea7STejun Heo    rdev = os.stat(f'/dev/{devname}').st_rdev
71*8504dea7STejun Heo    return (devname, f'{os.major(rdev)}:{os.minor(rdev)}')
72*8504dea7STejun Heo
73*8504dea7STejun Heodef create_testfile(path, size):
74*8504dea7STejun Heo    global args
75*8504dea7STejun Heo
76*8504dea7STejun Heo    if os.path.isfile(path) and os.stat(path).st_size == size:
77*8504dea7STejun Heo        return
78*8504dea7STejun Heo
79*8504dea7STejun Heo    info(f'Creating testfile {path}')
80*8504dea7STejun Heo    subprocess.check_call(f'rm -f {path}', shell=True)
81*8504dea7STejun Heo    subprocess.check_call(f'touch {path}', shell=True)
82*8504dea7STejun Heo    subprocess.call(f'chattr +C {path}', shell=True)
83*8504dea7STejun Heo    subprocess.check_call(
84*8504dea7STejun Heo        f'pv -s {size} -pr /dev/urandom {"-q" if args.quiet else ""} | '
85*8504dea7STejun Heo        f'dd of={path} count={size} '
86*8504dea7STejun Heo        f'iflag=count_bytes,fullblock oflag=direct bs=16M status=none',
87*8504dea7STejun Heo        shell=True)
88*8504dea7STejun Heo
89*8504dea7STejun Heodef run_fio(testfile, duration, iotype, iodepth, blocksize, jobs):
90*8504dea7STejun Heo    global args
91*8504dea7STejun Heo
92*8504dea7STejun Heo    eta = 'never' if args.quiet else 'always'
93*8504dea7STejun Heo    outfile = tempfile.NamedTemporaryFile()
94*8504dea7STejun Heo    cmd = (f'fio --direct=1 --ioengine=libaio --name=coef '
95*8504dea7STejun Heo           f'--filename={testfile} --runtime={round(duration)} '
96*8504dea7STejun Heo           f'--readwrite={iotype} --iodepth={iodepth} --blocksize={blocksize} '
97*8504dea7STejun Heo           f'--eta={eta} --output-format json --output={outfile.name} '
98*8504dea7STejun Heo           f'--time_based --numjobs={jobs}')
99*8504dea7STejun Heo    if args.verbose:
100*8504dea7STejun Heo        dbg(f'Running {cmd}')
101*8504dea7STejun Heo    subprocess.check_call(cmd, shell=True)
102*8504dea7STejun Heo    with open(outfile.name, 'r') as f:
103*8504dea7STejun Heo        d = json.loads(f.read())
104*8504dea7STejun Heo    return sum(j['read']['bw_bytes'] + j['write']['bw_bytes'] for j in d['jobs'])
105*8504dea7STejun Heo
106*8504dea7STejun Heodef restore_elevator_nomerges():
107*8504dea7STejun Heo    global elevator_path, nomerges_path, elevator, nomerges
108*8504dea7STejun Heo
109*8504dea7STejun Heo    info(f'Restoring elevator to {elevator} and nomerges to {nomerges}')
110*8504dea7STejun Heo    with open(elevator_path, 'w') as f:
111*8504dea7STejun Heo        f.write(elevator)
112*8504dea7STejun Heo    with open(nomerges_path, 'w') as f:
113*8504dea7STejun Heo        f.write(nomerges)
114*8504dea7STejun Heo
115*8504dea7STejun Heo
116*8504dea7STejun Heoargs = parser.parse_args()
117*8504dea7STejun Heo
118*8504dea7STejun Heomissing = False
119*8504dea7STejun Heofor cmd in [ 'findmnt', 'pv', 'dd', 'fio' ]:
120*8504dea7STejun Heo    if not shutil.which(cmd):
121*8504dea7STejun Heo        print(f'Required command "{cmd}" is missing', file=sys.stderr)
122*8504dea7STejun Heo        missing = True
123*8504dea7STejun Heoif missing:
124*8504dea7STejun Heo    sys.exit(1)
125*8504dea7STejun Heo
126*8504dea7STejun Heoif args.testdev:
127*8504dea7STejun Heo    devname = os.path.basename(args.testdev)
128*8504dea7STejun Heo    rdev = os.stat(f'/dev/{devname}').st_rdev
129*8504dea7STejun Heo    devno = f'{os.major(rdev)}:{os.minor(rdev)}'
130*8504dea7STejun Heo    testfile = f'/dev/{devname}'
131*8504dea7STejun Heo    info(f'Test target: {devname}({devno})')
132*8504dea7STejun Heoelse:
133*8504dea7STejun Heo    devname, devno = dir_to_dev('.')
134*8504dea7STejun Heo    testfile = 'iocost-coef-fio.testfile'
135*8504dea7STejun Heo    testfile_size = int(args.testfile_size_gb * 2 ** 30)
136*8504dea7STejun Heo    create_testfile(testfile, testfile_size)
137*8504dea7STejun Heo    info(f'Test target: {testfile} on {devname}({devno})')
138*8504dea7STejun Heo
139*8504dea7STejun Heoelevator_path = f'/sys/block/{devname}/queue/scheduler'
140*8504dea7STejun Heonomerges_path = f'/sys/block/{devname}/queue/nomerges'
141*8504dea7STejun Heo
142*8504dea7STejun Heowith open(elevator_path, 'r') as f:
143*8504dea7STejun Heo    elevator = re.sub(r'.*\[(.*)\].*', r'\1', f.read().strip())
144*8504dea7STejun Heowith open(nomerges_path, 'r') as f:
145*8504dea7STejun Heo    nomerges = f.read().strip()
146*8504dea7STejun Heo
147*8504dea7STejun Heoinfo(f'Temporarily disabling elevator and merges')
148*8504dea7STejun Heoatexit.register(restore_elevator_nomerges)
149*8504dea7STejun Heowith open(elevator_path, 'w') as f:
150*8504dea7STejun Heo    f.write('none')
151*8504dea7STejun Heowith open(nomerges_path, 'w') as f:
152*8504dea7STejun Heo    f.write('1')
153*8504dea7STejun Heo
154*8504dea7STejun Heoinfo('Determining rbps...')
155*8504dea7STejun Heorbps = run_fio(testfile, args.duration, 'read',
156*8504dea7STejun Heo               1, args.seqio_block_mb * (2 ** 20), args.numjobs)
157*8504dea7STejun Heoinfo(f'\nrbps={rbps}, determining rseqiops...')
158*8504dea7STejun Heorseqiops = round(run_fio(testfile, args.duration, 'read',
159*8504dea7STejun Heo                         args.seq_depth, 4096, args.numjobs) / 4096)
160*8504dea7STejun Heoinfo(f'\nrseqiops={rseqiops}, determining rrandiops...')
161*8504dea7STejun Heorrandiops = round(run_fio(testfile, args.duration, 'randread',
162*8504dea7STejun Heo                          args.rand_depth, 4096, args.numjobs) / 4096)
163*8504dea7STejun Heoinfo(f'\nrrandiops={rrandiops}, determining wbps...')
164*8504dea7STejun Heowbps = run_fio(testfile, args.duration, 'write',
165*8504dea7STejun Heo               1, args.seqio_block_mb * (2 ** 20), args.numjobs)
166*8504dea7STejun Heoinfo(f'\nwbps={wbps}, determining wseqiops...')
167*8504dea7STejun Heowseqiops = round(run_fio(testfile, args.duration, 'write',
168*8504dea7STejun Heo                         args.seq_depth, 4096, args.numjobs) / 4096)
169*8504dea7STejun Heoinfo(f'\nwseqiops={wseqiops}, determining wrandiops...')
170*8504dea7STejun Heowrandiops = round(run_fio(testfile, args.duration, 'randwrite',
171*8504dea7STejun Heo                          args.rand_depth, 4096, args.numjobs) / 4096)
172*8504dea7STejun Heoinfo(f'\nwrandiops={wrandiops}')
173*8504dea7STejun Heorestore_elevator_nomerges()
174*8504dea7STejun Heoatexit.unregister(restore_elevator_nomerges)
175*8504dea7STejun Heoinfo('')
176*8504dea7STejun Heo
177*8504dea7STejun Heoprint(f'{devno} rbps={rbps} rseqiops={rseqiops} rrandiops={rrandiops} '
178*8504dea7STejun Heo      f'wbps={wbps} wseqiops={wseqiops} wrandiops={wrandiops}')
179