fstests: add fio perf results support
[xfstests-dev.git] / src / perf / FioCompare.py
1 default_keys = [ 'iops', 'io_bytes', 'bw' ]
2 latency_keys = [ 'lat_ns_min', 'lat_ns_max' ]
3 main_job_keys = [ 'sys_cpu', 'elapsed' ]
4 io_ops = ['read', 'write', 'trim' ]
5
6 def _fuzzy_compare(a, b, fuzzy):
7     if a == b:
8         return 0
9     if a == 0:
10         return 100
11     a = float(a)
12     b = float(b)
13     fuzzy = float(fuzzy)
14     val = ((b - a) / a) * 100
15     if val > fuzzy or val < -fuzzy:
16         return val;
17     return 0
18
19 def _compare_jobs(ijob, njob, latency, fuzz, failures_only):
20     failed = 0
21     for k in default_keys:
22         for io in io_ops:
23             key = "{}_{}".format(io, k)
24             comp = _fuzzy_compare(ijob[key], njob[key], fuzz)
25             if comp < 0:
26                 print("    {} regressed: old {} new {} {}%".format(key,
27                       ijob[key], njob[key], comp))
28                 failed += 1
29             elif not failures_only and comp > 0:
30                 print("    {} improved: old {} new {} {}%".format(key,
31                       ijob[key], njob[key], comp))
32             elif not failures_only:
33                 print("{} is a-ok {} {}".format(key, ijob[key], njob[key]))
34     for k in latency_keys:
35         if not latency:
36             break
37         for io in io_ops:
38             key = "{}_{}".format(io, k)
39             comp = _fuzzy_compare(ijob[key], njob[key], fuzz)
40             if comp > 0:
41                 print("    {} regressed: old {} new {} {}%".format(key,
42                       ijob[key], njob[key], comp))
43                 failed += 1
44             elif not failures_only and comp < 0:
45                 print("    {} improved: old {} new {} {}%".format(key,
46                       ijob[key], njob[key], comp))
47             elif not failures_only:
48                 print("{} is a-ok {} {}".format(key, ijob[key], njob[key]))
49     for k in main_job_keys:
50         comp = _fuzzy_compare(ijob[k], njob[k], fuzz)
51         if comp > 0:
52             print("    {} regressed: old {} new {} {}%".format(k, ijob[k],
53                   njob[k], comp))
54             failed += 1
55         elif not failures_only and comp < 0:
56             print("    {} improved: old {} new {} {}%".format(k, ijob[k],
57                   njob[k], comp))
58         elif not failures_only:
59                 print("{} is a-ok {} {}".format(k, ijob[k], njob[k]))
60     return failed
61
62 def compare_individual_jobs(initial, data, fuzz, failures_only):
63     failed = 0;
64     initial_jobs = initial['jobs'][:]
65     for njob in data['jobs']:
66         for ijob in initial_jobs:
67             if njob['jobname'] == ijob['jobname']:
68                 print("  Checking results for {}".format(njob['jobname']))
69                 failed += _compare_jobs(ijob, njob, fuzz, failures_only)
70                 initial_jobs.remove(ijob)
71                 break
72     return failed
73
74 def default_merge(data):
75     '''Default merge function for multiple jobs in one run
76
77     For runs that include multiple threads we will have a lot of variation
78     between the different threads, which makes comparing them to eachother
79     across multiple runs less that useful.  Instead merge the jobs into a single
80     job.  This function does that by adding up 'iops', 'io_kbytes', and 'bw' for
81     read/write/trim in the merged job, and then taking the maximal values of the
82     latency numbers.
83     '''
84     merge_job = {}
85     for job in data['jobs']:
86         for k in main_job_keys:
87             if k not in merge_job:
88                 merge_job[k] = job[k]
89             else:
90                 merge_job[k] += job[k]
91         for io in io_ops:
92             for k in default_keys:
93                 key = "{}_{}".format(io, k)
94                 if key not in merge_job:
95                     merge_job[key] = job[key]
96                 else:
97                     merge_job[key] += job[key]
98             for k in latency_keys:
99                 key = "{}_{}".format(io, k)
100                 if key not in merge_job:
101                     merge_job[key] = job[key]
102                 elif merge_job[key] < job[key]:
103                     merge_job[key] = job[key]
104     return merge_job
105
106 def compare_fiodata(initial, data, latency, merge_func=default_merge, fuzz=5,
107                     failures_only=True):
108     failed  = 0
109     if merge_func is None:
110         return compare_individual_jobs(initial, data, fuzz, failures_only)
111     ijob = merge_func(initial)
112     njob = merge_func(data)
113     return _compare_jobs(ijob, njob, latency, fuzz, failures_only)