1 #!/usr/bin/env python 2 3 """ 4 compare.py - versatile benchmark output compare tool 5 """ 6 7 import argparse 8 from argparse import ArgumentParser 9 import sys 10 import gbench 11 from gbench import util, report 12 from gbench.util import * 13 14 15 def check_inputs(in1, in2, flags): 16 """ 17 Perform checking on the user provided inputs and diagnose any abnormalities 18 """ 19 in1_kind, in1_err = classify_input_file(in1) 20 in2_kind, in2_err = classify_input_file(in2) 21 output_file = find_benchmark_flag('--benchmark_out=', flags) 22 output_type = find_benchmark_flag('--benchmark_out_format=', flags) 23 if in1_kind == IT_Executable and in2_kind == IT_Executable and output_file: 24 print(("WARNING: '--benchmark_out=%s' will be passed to both " 25 "benchmarks causing it to be overwritten") % output_file) 26 if in1_kind == IT_JSON and in2_kind == IT_JSON and len(flags) > 0: 27 print("WARNING: passing optional flags has no effect since both " 28 "inputs are JSON") 29 if output_type is not None and output_type != 'json': 30 print(("ERROR: passing '--benchmark_out_format=%s' to 'compare.py`" 31 " is not supported.") % output_type) 32 sys.exit(1) 33 34 35 def create_parser(): 36 parser = ArgumentParser( 37 description='versatile benchmark output compare tool') 38 subparsers = parser.add_subparsers( 39 help='This tool has multiple modes of operation:', 40 dest='mode') 41 42 parser_a = subparsers.add_parser( 43 'benchmarks', 44 help='The most simple use-case, compare all the output of these two benchmarks') 45 baseline = parser_a.add_argument_group( 46 'baseline', 'The benchmark baseline') 47 baseline.add_argument( 48 'test_baseline', 49 metavar='test_baseline', 50 type=argparse.FileType('r'), 51 nargs=1, 52 help='A benchmark executable or JSON output file') 53 contender = parser_a.add_argument_group( 54 'contender', 'The benchmark that will be compared against the baseline') 55 contender.add_argument( 56 'test_contender', 57 metavar='test_contender', 58 type=argparse.FileType('r'), 59 nargs=1, 60 help='A benchmark executable or JSON output file') 61 parser_a.add_argument( 62 'benchmark_options', 63 metavar='benchmark_options', 64 nargs=argparse.REMAINDER, 65 help='Arguments to pass when running benchmark executables') 66 67 parser_b = subparsers.add_parser( 68 'filters', help='Compare filter one with the filter two of benchmark') 69 baseline = parser_b.add_argument_group( 70 'baseline', 'The benchmark baseline') 71 baseline.add_argument( 72 'test', 73 metavar='test', 74 type=argparse.FileType('r'), 75 nargs=1, 76 help='A benchmark executable or JSON output file') 77 baseline.add_argument( 78 'filter_baseline', 79 metavar='filter_baseline', 80 type=str, 81 nargs=1, 82 help='The first filter, that will be used as baseline') 83 contender = parser_b.add_argument_group( 84 'contender', 'The benchmark that will be compared against the baseline') 85 contender.add_argument( 86 'filter_contender', 87 metavar='filter_contender', 88 type=str, 89 nargs=1, 90 help='The second filter, that will be compared against the baseline') 91 parser_b.add_argument( 92 'benchmark_options', 93 metavar='benchmark_options', 94 nargs=argparse.REMAINDER, 95 help='Arguments to pass when running benchmark executables') 96 97 parser_c = subparsers.add_parser( 98 'benchmarksfiltered', 99 help='Compare filter one of first benchmark with filter two of the second benchmark') 100 baseline = parser_c.add_argument_group( 101 'baseline', 'The benchmark baseline') 102 baseline.add_argument( 103 'test_baseline', 104 metavar='test_baseline', 105 type=argparse.FileType('r'), 106 nargs=1, 107 help='A benchmark executable or JSON output file') 108 baseline.add_argument( 109 'filter_baseline', 110 metavar='filter_baseline', 111 type=str, 112 nargs=1, 113 help='The first filter, that will be used as baseline') 114 contender = parser_c.add_argument_group( 115 'contender', 'The benchmark that will be compared against the baseline') 116 contender.add_argument( 117 'test_contender', 118 metavar='test_contender', 119 type=argparse.FileType('r'), 120 nargs=1, 121 help='The second benchmark executable or JSON output file, that will be compared against the baseline') 122 contender.add_argument( 123 'filter_contender', 124 metavar='filter_contender', 125 type=str, 126 nargs=1, 127 help='The second filter, that will be compared against the baseline') 128 parser_c.add_argument( 129 'benchmark_options', 130 metavar='benchmark_options', 131 nargs=argparse.REMAINDER, 132 help='Arguments to pass when running benchmark executables') 133 134 return parser 135 136 137 def main(): 138 # Parse the command line flags 139 parser = create_parser() 140 args, unknown_args = parser.parse_known_args() 141 assert not unknown_args 142 benchmark_options = args.benchmark_options 143 144 if args.mode == 'benchmarks': 145 test_baseline = args.test_baseline[0].name 146 test_contender = args.test_contender[0].name 147 filter_baseline = '' 148 filter_contender = '' 149 150 # NOTE: if test_baseline == test_contender, you are analyzing the stdev 151 152 description = 'Comparing %s to %s' % (test_baseline, test_contender) 153 elif args.mode == 'filters': 154 test_baseline = args.test[0].name 155 test_contender = args.test[0].name 156 filter_baseline = args.filter_baseline[0] 157 filter_contender = args.filter_contender[0] 158 159 # NOTE: if filter_baseline == filter_contender, you are analyzing the 160 # stdev 161 162 description = 'Comparing %s to %s (from %s)' % ( 163 filter_baseline, filter_contender, args.test[0].name) 164 elif args.mode == 'benchmarksfiltered': 165 test_baseline = args.test_baseline[0].name 166 test_contender = args.test_contender[0].name 167 filter_baseline = args.filter_baseline[0] 168 filter_contender = args.filter_contender[0] 169 170 # NOTE: if test_baseline == test_contender and 171 # filter_baseline == filter_contender, you are analyzing the stdev 172 173 description = 'Comparing %s (from %s) to %s (from %s)' % ( 174 filter_baseline, test_baseline, filter_contender, test_contender) 175 else: 176 # should never happen 177 print("Unrecognized mode of operation: '%s'" % args.mode) 178 exit(1) 179 180 check_inputs(test_baseline, test_contender, benchmark_options) 181 182 options_baseline = [] 183 options_contender = [] 184 185 if filter_baseline and filter_contender: 186 options_baseline = ['--benchmark_filter=%s' % filter_baseline] 187 options_contender = ['--benchmark_filter=%s' % filter_contender] 188 189 # Run the benchmarks and report the results 190 json1 = json1_orig = gbench.util.run_or_load_benchmark( 191 test_baseline, benchmark_options + options_baseline) 192 json2 = json2_orig = gbench.util.run_or_load_benchmark( 193 test_contender, benchmark_options + options_contender) 194 195 # Now, filter the benchmarks so that the difference report can work 196 if filter_baseline and filter_contender: 197 replacement = '[%s vs. %s]' % (filter_baseline, filter_contender) 198 json1 = gbench.report.filter_benchmark( 199 json1_orig, filter_baseline, replacement) 200 json2 = gbench.report.filter_benchmark( 201 json2_orig, filter_contender, replacement) 202 203 # Diff and output 204 output_lines = gbench.report.generate_difference_report(json1, json2) 205 print(description) 206 for ln in output_lines: 207 print(ln) 208 209 210 import unittest 211 212 213 class TestParser(unittest.TestCase): 214 def setUp(self): 215 self.parser = create_parser() 216 testInputs = os.path.join( 217 os.path.dirname( 218 os.path.realpath(__file__)), 219 'gbench', 220 'Inputs') 221 self.testInput0 = os.path.join(testInputs, 'test_baseline_run1.json') 222 self.testInput1 = os.path.join(testInputs, 'test_baseline_run2.json') 223 224 def test_benchmarks_basic(self): 225 parsed = self.parser.parse_args( 226 ['benchmarks', self.testInput0, self.testInput1]) 227 self.assertEqual(parsed.mode, 'benchmarks') 228 self.assertEqual(parsed.test_baseline[0].name, self.testInput0) 229 self.assertEqual(parsed.test_contender[0].name, self.testInput1) 230 self.assertFalse(parsed.benchmark_options) 231 232 def test_benchmarks_with_remainder(self): 233 parsed = self.parser.parse_args( 234 ['benchmarks', self.testInput0, self.testInput1, 'd']) 235 self.assertEqual(parsed.mode, 'benchmarks') 236 self.assertEqual(parsed.test_baseline[0].name, self.testInput0) 237 self.assertEqual(parsed.test_contender[0].name, self.testInput1) 238 self.assertEqual(parsed.benchmark_options, ['d']) 239 240 def test_benchmarks_with_remainder_after_doubleminus(self): 241 parsed = self.parser.parse_args( 242 ['benchmarks', self.testInput0, self.testInput1, '--', 'e']) 243 self.assertEqual(parsed.mode, 'benchmarks') 244 self.assertEqual(parsed.test_baseline[0].name, self.testInput0) 245 self.assertEqual(parsed.test_contender[0].name, self.testInput1) 246 self.assertEqual(parsed.benchmark_options, ['e']) 247 248 def test_filters_basic(self): 249 parsed = self.parser.parse_args( 250 ['filters', self.testInput0, 'c', 'd']) 251 self.assertEqual(parsed.mode, 'filters') 252 self.assertEqual(parsed.test[0].name, self.testInput0) 253 self.assertEqual(parsed.filter_baseline[0], 'c') 254 self.assertEqual(parsed.filter_contender[0], 'd') 255 self.assertFalse(parsed.benchmark_options) 256 257 def test_filters_with_remainder(self): 258 parsed = self.parser.parse_args( 259 ['filters', self.testInput0, 'c', 'd', 'e']) 260 self.assertEqual(parsed.mode, 'filters') 261 self.assertEqual(parsed.test[0].name, self.testInput0) 262 self.assertEqual(parsed.filter_baseline[0], 'c') 263 self.assertEqual(parsed.filter_contender[0], 'd') 264 self.assertEqual(parsed.benchmark_options, ['e']) 265 266 def test_filters_with_remainder_after_doubleminus(self): 267 parsed = self.parser.parse_args( 268 ['filters', self.testInput0, 'c', 'd', '--', 'f']) 269 self.assertEqual(parsed.mode, 'filters') 270 self.assertEqual(parsed.test[0].name, self.testInput0) 271 self.assertEqual(parsed.filter_baseline[0], 'c') 272 self.assertEqual(parsed.filter_contender[0], 'd') 273 self.assertEqual(parsed.benchmark_options, ['f']) 274 275 def test_benchmarksfiltered_basic(self): 276 parsed = self.parser.parse_args( 277 ['benchmarksfiltered', self.testInput0, 'c', self.testInput1, 'e']) 278 self.assertEqual(parsed.mode, 'benchmarksfiltered') 279 self.assertEqual(parsed.test_baseline[0].name, self.testInput0) 280 self.assertEqual(parsed.filter_baseline[0], 'c') 281 self.assertEqual(parsed.test_contender[0].name, self.testInput1) 282 self.assertEqual(parsed.filter_contender[0], 'e') 283 self.assertFalse(parsed.benchmark_options) 284 285 def test_benchmarksfiltered_with_remainder(self): 286 parsed = self.parser.parse_args( 287 ['benchmarksfiltered', self.testInput0, 'c', self.testInput1, 'e', 'f']) 288 self.assertEqual(parsed.mode, 'benchmarksfiltered') 289 self.assertEqual(parsed.test_baseline[0].name, self.testInput0) 290 self.assertEqual(parsed.filter_baseline[0], 'c') 291 self.assertEqual(parsed.test_contender[0].name, self.testInput1) 292 self.assertEqual(parsed.filter_contender[0], 'e') 293 self.assertEqual(parsed.benchmark_options[0], 'f') 294 295 def test_benchmarksfiltered_with_remainder_after_doubleminus(self): 296 parsed = self.parser.parse_args( 297 ['benchmarksfiltered', self.testInput0, 'c', self.testInput1, 'e', '--', 'g']) 298 self.assertEqual(parsed.mode, 'benchmarksfiltered') 299 self.assertEqual(parsed.test_baseline[0].name, self.testInput0) 300 self.assertEqual(parsed.filter_baseline[0], 'c') 301 self.assertEqual(parsed.test_contender[0].name, self.testInput1) 302 self.assertEqual(parsed.filter_contender[0], 'e') 303 self.assertEqual(parsed.benchmark_options[0], 'g') 304 305 306 if __name__ == '__main__': 307 # unittest.main() 308 main() 309 310 # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 311 # kate: tab-width: 4; replace-tabs on; indent-width 4; tab-indents: off; 312 # kate: indent-mode python; remove-trailing-spaces modified; 313