]>
Commit | Line | Data |
---|---|---|
11fdf7f2 | 1 | #!/usr/bin/env python3 |
7c673cae FG |
2 | |
3 | import os | |
4 | import sys | |
5 | import getopt | |
6 | import subprocess | |
7 | import signal | |
8 | import re | |
9 | ||
10 | fio_bin = "fio" | |
11 | ||
12 | ||
13 | def show_help(): | |
11fdf7f2 TL |
14 | print("""Usage: {} run_fio.py [options] [args] |
15 | Description: | |
16 | Run FIO job file 'fio.job' on remote machines. | |
17 | NOTE: The job file must exist on remote machines on '/root/' directory. | |
7c673cae FG |
18 | Args: |
19 | [VMs] (ex. vm1_IP:vm1_port:vm1_disk1:vm_disk2,vm2_IP:vm2_port:vm2_disk1,etc...) | |
20 | Options: | |
21 | -h, --help Show this message. | |
11fdf7f2 TL |
22 | -j, --job-file Paths to file with FIO job configuration on remote host. |
23 | -f, --fio-bin Location of FIO binary on local host (Default "fio") | |
7c673cae | 24 | -o, --out Directory used to save generated job files and |
11fdf7f2 TL |
25 | files with test results |
26 | -J, --json Use JSON format for output | |
7c673cae | 27 | -p, --perf-vmex Enable aggregating statistic for VMEXITS for VMs |
11fdf7f2 | 28 | """.format(os.path.split(sys.executable)[-1])) |
7c673cae FG |
29 | |
30 | ||
31 | def exec_cmd(cmd, blocking): | |
32 | # Print result to STDOUT for now, we don't have json support yet. | |
33 | p = subprocess.Popen(cmd.split(" "), stdout=subprocess.PIPE, | |
34 | stderr=subprocess.STDOUT, stdin=subprocess.PIPE) | |
35 | if blocking is True: | |
36 | out, _ = p.communicate() | |
11fdf7f2 | 37 | return p.returncode, out.decode() |
7c673cae FG |
38 | return p |
39 | ||
40 | ||
41 | def save_file(path, mode, contents): | |
42 | with open(path, mode) as fh: | |
43 | fh.write(contents) | |
44 | fh.close() | |
45 | ||
46 | ||
11fdf7f2 | 47 | def run_fio(vms, fio_cfg_fname, out_path, perf_vmex=False, json=False): |
9f95a23c TL |
48 | global fio_bin |
49 | job_name = os.path.splitext(os.path.basename(fio_cfg_fname))[0] | |
50 | ||
51 | # Build command for FIO | |
52 | fio_cmd = " ".join([fio_bin, "--eta=never"]) | |
53 | if json: | |
54 | fio_cmd = " ".join([fio_bin, "--output-format=json"]) | |
55 | for vm in vms: | |
56 | # vm[0] = IP address, vm[1] = Port number | |
57 | fio_cmd = " ".join([fio_cmd, | |
58 | "--client={vm_ip},{vm_port}".format(vm_ip=vm[0], vm_port=vm[1]), | |
59 | "--remote-config {cfg}".format(cfg=fio_cfg_fname)]) | |
60 | print(fio_cmd) | |
61 | ||
62 | if perf_vmex: | |
63 | perf_dir = os.path.join(out_path, "perf_stats") | |
64 | try: | |
65 | os.mkdir(perf_dir) | |
66 | except OSError: | |
67 | pass | |
68 | ||
69 | # Start gathering perf statistics for host and VM guests | |
70 | perf_rec_file = os.path.join(perf_dir, "perf.data.kvm") | |
71 | perf_run_cmd = "perf kvm --host --guest " + \ | |
72 | "-o {0} stat record -a".format(perf_rec_file) | |
73 | print(perf_run_cmd) | |
74 | perf_p = exec_cmd(perf_run_cmd, blocking=False) | |
75 | ||
76 | # Run FIO test on VMs | |
77 | rc, out = exec_cmd(fio_cmd, blocking=True) | |
78 | ||
79 | # if for some reason output contains lines with "eta" - remove them | |
80 | out = re.sub(r'.+\[eta\s+\d{2}m:\d{2}s\]', '', out) | |
81 | ||
82 | print(out) | |
83 | ||
84 | if rc != 0: | |
85 | print("ERROR! While executing FIO jobs - RC: {rc}".format(rc=rc, out=out)) | |
86 | sys.exit(rc) | |
87 | else: | |
88 | save_file(os.path.join(out_path, ".".join([job_name, "log"])), "w", out) | |
89 | ||
90 | if perf_vmex: | |
91 | # Stop gathering perf statistics and prepare some result files | |
92 | perf_p.send_signal(signal.SIGINT) | |
93 | perf_p.wait() | |
94 | ||
95 | perf_stat_cmd = "perf kvm --host -i {perf_rec} stat report --event vmexit"\ | |
96 | .format(perf_rec=perf_rec_file) | |
97 | ||
98 | rc, out = exec_cmd(" ".join([perf_stat_cmd, "--event vmexit"]), | |
99 | blocking=True) | |
100 | print("VMexit host stats:") | |
101 | print("{perf_out}".format(perf_out=out)) | |
102 | save_file(os.path.join(perf_dir, "vmexit_stats_" + job_name), | |
103 | "w", "{perf_out}".format(perf_out=out)) | |
104 | try: | |
105 | os.remove(perf_rec_file) | |
106 | except OSError: | |
107 | pass | |
7c673cae FG |
108 | |
109 | ||
110 | def main(): | |
111 | global fio_bin | |
112 | ||
113 | abspath = os.path.abspath(__file__) | |
114 | dname = os.path.dirname(abspath) | |
7c673cae FG |
115 | |
116 | vms = [] | |
11fdf7f2 TL |
117 | fio_cfg = None |
118 | out_dir = None | |
7c673cae | 119 | perf_vmex = False |
11fdf7f2 | 120 | json = False |
7c673cae FG |
121 | |
122 | try: | |
11fdf7f2 | 123 | opts, args = getopt.getopt(sys.argv[1:], "hJj:f:o:p", |
7c673cae | 124 | ["help", "job-file=", "fio-bin=", |
11fdf7f2 | 125 | "out=", "perf-vmex", "json"]) |
7c673cae FG |
126 | except getopt.GetoptError: |
127 | show_help() | |
128 | sys.exit(1) | |
129 | ||
11fdf7f2 TL |
130 | if len(args) < 1: |
131 | show_help() | |
132 | sys.exit(1) | |
133 | ||
7c673cae FG |
134 | for o, a in opts: |
135 | if o in ("-j", "--job-file"): | |
11fdf7f2 | 136 | fio_cfg = a |
7c673cae FG |
137 | elif o in ("-h", "--help"): |
138 | show_help() | |
139 | sys.exit(1) | |
140 | elif o in ("-p", "--perf-vmex"): | |
141 | perf_vmex = True | |
142 | elif o in ("-o", "--out"): | |
11fdf7f2 | 143 | out_dir = a |
7c673cae FG |
144 | elif o in ("-f", "--fio-bin"): |
145 | fio_bin = a | |
11fdf7f2 TL |
146 | elif o in ("-J", "--json"): |
147 | json = True | |
7c673cae | 148 | |
11fdf7f2 TL |
149 | if fio_cfg is None: |
150 | print("ERROR! No FIO job provided!") | |
7c673cae FG |
151 | sys.exit(1) |
152 | ||
11fdf7f2 TL |
153 | if out_dir is None or not os.path.exists(out_dir): |
154 | print("ERROR! Folder {out_dir} does not exist ".format(out_dir=out_dir)) | |
7c673cae | 155 | sys.exit(1) |
11fdf7f2 TL |
156 | |
157 | # Get IP, port and fio 'filename' information from positional args | |
158 | for arg in args[0].split(","): | |
159 | _ = arg.split(":") | |
160 | ip, port, filenames = _[0], _[1], ":".join(_[2:]) | |
161 | vms.append((ip, port, filenames)) | |
162 | ||
163 | print("Running job file: {0}".format(fio_cfg)) | |
164 | run_fio(vms, fio_cfg, out_dir, perf_vmex, json) | |
165 | ||
7c673cae FG |
166 | |
167 | if __name__ == "__main__": | |
168 | sys.exit(main()) |