forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
blkcg: add tools/cgroup/iocost_coef_gen.py
Add a script which can be used to generate device-specific iocost linear model coefficients. Signed-off-by: Tejun Heo <[email protected]> Signed-off-by: Jens Axboe <[email protected]>
- Loading branch information
Showing
3 changed files
with
184 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
#!/usr/bin/env python3 | ||
# | ||
# Copyright (C) 2019 Tejun Heo <[email protected]> | ||
# Copyright (C) 2019 Andy Newell <[email protected]> | ||
# Copyright (C) 2019 Facebook | ||
|
||
desc = """ | ||
Generate linear IO cost model coefficients used by the blk-iocost | ||
controller. If the target raw testdev is specified, destructive tests | ||
are performed against the whole device; otherwise, on | ||
./iocost-coef-fio.testfile. The result can be written directly to | ||
/sys/fs/cgroup/io.cost.model. | ||
On high performance devices, --numjobs > 1 is needed to achieve | ||
saturation. | ||
See Documentation/admin-guide/cgroup-v2.rst and block/blk-iocost.c | ||
for more details. | ||
""" | ||
|
||
import argparse | ||
import re | ||
import json | ||
import glob | ||
import os | ||
import sys | ||
import atexit | ||
import shutil | ||
import tempfile | ||
import subprocess | ||
|
||
parser = argparse.ArgumentParser(description=desc, | ||
formatter_class=argparse.RawTextHelpFormatter) | ||
parser.add_argument('--testdev', metavar='DEV', | ||
help='Raw block device to use for testing, ignores --testfile-size') | ||
parser.add_argument('--testfile-size-gb', type=float, metavar='GIGABYTES', default=16, | ||
help='Testfile size in gigabytes (default: %(default)s)') | ||
parser.add_argument('--duration', type=int, metavar='SECONDS', default=120, | ||
help='Individual test run duration in seconds (default: %(default)s)') | ||
parser.add_argument('--seqio-block-mb', metavar='MEGABYTES', type=int, default=128, | ||
help='Sequential test block size in megabytes (default: %(default)s)') | ||
parser.add_argument('--seq-depth', type=int, metavar='DEPTH', default=64, | ||
help='Sequential test queue depth (default: %(default)s)') | ||
parser.add_argument('--rand-depth', type=int, metavar='DEPTH', default=64, | ||
help='Random test queue depth (default: %(default)s)') | ||
parser.add_argument('--numjobs', type=int, metavar='JOBS', default=1, | ||
help='Number of parallel fio jobs to run (default: %(default)s)') | ||
parser.add_argument('--quiet', action='store_true') | ||
parser.add_argument('--verbose', action='store_true') | ||
|
||
def info(msg): | ||
if not args.quiet: | ||
print(msg) | ||
|
||
def dbg(msg): | ||
if args.verbose and not args.quiet: | ||
print(msg) | ||
|
||
# determine ('DEVNAME', 'MAJ:MIN') for @path | ||
def dir_to_dev(path): | ||
# find the block device the current directory is on | ||
devname = subprocess.run(f'findmnt -nvo SOURCE -T{path}', | ||
stdout=subprocess.PIPE, shell=True).stdout | ||
devname = os.path.basename(devname).decode('utf-8').strip() | ||
|
||
# partition -> whole device | ||
parents = glob.glob('/sys/block/*/' + devname) | ||
if len(parents): | ||
devname = os.path.basename(os.path.dirname(parents[0])) | ||
rdev = os.stat(f'/dev/{devname}').st_rdev | ||
return (devname, f'{os.major(rdev)}:{os.minor(rdev)}') | ||
|
||
def create_testfile(path, size): | ||
global args | ||
|
||
if os.path.isfile(path) and os.stat(path).st_size == size: | ||
return | ||
|
||
info(f'Creating testfile {path}') | ||
subprocess.check_call(f'rm -f {path}', shell=True) | ||
subprocess.check_call(f'touch {path}', shell=True) | ||
subprocess.call(f'chattr +C {path}', shell=True) | ||
subprocess.check_call( | ||
f'pv -s {size} -pr /dev/urandom {"-q" if args.quiet else ""} | ' | ||
f'dd of={path} count={size} ' | ||
f'iflag=count_bytes,fullblock oflag=direct bs=16M status=none', | ||
shell=True) | ||
|
||
def run_fio(testfile, duration, iotype, iodepth, blocksize, jobs): | ||
global args | ||
|
||
eta = 'never' if args.quiet else 'always' | ||
outfile = tempfile.NamedTemporaryFile() | ||
cmd = (f'fio --direct=1 --ioengine=libaio --name=coef ' | ||
f'--filename={testfile} --runtime={round(duration)} ' | ||
f'--readwrite={iotype} --iodepth={iodepth} --blocksize={blocksize} ' | ||
f'--eta={eta} --output-format json --output={outfile.name} ' | ||
f'--time_based --numjobs={jobs}') | ||
if args.verbose: | ||
dbg(f'Running {cmd}') | ||
subprocess.check_call(cmd, shell=True) | ||
with open(outfile.name, 'r') as f: | ||
d = json.loads(f.read()) | ||
return sum(j['read']['bw_bytes'] + j['write']['bw_bytes'] for j in d['jobs']) | ||
|
||
def restore_elevator_nomerges(): | ||
global elevator_path, nomerges_path, elevator, nomerges | ||
|
||
info(f'Restoring elevator to {elevator} and nomerges to {nomerges}') | ||
with open(elevator_path, 'w') as f: | ||
f.write(elevator) | ||
with open(nomerges_path, 'w') as f: | ||
f.write(nomerges) | ||
|
||
|
||
args = parser.parse_args() | ||
|
||
missing = False | ||
for cmd in [ 'findmnt', 'pv', 'dd', 'fio' ]: | ||
if not shutil.which(cmd): | ||
print(f'Required command "{cmd}" is missing', file=sys.stderr) | ||
missing = True | ||
if missing: | ||
sys.exit(1) | ||
|
||
if args.testdev: | ||
devname = os.path.basename(args.testdev) | ||
rdev = os.stat(f'/dev/{devname}').st_rdev | ||
devno = f'{os.major(rdev)}:{os.minor(rdev)}' | ||
testfile = f'/dev/{devname}' | ||
info(f'Test target: {devname}({devno})') | ||
else: | ||
devname, devno = dir_to_dev('.') | ||
testfile = 'iocost-coef-fio.testfile' | ||
testfile_size = int(args.testfile_size_gb * 2 ** 30) | ||
create_testfile(testfile, testfile_size) | ||
info(f'Test target: {testfile} on {devname}({devno})') | ||
|
||
elevator_path = f'/sys/block/{devname}/queue/scheduler' | ||
nomerges_path = f'/sys/block/{devname}/queue/nomerges' | ||
|
||
with open(elevator_path, 'r') as f: | ||
elevator = re.sub(r'.*\[(.*)\].*', r'\1', f.read().strip()) | ||
with open(nomerges_path, 'r') as f: | ||
nomerges = f.read().strip() | ||
|
||
info(f'Temporarily disabling elevator and merges') | ||
atexit.register(restore_elevator_nomerges) | ||
with open(elevator_path, 'w') as f: | ||
f.write('none') | ||
with open(nomerges_path, 'w') as f: | ||
f.write('1') | ||
|
||
info('Determining rbps...') | ||
rbps = run_fio(testfile, args.duration, 'read', | ||
1, args.seqio_block_mb * (2 ** 20), args.numjobs) | ||
info(f'\nrbps={rbps}, determining rseqiops...') | ||
rseqiops = round(run_fio(testfile, args.duration, 'read', | ||
args.seq_depth, 4096, args.numjobs) / 4096) | ||
info(f'\nrseqiops={rseqiops}, determining rrandiops...') | ||
rrandiops = round(run_fio(testfile, args.duration, 'randread', | ||
args.rand_depth, 4096, args.numjobs) / 4096) | ||
info(f'\nrrandiops={rrandiops}, determining wbps...') | ||
wbps = run_fio(testfile, args.duration, 'write', | ||
1, args.seqio_block_mb * (2 ** 20), args.numjobs) | ||
info(f'\nwbps={wbps}, determining wseqiops...') | ||
wseqiops = round(run_fio(testfile, args.duration, 'write', | ||
args.seq_depth, 4096, args.numjobs) / 4096) | ||
info(f'\nwseqiops={wseqiops}, determining wrandiops...') | ||
wrandiops = round(run_fio(testfile, args.duration, 'randwrite', | ||
args.rand_depth, 4096, args.numjobs) / 4096) | ||
info(f'\nwrandiops={wrandiops}') | ||
restore_elevator_nomerges() | ||
atexit.unregister(restore_elevator_nomerges) | ||
info('') | ||
|
||
print(f'{devno} rbps={rbps} rseqiops={rseqiops} rrandiops={rrandiops} ' | ||
f'wbps={wbps} wseqiops={wseqiops} wrandiops={wrandiops}') |