cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

iocost_coef_gen.py (6934B)


      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
      7desc = """
      8Generate linear IO cost model coefficients used by the blk-iocost
      9controller.  If the target raw testdev is specified, destructive tests
     10are 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
     14On high performance devices, --numjobs > 1 is needed to achieve
     15saturation.
     16
     17See Documentation/admin-guide/cgroup-v2.rst and block/blk-iocost.c
     18for more details.
     19"""
     20
     21import argparse
     22import re
     23import json
     24import glob
     25import os
     26import sys
     27import atexit
     28import shutil
     29import tempfile
     30import subprocess
     31
     32parser = argparse.ArgumentParser(description=desc,
     33                                 formatter_class=argparse.RawTextHelpFormatter)
     34parser.add_argument('--testdev', metavar='DEV',
     35                    help='Raw block device to use for testing, ignores --testfile-size')
     36parser.add_argument('--testfile-size-gb', type=float, metavar='GIGABYTES', default=16,
     37                    help='Testfile size in gigabytes (default: %(default)s)')
     38parser.add_argument('--duration', type=int, metavar='SECONDS', default=120,
     39                    help='Individual test run duration in seconds (default: %(default)s)')
     40parser.add_argument('--seqio-block-mb', metavar='MEGABYTES', type=int, default=128,
     41                    help='Sequential test block size in megabytes (default: %(default)s)')
     42parser.add_argument('--seq-depth', type=int, metavar='DEPTH', default=64,
     43                    help='Sequential test queue depth (default: %(default)s)')
     44parser.add_argument('--rand-depth', type=int, metavar='DEPTH', default=64,
     45                    help='Random test queue depth (default: %(default)s)')
     46parser.add_argument('--numjobs', type=int, metavar='JOBS', default=1,
     47                    help='Number of parallel fio jobs to run (default: %(default)s)')
     48parser.add_argument('--quiet', action='store_true')
     49parser.add_argument('--verbose', action='store_true')
     50
     51def info(msg):
     52    if not args.quiet:
     53        print(msg)
     54
     55def dbg(msg):
     56    if args.verbose and not args.quiet:
     57        print(msg)
     58
     59# determine ('DEVNAME', 'MAJ:MIN') for @path
     60def 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
     73def 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
     89def 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
    106def 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
    116args = parser.parse_args()
    117
    118missing = False
    119for 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
    123if missing:
    124    sys.exit(1)
    125
    126if 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})')
    132else:
    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
    139elevator_path = f'/sys/block/{devname}/queue/scheduler'
    140nomerges_path = f'/sys/block/{devname}/queue/nomerges'
    141
    142with open(elevator_path, 'r') as f:
    143    elevator = re.sub(r'.*\[(.*)\].*', r'\1', f.read().strip())
    144with open(nomerges_path, 'r') as f:
    145    nomerges = f.read().strip()
    146
    147info(f'Temporarily disabling elevator and merges')
    148atexit.register(restore_elevator_nomerges)
    149with open(elevator_path, 'w') as f:
    150    f.write('none')
    151with open(nomerges_path, 'w') as f:
    152    f.write('1')
    153
    154info('Determining rbps...')
    155rbps = run_fio(testfile, args.duration, 'read',
    156               1, args.seqio_block_mb * (2 ** 20), args.numjobs)
    157info(f'\nrbps={rbps}, determining rseqiops...')
    158rseqiops = round(run_fio(testfile, args.duration, 'read',
    159                         args.seq_depth, 4096, args.numjobs) / 4096)
    160info(f'\nrseqiops={rseqiops}, determining rrandiops...')
    161rrandiops = round(run_fio(testfile, args.duration, 'randread',
    162                          args.rand_depth, 4096, args.numjobs) / 4096)
    163info(f'\nrrandiops={rrandiops}, determining wbps...')
    164wbps = run_fio(testfile, args.duration, 'write',
    165               1, args.seqio_block_mb * (2 ** 20), args.numjobs)
    166info(f'\nwbps={wbps}, determining wseqiops...')
    167wseqiops = round(run_fio(testfile, args.duration, 'write',
    168                         args.seq_depth, 4096, args.numjobs) / 4096)
    169info(f'\nwseqiops={wseqiops}, determining wrandiops...')
    170wrandiops = round(run_fio(testfile, args.duration, 'randwrite',
    171                          args.rand_depth, 4096, args.numjobs) / 4096)
    172info(f'\nwrandiops={wrandiops}')
    173restore_elevator_nomerges()
    174atexit.unregister(restore_elevator_nomerges)
    175info('')
    176
    177print(f'{devno} rbps={rbps} rseqiops={rseqiops} rrandiops={rrandiops} '
    178      f'wbps={wbps} wseqiops={wseqiops} wrandiops={wrandiops}')