|  | #!/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))) |