xref: /qemu/scripts/simplebench/simplebench.py (revision 270124e7efcaaef68c492d1293af975992138606)
17cc8e0a5SVladimir Sementsov-Ogievskiy#!/usr/bin/env python
27cc8e0a5SVladimir Sementsov-Ogievskiy#
37cc8e0a5SVladimir Sementsov-Ogievskiy# Simple benchmarking framework
47cc8e0a5SVladimir Sementsov-Ogievskiy#
57cc8e0a5SVladimir Sementsov-Ogievskiy# Copyright (c) 2019 Virtuozzo International GmbH.
67cc8e0a5SVladimir Sementsov-Ogievskiy#
77cc8e0a5SVladimir Sementsov-Ogievskiy# This program is free software; you can redistribute it and/or modify
87cc8e0a5SVladimir Sementsov-Ogievskiy# it under the terms of the GNU General Public License as published by
97cc8e0a5SVladimir Sementsov-Ogievskiy# the Free Software Foundation; either version 2 of the License, or
107cc8e0a5SVladimir Sementsov-Ogievskiy# (at your option) any later version.
117cc8e0a5SVladimir Sementsov-Ogievskiy#
127cc8e0a5SVladimir Sementsov-Ogievskiy# This program is distributed in the hope that it will be useful,
137cc8e0a5SVladimir Sementsov-Ogievskiy# but WITHOUT ANY WARRANTY; without even the implied warranty of
147cc8e0a5SVladimir Sementsov-Ogievskiy# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
157cc8e0a5SVladimir Sementsov-Ogievskiy# GNU General Public License for more details.
167cc8e0a5SVladimir Sementsov-Ogievskiy#
177cc8e0a5SVladimir Sementsov-Ogievskiy# You should have received a copy of the GNU General Public License
187cc8e0a5SVladimir Sementsov-Ogievskiy# along with this program.  If not, see <http://www.gnu.org/licenses/>.
197cc8e0a5SVladimir Sementsov-Ogievskiy#
207cc8e0a5SVladimir Sementsov-Ogievskiy
217cc8e0a5SVladimir Sementsov-Ogievskiy
227cc8e0a5SVladimir Sementsov-Ogievskiydef bench_one(test_func, test_env, test_case, count=5, initial_run=True):
237cc8e0a5SVladimir Sementsov-Ogievskiy    """Benchmark one test-case
247cc8e0a5SVladimir Sementsov-Ogievskiy
257cc8e0a5SVladimir Sementsov-Ogievskiy    test_func   -- benchmarking function with prototype
267cc8e0a5SVladimir Sementsov-Ogievskiy                   test_func(env, case), which takes test_env and test_case
277cc8e0a5SVladimir Sementsov-Ogievskiy                   arguments and returns {'seconds': int} (which is benchmark
287cc8e0a5SVladimir Sementsov-Ogievskiy                   result) on success and {'error': str} on error. Returned
297cc8e0a5SVladimir Sementsov-Ogievskiy                   dict may contain any other additional fields.
307cc8e0a5SVladimir Sementsov-Ogievskiy    test_env    -- test environment - opaque first argument for test_func
317cc8e0a5SVladimir Sementsov-Ogievskiy    test_case   -- test case - opaque second argument for test_func
327cc8e0a5SVladimir Sementsov-Ogievskiy    count       -- how many times to call test_func, to calculate average
337cc8e0a5SVladimir Sementsov-Ogievskiy    initial_run -- do initial run of test_func, which don't get into result
347cc8e0a5SVladimir Sementsov-Ogievskiy
357cc8e0a5SVladimir Sementsov-Ogievskiy    Returns dict with the following fields:
367cc8e0a5SVladimir Sementsov-Ogievskiy        'runs':     list of test_func results
377cc8e0a5SVladimir Sementsov-Ogievskiy        'average':  average seconds per run (exists only if at least one run
387cc8e0a5SVladimir Sementsov-Ogievskiy                    succeeded)
397cc8e0a5SVladimir Sementsov-Ogievskiy        'delta':    maximum delta between test_func result and the average
407cc8e0a5SVladimir Sementsov-Ogievskiy                    (exists only if at least one run succeeded)
417cc8e0a5SVladimir Sementsov-Ogievskiy        'n-failed': number of failed runs (exists only if at least one run
427cc8e0a5SVladimir Sementsov-Ogievskiy                    failed)
437cc8e0a5SVladimir Sementsov-Ogievskiy    """
447cc8e0a5SVladimir Sementsov-Ogievskiy    if initial_run:
457cc8e0a5SVladimir Sementsov-Ogievskiy        print('  #initial run:')
467cc8e0a5SVladimir Sementsov-Ogievskiy        print('   ', test_func(test_env, test_case))
477cc8e0a5SVladimir Sementsov-Ogievskiy
487cc8e0a5SVladimir Sementsov-Ogievskiy    runs = []
497cc8e0a5SVladimir Sementsov-Ogievskiy    for i in range(count):
507cc8e0a5SVladimir Sementsov-Ogievskiy        print('  #run {}'.format(i+1))
517cc8e0a5SVladimir Sementsov-Ogievskiy        res = test_func(test_env, test_case)
527cc8e0a5SVladimir Sementsov-Ogievskiy        print('   ', res)
537cc8e0a5SVladimir Sementsov-Ogievskiy        runs.append(res)
547cc8e0a5SVladimir Sementsov-Ogievskiy
557cc8e0a5SVladimir Sementsov-Ogievskiy    result = {'runs': runs}
567cc8e0a5SVladimir Sementsov-Ogievskiy
57*270124e7SVladimir Sementsov-Ogievskiy    succeeded = [r for r in runs if ('seconds' in r)]
58*270124e7SVladimir Sementsov-Ogievskiy    if succeeded:
59*270124e7SVladimir Sementsov-Ogievskiy        avg = sum(r['seconds'] for r in succeeded) / len(succeeded)
607cc8e0a5SVladimir Sementsov-Ogievskiy        result['average'] = avg
61*270124e7SVladimir Sementsov-Ogievskiy        result['delta'] = max(abs(r['seconds'] - avg) for r in succeeded)
627cc8e0a5SVladimir Sementsov-Ogievskiy
63*270124e7SVladimir Sementsov-Ogievskiy    if len(succeeded) < count:
64*270124e7SVladimir Sementsov-Ogievskiy        result['n-failed'] = count - len(succeeded)
657cc8e0a5SVladimir Sementsov-Ogievskiy
667cc8e0a5SVladimir Sementsov-Ogievskiy    return result
677cc8e0a5SVladimir Sementsov-Ogievskiy
687cc8e0a5SVladimir Sementsov-Ogievskiy
697cc8e0a5SVladimir Sementsov-Ogievskiydef ascii_one(result):
707cc8e0a5SVladimir Sementsov-Ogievskiy    """Return ASCII representation of bench_one() returned dict."""
717cc8e0a5SVladimir Sementsov-Ogievskiy    if 'average' in result:
727cc8e0a5SVladimir Sementsov-Ogievskiy        s = '{:.2f} +- {:.2f}'.format(result['average'], result['delta'])
737cc8e0a5SVladimir Sementsov-Ogievskiy        if 'n-failed' in result:
747cc8e0a5SVladimir Sementsov-Ogievskiy            s += '\n({} failed)'.format(result['n-failed'])
757cc8e0a5SVladimir Sementsov-Ogievskiy        return s
767cc8e0a5SVladimir Sementsov-Ogievskiy    else:
777cc8e0a5SVladimir Sementsov-Ogievskiy        return 'FAILED'
787cc8e0a5SVladimir Sementsov-Ogievskiy
797cc8e0a5SVladimir Sementsov-Ogievskiy
807cc8e0a5SVladimir Sementsov-Ogievskiydef bench(test_func, test_envs, test_cases, *args, **vargs):
817cc8e0a5SVladimir Sementsov-Ogievskiy    """Fill benchmark table
827cc8e0a5SVladimir Sementsov-Ogievskiy
837cc8e0a5SVladimir Sementsov-Ogievskiy    test_func -- benchmarking function, see bench_one for description
847cc8e0a5SVladimir Sementsov-Ogievskiy    test_envs -- list of test environments, see bench_one
857cc8e0a5SVladimir Sementsov-Ogievskiy    test_cases -- list of test cases, see bench_one
867cc8e0a5SVladimir Sementsov-Ogievskiy    args, vargs -- additional arguments for bench_one
877cc8e0a5SVladimir Sementsov-Ogievskiy
887cc8e0a5SVladimir Sementsov-Ogievskiy    Returns dict with the following fields:
897cc8e0a5SVladimir Sementsov-Ogievskiy        'envs':  test_envs
907cc8e0a5SVladimir Sementsov-Ogievskiy        'cases': test_cases
917cc8e0a5SVladimir Sementsov-Ogievskiy        'tab':   filled 2D array, where cell [i][j] is bench_one result for
927cc8e0a5SVladimir Sementsov-Ogievskiy                 test_cases[i] for test_envs[j] (i.e., rows are test cases and
937cc8e0a5SVladimir Sementsov-Ogievskiy                 columns are test environments)
947cc8e0a5SVladimir Sementsov-Ogievskiy    """
957cc8e0a5SVladimir Sementsov-Ogievskiy    tab = {}
967cc8e0a5SVladimir Sementsov-Ogievskiy    results = {
977cc8e0a5SVladimir Sementsov-Ogievskiy        'envs': test_envs,
987cc8e0a5SVladimir Sementsov-Ogievskiy        'cases': test_cases,
997cc8e0a5SVladimir Sementsov-Ogievskiy        'tab': tab
1007cc8e0a5SVladimir Sementsov-Ogievskiy    }
1017cc8e0a5SVladimir Sementsov-Ogievskiy    n = 1
1027cc8e0a5SVladimir Sementsov-Ogievskiy    n_tests = len(test_envs) * len(test_cases)
1037cc8e0a5SVladimir Sementsov-Ogievskiy    for env in test_envs:
1047cc8e0a5SVladimir Sementsov-Ogievskiy        for case in test_cases:
1057cc8e0a5SVladimir Sementsov-Ogievskiy            print('Testing {}/{}: {} :: {}'.format(n, n_tests,
1067cc8e0a5SVladimir Sementsov-Ogievskiy                                                   env['id'], case['id']))
1077cc8e0a5SVladimir Sementsov-Ogievskiy            if case['id'] not in tab:
1087cc8e0a5SVladimir Sementsov-Ogievskiy                tab[case['id']] = {}
1097cc8e0a5SVladimir Sementsov-Ogievskiy            tab[case['id']][env['id']] = bench_one(test_func, env, case,
1107cc8e0a5SVladimir Sementsov-Ogievskiy                                                   *args, **vargs)
1117cc8e0a5SVladimir Sementsov-Ogievskiy            n += 1
1127cc8e0a5SVladimir Sementsov-Ogievskiy
1137cc8e0a5SVladimir Sementsov-Ogievskiy    print('Done')
1147cc8e0a5SVladimir Sementsov-Ogievskiy    return results
1157cc8e0a5SVladimir Sementsov-Ogievskiy
1167cc8e0a5SVladimir Sementsov-Ogievskiy
1177cc8e0a5SVladimir Sementsov-Ogievskiydef ascii(results):
1187cc8e0a5SVladimir Sementsov-Ogievskiy    """Return ASCII representation of bench() returned dict."""
1197cc8e0a5SVladimir Sementsov-Ogievskiy    from tabulate import tabulate
1207cc8e0a5SVladimir Sementsov-Ogievskiy
1217cc8e0a5SVladimir Sementsov-Ogievskiy    tab = [[""] + [c['id'] for c in results['envs']]]
1227cc8e0a5SVladimir Sementsov-Ogievskiy    for case in results['cases']:
1237cc8e0a5SVladimir Sementsov-Ogievskiy        row = [case['id']]
1247cc8e0a5SVladimir Sementsov-Ogievskiy        for env in results['envs']:
1257cc8e0a5SVladimir Sementsov-Ogievskiy            row.append(ascii_one(results['tab'][case['id']][env['id']]))
1267cc8e0a5SVladimir Sementsov-Ogievskiy        tab.append(row)
1277cc8e0a5SVladimir Sementsov-Ogievskiy
1287cc8e0a5SVladimir Sementsov-Ogievskiy    return tabulate(tab)
129