Newer
Older
#!/usr/bin/env python3
"""
Collect the json files from individual benchmark tests into
a larger json file that combines all benchmark information,
and do additional accounting for the benchmark.
Tests results are expected to have the following file name and directory
structure:
results/<BENCHMARK_NAME>/**/<SOME_NAME>.json
where ** implies we check recursively check all sub-directories of <BENCHMARK_NAME>
Internally, we will look for the "tests" keyword in each of these
files to identify them as benchmark components.
"""
## Our benchmark definition file, stored in the benchmark root directory
BENCHMARK_FILE=r'{}/benchmark.json'
## Our benchmark results directory
RESULTS_PATH=r'results/{}'
## Output json file with benchmark results
OUTPUT_FILE=r'results/{}.json'
## Exceptions for this module
class Error(Exception):
'''Base class for exceptions in this module.'''
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
class FileNotFoundError(Exception):
'''File does not exist.
Attributes:
file: the file name
message: error message
'''
def __init__(self, file):
self.file = file
self.message = 'No such file or directory: {}'.format(file)
class InvalidBenchmarkDefinitionError(Exception):
'''Raised for missing keys in the benchmark definition.
Attributes:
key: the missing key
file: the benchmark definition file
message: error message
'''
def __init__(self, key, file):
self.key = key
self.file = file
self.message = "key '{}' not found in benchmark file '{}'".format(key, file)
class InvalidTestDefinitionError(Exception):
'''Raised for missing keys in the test result.
Attributes:
key: the missing key
file: the test result file
message: error message
'''
def __init__(self, key, file):
self.key = key
self.file = file
self.message = "key '{}' not found in test file '{}'".format(key, file)
class InvalidTestResultError(Exception):
'''Raised for invalid test result value.
Attributes:
key: the missing key
value: the invalid value
file: the benchmark definition file
message: error message
'''
def __init__(self, key, value, file):
self.key = key
self.value = value
self.file = file
self.message = "value '{}' for key '{}' invalid in test file '{}'".format(
value, key, file)
parser = argparse.ArgumentParser()
parser.add_argument(
'benchmark',
action='append',
help='One or more benchmarks for which to collect test results.')
def collect_results(benchmark):
'''Collect benchmark tests and write results to file.'''
print("Collecting results for benchmark '{}'".format(benchmark))
## load the test definition for this benchmark
results = _load_benchmark(benchmark)
## collect the test results
results['tests'] = _load_tests(benchmark)
## calculate aggregate test statistics
results = _aggregate_results(results)
## save results to output file
_save(benchmark, results)
## Summarize results
_print_summary(results)
def _load_benchmark(benchmark):
'''Load benchmark definition.'''
benchfile = Path(BENCHMARK_FILE.format(benchmark))
if not benchfile.exists():
raise FileNotFoundError(benchfile)
print(' --> Loading benchmark definition from:', benchfile)
results = None
with benchfile.open() as f:
results = json.load(f)
## ensure this is a valid benchmark file
for key in ('name', 'title', 'description', 'target'):
if not key in results:
raise InvalidBenchmarkDefinitionError('target', benchfile)
return results
def _load_tests(benchmark):
'''Loop over all test results in benchmark folder and return results.'''
print(' --> Collecting all test results')
rootdir = Path(RESULTS_PATH.format(benchmark))
results = []
for file in rootdir.glob('**/*.json'):
print(' --> Loading file:', file, '... ', end='')
with open(file) as f:
new_results = json.load(f)
## skip files that don't include test results
if not 'tests' in new_results:
print('not a test result')
continue
## check if these are valid test results,
## raise exception otherwise
for test in new_results['tests']:
for key in ('name', 'title', 'description', 'quantity', 'target',
'value', 'result'):
if not key in test:
raise InvalidTestDefinitionError(key, file)
if test['result'] not in ('pass', 'fail', 'error'):
raise InvalidTestResultError('result', test['result'], file)
## ensure 'weight' key present, defaulting to 1 in needed
if not 'weight' in test:
test['weight'] = 1.
## Append to our test results
results.append(test)
print('done')
return results
def _aggregate_results(results):
'''Aggregate test results for our benchmark.'''
print(' --> Aggregating benchmark statistics')
results['target'] = float(results['target'])
results['n_tests'] = len(results['tests'])
results['n_pass'] = len([1 for t in results['tests'] if t['result'] == 'pass'])
results['n_fail'] = len([1 for t in results['tests'] if t['result'] == 'fail'])
results['n_error'] = len([1 for t in results['tests'] if t['result'] == 'error'])
results['maximum'] = sum([t['weight'] for t in results['tests']])
results['sum'] = sum([t['weight'] for t in results['tests'] if t['result'] == 'pass'])
if (results['n_tests'] > 0):
results['value'] = results['sum'] / results['maximum']
if results['n_error'] > 0:
results['result'] = 'error'
elif results['value'] >= results['target']:
results['result'] = 'pass'
else:
results['result'] = 'fail'
else:
results['value'] = -1
results['result'] = 'error'
return results
def _save(benchmark, results):
'''Save benchmark results'''
ofile = Path(OUTPUT_FILE.format(benchmark))
print(' --> Saving benchmark results to:', ofile)
with ofile.open('w') as f:
json.dump(results, f, indent=4)
def _print_summary(results):
'''Print benchmark summary to the terminal.'''
print('====================================================================')
print('Summary for:', results['title'])
print('Pass: {}, Fail: {}, Error: {} out of {} total tests'.format(
results['n_pass'], results['n_fail'], results['n_error'],
results['n_tests']))
print('Weighted sum: {} / {}'.format(results['sum'], results['maximum']))
print('Benchmark value: {} (target: {})'.format(
results['value'], results['target']))
print('===> status:', results['result'])
print('====================================================================')
if __name__ == "__main__":
args = parser.parse_args()
for benchmark in args.benchmark:
collect_results(benchmark)