xref: /qemu/scripts/simplebench/simplebench.py (revision 7cc8e0a545cb096a136bc13a3344ca51260956ef)
1*7cc8e0a5SVladimir Sementsov-Ogievskiy#!/usr/bin/env python
2*7cc8e0a5SVladimir Sementsov-Ogievskiy#
3*7cc8e0a5SVladimir Sementsov-Ogievskiy# Simple benchmarking framework
4*7cc8e0a5SVladimir Sementsov-Ogievskiy#
5*7cc8e0a5SVladimir Sementsov-Ogievskiy# Copyright (c) 2019 Virtuozzo International GmbH.
6*7cc8e0a5SVladimir Sementsov-Ogievskiy#
7*7cc8e0a5SVladimir Sementsov-Ogievskiy# This program is free software; you can redistribute it and/or modify
8*7cc8e0a5SVladimir Sementsov-Ogievskiy# it under the terms of the GNU General Public License as published by
9*7cc8e0a5SVladimir Sementsov-Ogievskiy# the Free Software Foundation; either version 2 of the License, or
10*7cc8e0a5SVladimir Sementsov-Ogievskiy# (at your option) any later version.
11*7cc8e0a5SVladimir Sementsov-Ogievskiy#
12*7cc8e0a5SVladimir Sementsov-Ogievskiy# This program is distributed in the hope that it will be useful,
13*7cc8e0a5SVladimir Sementsov-Ogievskiy# but WITHOUT ANY WARRANTY; without even the implied warranty of
14*7cc8e0a5SVladimir Sementsov-Ogievskiy# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15*7cc8e0a5SVladimir Sementsov-Ogievskiy# GNU General Public License for more details.
16*7cc8e0a5SVladimir Sementsov-Ogievskiy#
17*7cc8e0a5SVladimir Sementsov-Ogievskiy# You should have received a copy of the GNU General Public License
18*7cc8e0a5SVladimir Sementsov-Ogievskiy# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19*7cc8e0a5SVladimir Sementsov-Ogievskiy#
20*7cc8e0a5SVladimir Sementsov-Ogievskiy
21*7cc8e0a5SVladimir Sementsov-Ogievskiy
22*7cc8e0a5SVladimir Sementsov-Ogievskiydef bench_one(test_func, test_env, test_case, count=5, initial_run=True):
23*7cc8e0a5SVladimir Sementsov-Ogievskiy    """Benchmark one test-case
24*7cc8e0a5SVladimir Sementsov-Ogievskiy
25*7cc8e0a5SVladimir Sementsov-Ogievskiy    test_func   -- benchmarking function with prototype
26*7cc8e0a5SVladimir Sementsov-Ogievskiy                   test_func(env, case), which takes test_env and test_case
27*7cc8e0a5SVladimir Sementsov-Ogievskiy                   arguments and returns {'seconds': int} (which is benchmark
28*7cc8e0a5SVladimir Sementsov-Ogievskiy                   result) on success and {'error': str} on error. Returned
29*7cc8e0a5SVladimir Sementsov-Ogievskiy                   dict may contain any other additional fields.
30*7cc8e0a5SVladimir Sementsov-Ogievskiy    test_env    -- test environment - opaque first argument for test_func
31*7cc8e0a5SVladimir Sementsov-Ogievskiy    test_case   -- test case - opaque second argument for test_func
32*7cc8e0a5SVladimir Sementsov-Ogievskiy    count       -- how many times to call test_func, to calculate average
33*7cc8e0a5SVladimir Sementsov-Ogievskiy    initial_run -- do initial run of test_func, which don't get into result
34*7cc8e0a5SVladimir Sementsov-Ogievskiy
35*7cc8e0a5SVladimir Sementsov-Ogievskiy    Returns dict with the following fields:
36*7cc8e0a5SVladimir Sementsov-Ogievskiy        'runs':     list of test_func results
37*7cc8e0a5SVladimir Sementsov-Ogievskiy        'average':  average seconds per run (exists only if at least one run
38*7cc8e0a5SVladimir Sementsov-Ogievskiy                    succeeded)
39*7cc8e0a5SVladimir Sementsov-Ogievskiy        'delta':    maximum delta between test_func result and the average
40*7cc8e0a5SVladimir Sementsov-Ogievskiy                    (exists only if at least one run succeeded)
41*7cc8e0a5SVladimir Sementsov-Ogievskiy        'n-failed': number of failed runs (exists only if at least one run
42*7cc8e0a5SVladimir Sementsov-Ogievskiy                    failed)
43*7cc8e0a5SVladimir Sementsov-Ogievskiy    """
44*7cc8e0a5SVladimir Sementsov-Ogievskiy    if initial_run:
45*7cc8e0a5SVladimir Sementsov-Ogievskiy        print('  #initial run:')
46*7cc8e0a5SVladimir Sementsov-Ogievskiy        print('   ', test_func(test_env, test_case))
47*7cc8e0a5SVladimir Sementsov-Ogievskiy
48*7cc8e0a5SVladimir Sementsov-Ogievskiy    runs = []
49*7cc8e0a5SVladimir Sementsov-Ogievskiy    for i in range(count):
50*7cc8e0a5SVladimir Sementsov-Ogievskiy        print('  #run {}'.format(i+1))
51*7cc8e0a5SVladimir Sementsov-Ogievskiy        res = test_func(test_env, test_case)
52*7cc8e0a5SVladimir Sementsov-Ogievskiy        print('   ', res)
53*7cc8e0a5SVladimir Sementsov-Ogievskiy        runs.append(res)
54*7cc8e0a5SVladimir Sementsov-Ogievskiy
55*7cc8e0a5SVladimir Sementsov-Ogievskiy    result = {'runs': runs}
56*7cc8e0a5SVladimir Sementsov-Ogievskiy
57*7cc8e0a5SVladimir Sementsov-Ogievskiy    successed = [r for r in runs if ('seconds' in r)]
58*7cc8e0a5SVladimir Sementsov-Ogievskiy    if successed:
59*7cc8e0a5SVladimir Sementsov-Ogievskiy        avg = sum(r['seconds'] for r in successed) / len(successed)
60*7cc8e0a5SVladimir Sementsov-Ogievskiy        result['average'] = avg
61*7cc8e0a5SVladimir Sementsov-Ogievskiy        result['delta'] = max(abs(r['seconds'] - avg) for r in successed)
62*7cc8e0a5SVladimir Sementsov-Ogievskiy
63*7cc8e0a5SVladimir Sementsov-Ogievskiy    if len(successed) < count:
64*7cc8e0a5SVladimir Sementsov-Ogievskiy        result['n-failed'] = count - len(successed)
65*7cc8e0a5SVladimir Sementsov-Ogievskiy
66*7cc8e0a5SVladimir Sementsov-Ogievskiy    return result
67*7cc8e0a5SVladimir Sementsov-Ogievskiy
68*7cc8e0a5SVladimir Sementsov-Ogievskiy
69*7cc8e0a5SVladimir Sementsov-Ogievskiydef ascii_one(result):
70*7cc8e0a5SVladimir Sementsov-Ogievskiy    """Return ASCII representation of bench_one() returned dict."""
71*7cc8e0a5SVladimir Sementsov-Ogievskiy    if 'average' in result:
72*7cc8e0a5SVladimir Sementsov-Ogievskiy        s = '{:.2f} +- {:.2f}'.format(result['average'], result['delta'])
73*7cc8e0a5SVladimir Sementsov-Ogievskiy        if 'n-failed' in result:
74*7cc8e0a5SVladimir Sementsov-Ogievskiy            s += '\n({} failed)'.format(result['n-failed'])
75*7cc8e0a5SVladimir Sementsov-Ogievskiy        return s
76*7cc8e0a5SVladimir Sementsov-Ogievskiy    else:
77*7cc8e0a5SVladimir Sementsov-Ogievskiy        return 'FAILED'
78*7cc8e0a5SVladimir Sementsov-Ogievskiy
79*7cc8e0a5SVladimir Sementsov-Ogievskiy
80*7cc8e0a5SVladimir Sementsov-Ogievskiydef bench(test_func, test_envs, test_cases, *args, **vargs):
81*7cc8e0a5SVladimir Sementsov-Ogievskiy    """Fill benchmark table
82*7cc8e0a5SVladimir Sementsov-Ogievskiy
83*7cc8e0a5SVladimir Sementsov-Ogievskiy    test_func -- benchmarking function, see bench_one for description
84*7cc8e0a5SVladimir Sementsov-Ogievskiy    test_envs -- list of test environments, see bench_one
85*7cc8e0a5SVladimir Sementsov-Ogievskiy    test_cases -- list of test cases, see bench_one
86*7cc8e0a5SVladimir Sementsov-Ogievskiy    args, vargs -- additional arguments for bench_one
87*7cc8e0a5SVladimir Sementsov-Ogievskiy
88*7cc8e0a5SVladimir Sementsov-Ogievskiy    Returns dict with the following fields:
89*7cc8e0a5SVladimir Sementsov-Ogievskiy        'envs':  test_envs
90*7cc8e0a5SVladimir Sementsov-Ogievskiy        'cases': test_cases
91*7cc8e0a5SVladimir Sementsov-Ogievskiy        'tab':   filled 2D array, where cell [i][j] is bench_one result for
92*7cc8e0a5SVladimir Sementsov-Ogievskiy                 test_cases[i] for test_envs[j] (i.e., rows are test cases and
93*7cc8e0a5SVladimir Sementsov-Ogievskiy                 columns are test environments)
94*7cc8e0a5SVladimir Sementsov-Ogievskiy    """
95*7cc8e0a5SVladimir Sementsov-Ogievskiy    tab = {}
96*7cc8e0a5SVladimir Sementsov-Ogievskiy    results = {
97*7cc8e0a5SVladimir Sementsov-Ogievskiy        'envs': test_envs,
98*7cc8e0a5SVladimir Sementsov-Ogievskiy        'cases': test_cases,
99*7cc8e0a5SVladimir Sementsov-Ogievskiy        'tab': tab
100*7cc8e0a5SVladimir Sementsov-Ogievskiy    }
101*7cc8e0a5SVladimir Sementsov-Ogievskiy    n = 1
102*7cc8e0a5SVladimir Sementsov-Ogievskiy    n_tests = len(test_envs) * len(test_cases)
103*7cc8e0a5SVladimir Sementsov-Ogievskiy    for env in test_envs:
104*7cc8e0a5SVladimir Sementsov-Ogievskiy        for case in test_cases:
105*7cc8e0a5SVladimir Sementsov-Ogievskiy            print('Testing {}/{}: {} :: {}'.format(n, n_tests,
106*7cc8e0a5SVladimir Sementsov-Ogievskiy                                                   env['id'], case['id']))
107*7cc8e0a5SVladimir Sementsov-Ogievskiy            if case['id'] not in tab:
108*7cc8e0a5SVladimir Sementsov-Ogievskiy                tab[case['id']] = {}
109*7cc8e0a5SVladimir Sementsov-Ogievskiy            tab[case['id']][env['id']] = bench_one(test_func, env, case,
110*7cc8e0a5SVladimir Sementsov-Ogievskiy                                                   *args, **vargs)
111*7cc8e0a5SVladimir Sementsov-Ogievskiy            n += 1
112*7cc8e0a5SVladimir Sementsov-Ogievskiy
113*7cc8e0a5SVladimir Sementsov-Ogievskiy    print('Done')
114*7cc8e0a5SVladimir Sementsov-Ogievskiy    return results
115*7cc8e0a5SVladimir Sementsov-Ogievskiy
116*7cc8e0a5SVladimir Sementsov-Ogievskiy
117*7cc8e0a5SVladimir Sementsov-Ogievskiydef ascii(results):
118*7cc8e0a5SVladimir Sementsov-Ogievskiy    """Return ASCII representation of bench() returned dict."""
119*7cc8e0a5SVladimir Sementsov-Ogievskiy    from tabulate import tabulate
120*7cc8e0a5SVladimir Sementsov-Ogievskiy
121*7cc8e0a5SVladimir Sementsov-Ogievskiy    tab = [[""] + [c['id'] for c in results['envs']]]
122*7cc8e0a5SVladimir Sementsov-Ogievskiy    for case in results['cases']:
123*7cc8e0a5SVladimir Sementsov-Ogievskiy        row = [case['id']]
124*7cc8e0a5SVladimir Sementsov-Ogievskiy        for env in results['envs']:
125*7cc8e0a5SVladimir Sementsov-Ogievskiy            row.append(ascii_one(results['tab'][case['id']][env['id']]))
126*7cc8e0a5SVladimir Sementsov-Ogievskiy        tab.append(row)
127*7cc8e0a5SVladimir Sementsov-Ogievskiy
128*7cc8e0a5SVladimir Sementsov-Ogievskiy    return tabulate(tab)
129