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