| #!/usr/bin/env python3 |
| # |
| # Simple benchmarking framework |
| # |
| # Copyright (c) 2019 Virtuozzo International GmbH. |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| # |
| |
| import math |
| import tabulate |
| |
| # We want leading whitespace for difference row cells (see below) |
| tabulate.PRESERVE_WHITESPACE = True |
| |
| |
| def format_value(x, stdev): |
| stdev_pr = stdev / x * 100 |
| if stdev_pr < 1.5: |
| # don't care too much |
| return f'{x:.2g}' |
| else: |
| return f'{x:.2g} ± {math.ceil(stdev_pr)}%' |
| |
| |
| def result_to_text(result): |
| """Return text representation of bench_one() returned dict.""" |
| if 'average' in result: |
| s = format_value(result['average'], result['stdev']) |
| if 'n-failed' in result: |
| s += '\n({} failed)'.format(result['n-failed']) |
| return s |
| else: |
| return 'FAILED' |
| |
| |
| def results_dimension(results): |
| dim = None |
| for case in results['cases']: |
| for env in results['envs']: |
| res = results['tab'][case['id']][env['id']] |
| if dim is None: |
| dim = res['dimension'] |
| else: |
| assert dim == res['dimension'] |
| |
| assert dim in ('iops', 'seconds') |
| |
| return dim |
| |
| |
| def results_to_text(results): |
| """Return text representation of bench() returned dict.""" |
| n_columns = len(results['envs']) |
| named_columns = n_columns > 2 |
| dim = results_dimension(results) |
| tab = [] |
| |
| if named_columns: |
| # Environment columns are named A, B, ... |
| tab.append([''] + [chr(ord('A') + i) for i in range(n_columns)]) |
| |
| tab.append([''] + [c['id'] for c in results['envs']]) |
| |
| for case in results['cases']: |
| row = [case['id']] |
| case_results = results['tab'][case['id']] |
| for env in results['envs']: |
| res = case_results[env['id']] |
| row.append(result_to_text(res)) |
| tab.append(row) |
| |
| # Add row of difference between columns. For each column starting from |
| # B we calculate difference with all previous columns. |
| row = ['', ''] # case name and first column |
| for i in range(1, n_columns): |
| cell = '' |
| env = results['envs'][i] |
| res = case_results[env['id']] |
| |
| if 'average' not in res: |
| # Failed result |
| row.append(cell) |
| continue |
| |
| for j in range(0, i): |
| env_j = results['envs'][j] |
| res_j = case_results[env_j['id']] |
| cell += ' ' |
| |
| if 'average' not in res_j: |
| # Failed result |
| cell += '--' |
| continue |
| |
| col_j = tab[0][j + 1] if named_columns else '' |
| diff_pr = round((res['average'] - res_j['average']) / |
| res_j['average'] * 100) |
| cell += f' {col_j}{diff_pr:+}%' |
| row.append(cell) |
| tab.append(row) |
| |
| return f'All results are in {dim}\n\n' + tabulate.tabulate(tab) |
| |
| |
| if __name__ == '__main__': |
| import sys |
| import json |
| |
| if len(sys.argv) < 2: |
| print(f'USAGE: {sys.argv[0]} results.json') |
| exit(1) |
| |
| with open(sys.argv[1]) as f: |
| print(results_to_text(json.load(f))) |