File indexing completed on 2026-09-23 09:34:10
0001
0002
0003 """
0004 Collect the json files from individual benchmark tests into
0005 a larger json file that combines all benchmark information,
0006 and do additional accounting for the benchmark.
0007
0008 Tests results are expected to have the following file name and directory
0009 structure:
0010 results/<BENCHMARK_NAME>/**/<SOME_NAME>.json
0011 where ** implies we check recursively check all sub-directories of <BENCHMARK_NAME>
0012
0013 Internally, we will look for the "tests" keyword in each of these
0014 files to identify them as benchmark components.
0015 """
0016
0017
0018 BENCHMARK_FILE=r'benchmarks/{}/benchmark.json'
0019
0020
0021 RESULTS_PATH=r'results/{}'
0022
0023
0024 OUTPUT_FILE=r'results/{}.json'
0025
0026 import argparse
0027 import json
0028 from pathlib import Path
0029
0030
0031 class Error(Exception):
0032 '''Base class for exceptions in this module.'''
0033 pass
0034 class FileNotFoundError(Exception):
0035 '''File does not exist.
0036
0037 Attributes:
0038 file: the file name
0039 message: error message
0040 '''
0041 def __init__(self, file):
0042 self.file = file
0043 self.message = 'No such file or directory: {}'.format(file)
0044
0045 class InvalidBenchmarkDefinitionError(Exception):
0046 '''Raised for missing keys in the benchmark definition.
0047
0048 Attributes:
0049 key: the missing key
0050 file: the benchmark definition file
0051 message: error message
0052 '''
0053 def __init__(self, key, file):
0054 self.key = key
0055 self.file = file
0056 self.message = "key '{}' not found in benchmark file '{}'".format(key, file)
0057
0058 class InvalidTestDefinitionError(Exception):
0059 '''Raised for missing keys in the test result.
0060
0061 Attributes:
0062 key: the missing key
0063 file: the test result file
0064 message: error message
0065 '''
0066 def __init__(self, key, file):
0067 self.key = key
0068 self.file = file
0069 self.message = "key '{}' not found in test file '{}'".format(key, file)
0070 class InvalidTestResultError(Exception):
0071 '''Raised for invalid test result value.
0072
0073 Attributes:
0074 key: the missing key
0075 value: the invalid value
0076 file: the benchmark definition file
0077 message: error message
0078 '''
0079 def __init__(self, key, value, file):
0080 self.key = key
0081 self.value = value
0082 self.file = file
0083 self.message = "value '{}' for key '{}' invalid in test file '{}'".format(
0084 value, key, file)
0085
0086
0087 parser = argparse.ArgumentParser()
0088 parser.add_argument(
0089 'benchmark',
0090 action='append',
0091 help='One or more benchmarks for which to collect test results.')
0092
0093 def collect_results(benchmark):
0094 '''Collect benchmark tests and write results to file.'''
0095 print("Collecting results for benchmark '{}'".format(benchmark))
0096
0097
0098 results = _load_benchmark(benchmark)
0099
0100
0101 results['tests'] = _load_tests(benchmark)
0102
0103
0104 results = _aggregate_results(results)
0105
0106
0107 _save(benchmark, results)
0108
0109
0110 _print_summary(results)
0111
0112 def _load_benchmark(benchmark):
0113 '''Load benchmark definition.'''
0114 benchfile = Path(BENCHMARK_FILE.format(benchmark))
0115 if not benchfile.exists():
0116 raise FileNotFoundError(benchfile)
0117 print(' --> Loading benchmark definition from:', benchfile)
0118 results = None
0119 with benchfile.open() as f:
0120 results = json.load(f)
0121
0122 for key in ('name', 'title', 'description', 'target'):
0123 if not key in results:
0124 raise InvalidBenchmarkDefinitionError('target', benchfile)
0125 return results
0126
0127 def _load_tests(benchmark):
0128 '''Loop over all test results in benchmark folder and return results.'''
0129 print(' --> Collecting all test results')
0130 rootdir = Path(RESULTS_PATH.format(benchmark))
0131 results = []
0132 for file in rootdir.glob('**/*.json'):
0133 print(' --> Loading file:', file, '... ', end='')
0134 with open(file) as f:
0135 new_results = json.load(f)
0136
0137 if not 'tests' in new_results:
0138 print('not a test result')
0139 continue
0140
0141
0142 for test in new_results['tests']:
0143 for key in ('name', 'title', 'description', 'quantity', 'target',
0144 'value', 'result'):
0145 if not key in test:
0146 raise InvalidTestDefinitionError(key, file)
0147 if test['result'] not in ('pass', 'fail', 'error'):
0148 raise InvalidTestResultError('result', test['result'], file)
0149
0150 if not 'weight' in test:
0151 test['weight'] = 1.
0152
0153 results.append(test)
0154 print('done')
0155 return results
0156
0157 def _aggregate_results(results):
0158 '''Aggregate test results for our benchmark.'''
0159 print(' --> Aggregating benchmark statistics')
0160 results['target'] = float(results['target'])
0161 results['n_tests'] = len(results['tests'])
0162 results['n_pass'] = len([1 for t in results['tests'] if t['result'] == 'pass'])
0163 results['n_fail'] = len([1 for t in results['tests'] if t['result'] == 'fail'])
0164 results['n_error'] = len([1 for t in results['tests'] if t['result'] == 'error'])
0165 results['maximum'] = sum([t['weight'] for t in results['tests']])
0166 results['sum'] = sum([t['weight'] for t in results['tests'] if t['result'] == 'pass'])
0167 if (results['n_tests'] > 0):
0168 results['value'] = results['sum'] / results['maximum']
0169 if results['n_error'] > 0:
0170 results['result'] = 'error'
0171 elif results['value'] >= results['target']:
0172 results['result'] = 'pass'
0173 else:
0174 results['result'] = 'fail'
0175 else:
0176 results['value'] = -1
0177 results['result'] = 'error'
0178 return results
0179
0180 def _save(benchmark, results):
0181 '''Save benchmark results'''
0182 ofile = Path(OUTPUT_FILE.format(Path(benchmark).name))
0183 print(' --> Saving benchmark results to:', ofile)
0184 with ofile.open('w') as f:
0185 json.dump(results, f, indent=4)
0186
0187 def _print_summary(results):
0188 '''Print benchmark summary to the terminal.'''
0189 print('====================================================================')
0190 print('Summary for:', results['title'])
0191 print('Pass: {}, Fail: {}, Error: {} out of {} total tests'.format(
0192 results['n_pass'], results['n_fail'], results['n_error'],
0193 results['n_tests']))
0194 print('Weighted sum: {} / {}'.format(results['sum'], results['maximum']))
0195 print('Benchmark value: {} (target: {})'.format(
0196 results['value'], results['target']))
0197 print('===> status:', results['result'])
0198 print('====================================================================')
0199
0200
0201 if __name__ == "__main__":
0202 args = parser.parse_args()
0203 for benchmark in args.benchmark:
0204 collect_results(benchmark)