blob: 5969062e63e90515ee047d5760c98e0c2da66217 [file] [log] [blame]
import re
import os
import sys
import stat
import time
import json
import os.path
import argparse
import warnings
import subprocess
class BenchmarkOption(object):
def __init__(self, concurence, iodepth, action, blocksize, size):
self.iodepth = iodepth
self.action = action
self.blocksize = blocksize
self.concurence = concurence
self.size = size
self.direct_io = False
self.use_hight_io_priority = True
self.sync = False
class IOZoneParser(object):
"class to parse iozone results"
start_tests = re.compile(r"^\s+KB\s+reclen\s+")
resuts = re.compile(r"[\s0-9]+")
mt_iozone_re = re.compile(r"\s+Children see throughput " +
r"for\s+\d+\s+(?P<cmd>.*?)\s+=\s+" +
r"(?P<perf>[\d.]+)\s+KB/sec")
cmap = {'initial writers': 'write',
'rewriters': 'rewrite',
'initial readers': 'read',
're-readers': 'reread',
'random readers': 'random read',
'random writers': 'random write'}
string1 = " " + \
" random random " + \
"bkwd record stride "
string2 = "KB reclen write rewrite " + \
"read reread read write " + \
"read rewrite read fwrite frewrite fread freread"
@classmethod
def apply_parts(cls, parts, string, sep=' \t\n'):
add_offset = 0
for part in parts:
_, start, stop = part
start += add_offset
add_offset = 0
# condition splited to make pylint happy
while stop + add_offset < len(string):
# condition splited to make pylint happy
if not (string[stop + add_offset] not in sep):
break
add_offset += 1
yield part, string[start:stop + add_offset]
@classmethod
def make_positions(cls):
items = [i for i in cls.string2.split() if i]
pos = 0
cls.positions = []
for item in items:
npos = cls.string2.index(item, 0 if pos == 0 else pos + 1)
cls.positions.append([item, pos, npos + len(item)])
pos = npos + len(item)
for itm, val in cls.apply_parts(cls.positions, cls.string1):
if val.strip():
itm[0] = val.strip() + " " + itm[0]
@classmethod
def parse_iozone_res(cls, res, mthreads=False):
parsed_res = None
sres = res.split('\n')
if not mthreads:
for pos, line in enumerate(sres[1:]):
if line.strip() == cls.string2 and \
sres[pos].strip() == cls.string1.strip():
add_pos = line.index(cls.string2)
parsed_res = {}
npos = [(name, start + add_pos, stop + add_pos)
for name, start, stop in cls.positions]
for itm, res in cls.apply_parts(npos, sres[pos + 2]):
if res.strip() != '':
parsed_res[itm[0]] = int(res.strip())
del parsed_res['KB']
del parsed_res['reclen']
else:
parsed_res = {}
for line in sres:
rr = cls.mt_iozone_re.match(line)
if rr is not None:
cmd = rr.group('cmd')
key = cls.cmap.get(cmd, cmd)
perf = int(float(rr.group('perf')))
parsed_res[key] = perf
return parsed_res
def ssize_to_kb(ssize):
try:
smap = dict(k=1, K=1, M=1024, m=1024, G=1024**2, g=1024**2)
for ext, coef in smap.items():
if ssize.endswith(ext):
return int(ssize[:-1]) * coef
if int(ssize) % 1024 != 0:
raise ValueError()
return int(ssize) / 1024
except (ValueError, TypeError, AttributeError):
tmpl = "Unknow size format {0!r} (or size not multiples 1024)"
raise ValueError(tmpl.format(ssize))
IOZoneParser.make_positions()
def do_run_iozone(params, filename, timeout, iozone_path='iozone',
microsecond_mode=False):
cmd = [iozone_path]
if params.sync:
cmd.append('-o')
if params.direct_io:
cmd.append('-I')
if microsecond_mode:
cmd.append('-N')
all_files = []
threads = int(params.concurence)
if 1 != threads:
cmd.extend(('-t', str(threads), '-F'))
filename = filename + "_{}"
cmd.extend(filename % i for i in range(threads))
all_files.extend(filename % i for i in range(threads))
else:
cmd.extend(('-f', filename))
all_files.append(filename)
bsz = 1024 if params.size > 1024 else params.size
if params.size % bsz != 0:
fsz = (params.size // bsz + 1) * bsz
else:
fsz = params.size
for fname in all_files:
ccmd = [iozone_path, "-f", fname, "-i", "0",
"-s", str(fsz), "-r", str(bsz), "-w"]
subprocess.check_output(ccmd)
cmd.append('-i')
if params.action == 'write':
cmd.append("0")
elif params.action == 'randwrite':
cmd.append("2")
else:
raise ValueError("Unknown action {0!r}".format(params.action))
cmd.extend(('-s', str(params.size)))
cmd.extend(('-r', str(params.blocksize)))
raw_res = subprocess.check_output(cmd)
try:
parsed_res = IOZoneParser.parse_iozone_res(raw_res, threads > 1)
res = {}
if params.action == 'write':
res['bw_mean'] = parsed_res['write']
elif params.action == 'randwrite':
res['bw_mean'] = parsed_res['random write']
elif params.action == 'read':
res['bw_mean'] = parsed_res['read']
elif params.action == 'randread':
res['bw_mean'] = parsed_res['random read']
except:
print raw_res
raise
# res['bw_dev'] = 0
# res['bw_max'] = res["bw_mean"]
# res['bw_min'] = res["bw_mean"]
return res
def run_iozone(benchmark, iozone_path, tmpname, timeout=None):
if timeout is not None:
benchmark.size = benchmark.blocksize * 50
res_time = do_run_iozone(benchmark, tmpname, timeout,
iozone_path=iozone_path,
microsecond_mode=True)
size = (benchmark.blocksize * timeout * 1000000)
size /= res_time["bw_mean"]
size = (size // benchmark.blocksize + 1) * benchmark.blocksize
benchmark.size = size
return do_run_iozone(benchmark, tmpname, timeout,
iozone_path=iozone_path)
def which(program):
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
def install_iozone_package():
if which('iozone'):
return
is_redhat = os.path.exists('/etc/centos-release')
is_redhat = is_redhat or os.path.exists('/etc/fedora-release')
is_redhat = is_redhat or os.path.exists('/etc/redhat-release')
if is_redhat:
subprocess.check_output(["yum", "install", 'iozone3'])
return
try:
os_release_cont = open('/etc/os-release').read()
is_ubuntu = "Ubuntu" in os_release_cont
if is_ubuntu or "Debian GNU/Linux" in os_release_cont:
subprocess.check_output(["apt-get", "install", "iozone3"])
return
except (IOError, OSError) as exc:
print exc
pass
raise RuntimeError("Unknown host OS.")
def install_iozone_static(iozone_url, dst):
if not os.path.isfile(dst):
import urllib
urllib.urlretrieve(iozone_url, dst)
st = os.stat(dst)
os.chmod(dst, st.st_mode | stat.S_IEXEC)
def type_size(string):
try:
return re.match("\d+[KGBM]?", string, re.I).group(0)
except:
msg = "{0!r} don't looks like size-description string".format(string)
raise ValueError(msg)
def parse_args(argv):
parser = argparse.ArgumentParser(
description="Run set of `iozone` invocations and return result")
parser.add_argument(
"--iodepth", metavar="IODEPTH", type=int,
help="I/O depths to test in kb", required=True)
parser.add_argument(
'-a', "--action", metavar="ACTION", type=str,
help="actions to run", required=True,
choices=["read", "write", "randread", "randwrite"])
parser.add_argument(
"--blocksize", metavar="BLOCKSIZE", type=type_size,
help="single operation block size", required=True)
parser.add_argument(
"--timeout", metavar="TIMEOUT", type=int,
help="runtime of a single run", default=None)
parser.add_argument(
"--iosize", metavar="SIZE", type=type_size,
help="file size", default=None)
parser.add_argument(
"-s", "--sync", default=False, action="store_true",
help="exec sync after each write")
parser.add_argument(
"-d", "--direct-io", default=False, action="store_true",
help="use O_DIRECT", dest='directio')
parser.add_argument(
"-t", "--sync-time", default=None, type=int,
help="sleep till sime utc time", dest='sync_time')
parser.add_argument(
"--iozone-url", help="iozone static binary url",
dest="iozone_url", default=None)
parser.add_argument(
"--test-file", help="file path to run test on",
default=None, dest='test_file')
parser.add_argument(
"--iozone-path", help="iozone path",
default=None, dest='iozone_path')
return parser.parse_args(argv)
def main(argv):
argv_obj = parse_args(argv)
argv_obj.blocksize = ssize_to_kb(argv_obj.blocksize)
if argv_obj.iosize is not None:
argv_obj.iosize = ssize_to_kb(argv_obj.iosize)
benchmark = BenchmarkOption(1,
argv_obj.iodepth,
argv_obj.action,
argv_obj.blocksize,
argv_obj.iosize)
benchmark.direct_io = argv_obj.directio
test_file_name = argv_obj.test_file
if test_file_name is None:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
test_file_name = os.tmpnam()
remove_iozone_bin = False
if argv_obj.iozone_url is not None:
if argv_obj.iozone_path is not None:
sys.stderr.write("At most one option from --iozone-path and "
"--iozone-url should be provided")
exit(1)
iozone_binary = os.tmpnam()
install_iozone_static(argv_obj.iozone_url, iozone_binary)
remove_iozone_bin = True
elif argv_obj.iozone_path is not None:
iozone_binary = argv_obj.iozone_path
if os.path.isfile(iozone_binary):
if not os.access(iozone_binary, os.X_OK):
st = os.stat(iozone_binary)
os.chmod(iozone_binary, st.st_mode | stat.S_IEXEC)
else:
iozone_binary = which('iozone')
if iozone_binary is None:
iozone_binary = which('iozone3')
if iozone_binary is None:
sys.stderr.write("Can't found neither iozone not iozone3 binary"
"Provide --iozone-path or --iozone-url option")
exit(1)
try:
if argv_obj.sync_time is not None:
dt = argv_obj.sync_time - time.time()
if dt > 0:
time.sleep(dt)
res = run_iozone(benchmark, iozone_binary, test_file_name)
sys.stdout.write(json.dumps(res) + "\n")
finally:
if remove_iozone_bin:
os.unlink(iozone_binary)
if os.path.isfile(test_file_name):
os.unlink(test_file_name)
# function-marker for patching, don't 'optimize' it
def INSERT_IOZONE_ARGS(x):
return x
if __name__ == '__main__':
# this line would be patched in case of run under rally
# don't modify it!
argv = INSERT_IOZONE_ARGS(sys.argv[1:])
exit(main(argv))