#!/usr/bin/env python3 import os import sys import getopt import subprocess import signal import re fio_bin = "fio" def show_help(): print("""Usage: {} run_fio.py [options] [args] Description: Run FIO job file 'fio.job' on remote machines. NOTE: The job file must exist on remote machines on '/root/' directory. Args: [VMs] (ex. vm1_IP:vm1_port:vm1_disk1:vm_disk2,vm2_IP:vm2_port:vm2_disk1,etc...) Options: -h, --help Show this message. -j, --job-file Paths to file with FIO job configuration on remote host. -f, --fio-bin Location of FIO binary on local host (Default "fio") -o, --out Directory used to save generated job files and files with test results -J, --json Use JSON format for output -p, --perf-vmex Enable aggregating statistic for VMEXITS for VMs """.format(os.path.split(sys.executable)[-1])) def exec_cmd(cmd, blocking): # Print result to STDOUT for now, we don't have json support yet. p = subprocess.Popen(cmd.split(" "), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE) if blocking is True: out, _ = p.communicate() return p.returncode, out.decode() return p def save_file(path, mode, contents): with open(path, mode) as fh: fh.write(contents) fh.close() def run_fio(vms, fio_cfg_fname, out_path, perf_vmex=False, json=False): global fio_bin job_name = os.path.splitext(os.path.basename(fio_cfg_fname))[0] # Build command for FIO fio_cmd = " ".join([fio_bin, "--eta=never"]) if json: fio_cmd = " ".join([fio_bin, "--output-format=json"]) for vm in vms: # vm[0] = IP address, vm[1] = Port number fio_cmd = " ".join([fio_cmd, "--client={vm_ip},{vm_port}".format(vm_ip=vm[0], vm_port=vm[1]), "--remote-config {cfg}".format(cfg=fio_cfg_fname)]) print(fio_cmd) if perf_vmex: perf_dir = os.path.join(out_path, "perf_stats") try: os.mkdir(perf_dir) except OSError: pass # Start gathering perf statistics for host and VM guests perf_rec_file = os.path.join(perf_dir, "perf.data.kvm") perf_run_cmd = "perf kvm --host --guest " + \ "-o {0} stat record -a".format(perf_rec_file) print(perf_run_cmd) perf_p = exec_cmd(perf_run_cmd, blocking=False) # Run FIO test on VMs rc, out = exec_cmd(fio_cmd, blocking=True) # if for some reason output contains lines with "eta" - remove them out = re.sub(r'.+\[eta\s+\d{2}m:\d{2}s\]', '', out) print(out) if rc != 0: print("ERROR! While executing FIO jobs - RC: {rc}".format(rc=rc, out=out)) sys.exit(rc) else: save_file(os.path.join(out_path, ".".join([job_name, "log"])), "w", out) if perf_vmex: # Stop gathering perf statistics and prepare some result files perf_p.send_signal(signal.SIGINT) perf_p.wait() perf_stat_cmd = "perf kvm --host -i {perf_rec} stat report --event vmexit"\ .format(perf_rec=perf_rec_file) rc, out = exec_cmd(" ".join([perf_stat_cmd, "--event vmexit"]), blocking=True) print("VMexit host stats:") print("{perf_out}".format(perf_out=out)) save_file(os.path.join(perf_dir, "vmexit_stats_" + job_name), "w", "{perf_out}".format(perf_out=out)) try: os.remove(perf_rec_file) except OSError: pass def main(): global fio_bin abspath = os.path.abspath(__file__) dname = os.path.dirname(abspath) vms = [] fio_cfg = None out_dir = None perf_vmex = False json = False try: opts, args = getopt.getopt(sys.argv[1:], "hJj:f:o:p", ["help", "job-file=", "fio-bin=", "out=", "perf-vmex", "json"]) except getopt.GetoptError: show_help() sys.exit(1) if len(args) < 1: show_help() sys.exit(1) for o, a in opts: if o in ("-j", "--job-file"): fio_cfg = a elif o in ("-h", "--help"): show_help() sys.exit(1) elif o in ("-p", "--perf-vmex"): perf_vmex = True elif o in ("-o", "--out"): out_dir = a elif o in ("-f", "--fio-bin"): fio_bin = a elif o in ("-J", "--json"): json = True if fio_cfg is None: print("ERROR! No FIO job provided!") sys.exit(1) if out_dir is None or not os.path.exists(out_dir): print("ERROR! Folder {out_dir} does not exist ".format(out_dir=out_dir)) sys.exit(1) # Get IP, port and fio 'filename' information from positional args for arg in args[0].split(","): _ = arg.split(":") ip, port, filenames = _[0], _[1], ":".join(_[2:]) vms.append((ip, port, filenames)) print("Running job file: {0}".format(fio_cfg)) run_fio(vms, fio_cfg, out_dir, perf_vmex, json) if __name__ == "__main__": sys.exit(main())