xref: /qemu/scripts/simplebench/simplebench.py (revision dab346986e1e91dfd28671e9fb97970a9eb15fca)
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
21f52e1af0SVladimir Sementsov-Ogievskiyimport statistics
22*dab34698SVladimir Sementsov-Ogievskiyimport time
23f52e1af0SVladimir Sementsov-Ogievskiy
247cc8e0a5SVladimir Sementsov-Ogievskiy
25*dab34698SVladimir Sementsov-Ogievskiydef bench_one(test_func, test_env, test_case, count=5, initial_run=True,
26*dab34698SVladimir Sementsov-Ogievskiy              slow_limit=100):
277cc8e0a5SVladimir Sementsov-Ogievskiy    """Benchmark one test-case
287cc8e0a5SVladimir Sementsov-Ogievskiy
297cc8e0a5SVladimir Sementsov-Ogievskiy    test_func   -- benchmarking function with prototype
307cc8e0a5SVladimir Sementsov-Ogievskiy                   test_func(env, case), which takes test_env and test_case
314a44554aSVladimir Sementsov-Ogievskiy                   arguments and on success returns dict with 'seconds' or
324a44554aSVladimir Sementsov-Ogievskiy                   'iops' (or both) fields, specifying the benchmark result.
334a44554aSVladimir Sementsov-Ogievskiy                   If both 'iops' and 'seconds' provided, the 'iops' is
344a44554aSVladimir Sementsov-Ogievskiy                   considered the main, and 'seconds' is just an additional
354a44554aSVladimir Sementsov-Ogievskiy                   info. On failure test_func should return {'error': str}.
364a44554aSVladimir Sementsov-Ogievskiy                   Returned dict may contain any other additional fields.
377cc8e0a5SVladimir Sementsov-Ogievskiy    test_env    -- test environment - opaque first argument for test_func
387cc8e0a5SVladimir Sementsov-Ogievskiy    test_case   -- test case - opaque second argument for test_func
397cc8e0a5SVladimir Sementsov-Ogievskiy    count       -- how many times to call test_func, to calculate average
407cc8e0a5SVladimir Sementsov-Ogievskiy    initial_run -- do initial run of test_func, which don't get into result
41*dab34698SVladimir Sementsov-Ogievskiy    slow_limit  -- stop at slow run (that exceedes the slow_limit by seconds).
42*dab34698SVladimir Sementsov-Ogievskiy                   (initial run is not measured)
437cc8e0a5SVladimir Sementsov-Ogievskiy
447cc8e0a5SVladimir Sementsov-Ogievskiy    Returns dict with the following fields:
457cc8e0a5SVladimir Sementsov-Ogievskiy        'runs':     list of test_func results
464a44554aSVladimir Sementsov-Ogievskiy        'dimension': dimension of results, may be 'seconds' or 'iops'
474a44554aSVladimir Sementsov-Ogievskiy        'average':  average value (iops or seconds) per run (exists only if at
484a44554aSVladimir Sementsov-Ogievskiy                    least one run succeeded)
49f52e1af0SVladimir Sementsov-Ogievskiy        'stdev':    standard deviation of results
507cc8e0a5SVladimir Sementsov-Ogievskiy                    (exists only if at least one run succeeded)
517cc8e0a5SVladimir Sementsov-Ogievskiy        'n-failed': number of failed runs (exists only if at least one run
527cc8e0a5SVladimir Sementsov-Ogievskiy                    failed)
537cc8e0a5SVladimir Sementsov-Ogievskiy    """
547cc8e0a5SVladimir Sementsov-Ogievskiy    if initial_run:
557cc8e0a5SVladimir Sementsov-Ogievskiy        print('  #initial run:')
567cc8e0a5SVladimir Sementsov-Ogievskiy        print('   ', test_func(test_env, test_case))
577cc8e0a5SVladimir Sementsov-Ogievskiy
587cc8e0a5SVladimir Sementsov-Ogievskiy    runs = []
597cc8e0a5SVladimir Sementsov-Ogievskiy    for i in range(count):
60*dab34698SVladimir Sementsov-Ogievskiy        t = time.time()
61*dab34698SVladimir Sementsov-Ogievskiy
627cc8e0a5SVladimir Sementsov-Ogievskiy        print('  #run {}'.format(i+1))
637cc8e0a5SVladimir Sementsov-Ogievskiy        res = test_func(test_env, test_case)
647cc8e0a5SVladimir Sementsov-Ogievskiy        print('   ', res)
657cc8e0a5SVladimir Sementsov-Ogievskiy        runs.append(res)
667cc8e0a5SVladimir Sementsov-Ogievskiy
67*dab34698SVladimir Sementsov-Ogievskiy        if time.time() - t > slow_limit:
68*dab34698SVladimir Sementsov-Ogievskiy            print('    - run is too slow, stop here')
69*dab34698SVladimir Sementsov-Ogievskiy            break
70*dab34698SVladimir Sementsov-Ogievskiy
71*dab34698SVladimir Sementsov-Ogievskiy    count = len(runs)
72*dab34698SVladimir Sementsov-Ogievskiy
737cc8e0a5SVladimir Sementsov-Ogievskiy    result = {'runs': runs}
747cc8e0a5SVladimir Sementsov-Ogievskiy
754a44554aSVladimir Sementsov-Ogievskiy    succeeded = [r for r in runs if ('seconds' in r or 'iops' in r)]
76270124e7SVladimir Sementsov-Ogievskiy    if succeeded:
774a44554aSVladimir Sementsov-Ogievskiy        if 'iops' in succeeded[0]:
784a44554aSVladimir Sementsov-Ogievskiy            assert all('iops' in r for r in succeeded)
794a44554aSVladimir Sementsov-Ogievskiy            dim = 'iops'
804a44554aSVladimir Sementsov-Ogievskiy        else:
814a44554aSVladimir Sementsov-Ogievskiy            assert all('seconds' in r for r in succeeded)
824a44554aSVladimir Sementsov-Ogievskiy            assert all('iops' not in r for r in succeeded)
834a44554aSVladimir Sementsov-Ogievskiy            dim = 'seconds'
844a44554aSVladimir Sementsov-Ogievskiy        result['dimension'] = dim
85f52e1af0SVladimir Sementsov-Ogievskiy        result['average'] = statistics.mean(r[dim] for r in succeeded)
86f52e1af0SVladimir Sementsov-Ogievskiy        result['stdev'] = statistics.stdev(r[dim] for r in succeeded)
877cc8e0a5SVladimir Sementsov-Ogievskiy
88270124e7SVladimir Sementsov-Ogievskiy    if len(succeeded) < count:
89270124e7SVladimir Sementsov-Ogievskiy        result['n-failed'] = count - len(succeeded)
907cc8e0a5SVladimir Sementsov-Ogievskiy
917cc8e0a5SVladimir Sementsov-Ogievskiy    return result
927cc8e0a5SVladimir Sementsov-Ogievskiy
937cc8e0a5SVladimir Sementsov-Ogievskiy
947cc8e0a5SVladimir Sementsov-Ogievskiydef bench(test_func, test_envs, test_cases, *args, **vargs):
957cc8e0a5SVladimir Sementsov-Ogievskiy    """Fill benchmark table
967cc8e0a5SVladimir Sementsov-Ogievskiy
977cc8e0a5SVladimir Sementsov-Ogievskiy    test_func -- benchmarking function, see bench_one for description
987cc8e0a5SVladimir Sementsov-Ogievskiy    test_envs -- list of test environments, see bench_one
997cc8e0a5SVladimir Sementsov-Ogievskiy    test_cases -- list of test cases, see bench_one
1007cc8e0a5SVladimir Sementsov-Ogievskiy    args, vargs -- additional arguments for bench_one
1017cc8e0a5SVladimir Sementsov-Ogievskiy
1027cc8e0a5SVladimir Sementsov-Ogievskiy    Returns dict with the following fields:
1037cc8e0a5SVladimir Sementsov-Ogievskiy        'envs':  test_envs
1047cc8e0a5SVladimir Sementsov-Ogievskiy        'cases': test_cases
1057cc8e0a5SVladimir Sementsov-Ogievskiy        'tab':   filled 2D array, where cell [i][j] is bench_one result for
1067cc8e0a5SVladimir Sementsov-Ogievskiy                 test_cases[i] for test_envs[j] (i.e., rows are test cases and
1077cc8e0a5SVladimir Sementsov-Ogievskiy                 columns are test environments)
1087cc8e0a5SVladimir Sementsov-Ogievskiy    """
1097cc8e0a5SVladimir Sementsov-Ogievskiy    tab = {}
1107cc8e0a5SVladimir Sementsov-Ogievskiy    results = {
1117cc8e0a5SVladimir Sementsov-Ogievskiy        'envs': test_envs,
1127cc8e0a5SVladimir Sementsov-Ogievskiy        'cases': test_cases,
1137cc8e0a5SVladimir Sementsov-Ogievskiy        'tab': tab
1147cc8e0a5SVladimir Sementsov-Ogievskiy    }
1157cc8e0a5SVladimir Sementsov-Ogievskiy    n = 1
1167cc8e0a5SVladimir Sementsov-Ogievskiy    n_tests = len(test_envs) * len(test_cases)
1177cc8e0a5SVladimir Sementsov-Ogievskiy    for env in test_envs:
1187cc8e0a5SVladimir Sementsov-Ogievskiy        for case in test_cases:
1197cc8e0a5SVladimir Sementsov-Ogievskiy            print('Testing {}/{}: {} :: {}'.format(n, n_tests,
1207cc8e0a5SVladimir Sementsov-Ogievskiy                                                   env['id'], case['id']))
1217cc8e0a5SVladimir Sementsov-Ogievskiy            if case['id'] not in tab:
1227cc8e0a5SVladimir Sementsov-Ogievskiy                tab[case['id']] = {}
1237cc8e0a5SVladimir Sementsov-Ogievskiy            tab[case['id']][env['id']] = bench_one(test_func, env, case,
1247cc8e0a5SVladimir Sementsov-Ogievskiy                                                   *args, **vargs)
1257cc8e0a5SVladimir Sementsov-Ogievskiy            n += 1
1267cc8e0a5SVladimir Sementsov-Ogievskiy
1277cc8e0a5SVladimir Sementsov-Ogievskiy    print('Done')
1287cc8e0a5SVladimir Sementsov-Ogievskiy    return results
129