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