xref: /qemu/scripts/simplebench/simplebench.py (revision 6c769690ac845fa62642a5f93b4e4bd906adab95)
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*8c8407feSVladimir Sementsov-Ogievskiyimport subprocess
23dab34698SVladimir Sementsov-Ogievskiyimport time
24f52e1af0SVladimir Sementsov-Ogievskiy
257cc8e0a5SVladimir Sementsov-Ogievskiy
26*8c8407feSVladimir Sementsov-Ogievskiydef do_drop_caches():
27*8c8407feSVladimir Sementsov-Ogievskiy    subprocess.run('sync; echo 3 > /proc/sys/vm/drop_caches', shell=True,
28*8c8407feSVladimir Sementsov-Ogievskiy                   check=True)
29*8c8407feSVladimir Sementsov-Ogievskiy
30*8c8407feSVladimir Sementsov-Ogievskiy
31dab34698SVladimir Sementsov-Ogievskiydef bench_one(test_func, test_env, test_case, count=5, initial_run=True,
32*8c8407feSVladimir Sementsov-Ogievskiy              slow_limit=100, drop_caches=False):
337cc8e0a5SVladimir Sementsov-Ogievskiy    """Benchmark one test-case
347cc8e0a5SVladimir Sementsov-Ogievskiy
357cc8e0a5SVladimir Sementsov-Ogievskiy    test_func   -- benchmarking function with prototype
367cc8e0a5SVladimir Sementsov-Ogievskiy                   test_func(env, case), which takes test_env and test_case
374a44554aSVladimir Sementsov-Ogievskiy                   arguments and on success returns dict with 'seconds' or
384a44554aSVladimir Sementsov-Ogievskiy                   'iops' (or both) fields, specifying the benchmark result.
394a44554aSVladimir Sementsov-Ogievskiy                   If both 'iops' and 'seconds' provided, the 'iops' is
404a44554aSVladimir Sementsov-Ogievskiy                   considered the main, and 'seconds' is just an additional
414a44554aSVladimir Sementsov-Ogievskiy                   info. On failure test_func should return {'error': str}.
424a44554aSVladimir Sementsov-Ogievskiy                   Returned dict may contain any other additional fields.
437cc8e0a5SVladimir Sementsov-Ogievskiy    test_env    -- test environment - opaque first argument for test_func
447cc8e0a5SVladimir Sementsov-Ogievskiy    test_case   -- test case - opaque second argument for test_func
457cc8e0a5SVladimir Sementsov-Ogievskiy    count       -- how many times to call test_func, to calculate average
467cc8e0a5SVladimir Sementsov-Ogievskiy    initial_run -- do initial run of test_func, which don't get into result
47dab34698SVladimir Sementsov-Ogievskiy    slow_limit  -- stop at slow run (that exceedes the slow_limit by seconds).
48dab34698SVladimir Sementsov-Ogievskiy                   (initial run is not measured)
49*8c8407feSVladimir Sementsov-Ogievskiy    drop_caches -- drop caches before each run
507cc8e0a5SVladimir Sementsov-Ogievskiy
517cc8e0a5SVladimir Sementsov-Ogievskiy    Returns dict with the following fields:
527cc8e0a5SVladimir Sementsov-Ogievskiy        'runs':     list of test_func results
534a44554aSVladimir Sementsov-Ogievskiy        'dimension': dimension of results, may be 'seconds' or 'iops'
544a44554aSVladimir Sementsov-Ogievskiy        'average':  average value (iops or seconds) per run (exists only if at
554a44554aSVladimir Sementsov-Ogievskiy                    least one run succeeded)
56f52e1af0SVladimir Sementsov-Ogievskiy        'stdev':    standard deviation of results
577cc8e0a5SVladimir Sementsov-Ogievskiy                    (exists only if at least one run succeeded)
587cc8e0a5SVladimir Sementsov-Ogievskiy        'n-failed': number of failed runs (exists only if at least one run
597cc8e0a5SVladimir Sementsov-Ogievskiy                    failed)
607cc8e0a5SVladimir Sementsov-Ogievskiy    """
617cc8e0a5SVladimir Sementsov-Ogievskiy    if initial_run:
627cc8e0a5SVladimir Sementsov-Ogievskiy        print('  #initial run:')
63*8c8407feSVladimir Sementsov-Ogievskiy        do_drop_caches()
647cc8e0a5SVladimir Sementsov-Ogievskiy        print('   ', test_func(test_env, test_case))
657cc8e0a5SVladimir Sementsov-Ogievskiy
667cc8e0a5SVladimir Sementsov-Ogievskiy    runs = []
677cc8e0a5SVladimir Sementsov-Ogievskiy    for i in range(count):
68dab34698SVladimir Sementsov-Ogievskiy        t = time.time()
69dab34698SVladimir Sementsov-Ogievskiy
707cc8e0a5SVladimir Sementsov-Ogievskiy        print('  #run {}'.format(i+1))
71*8c8407feSVladimir Sementsov-Ogievskiy        do_drop_caches()
727cc8e0a5SVladimir Sementsov-Ogievskiy        res = test_func(test_env, test_case)
737cc8e0a5SVladimir Sementsov-Ogievskiy        print('   ', res)
747cc8e0a5SVladimir Sementsov-Ogievskiy        runs.append(res)
757cc8e0a5SVladimir Sementsov-Ogievskiy
76dab34698SVladimir Sementsov-Ogievskiy        if time.time() - t > slow_limit:
77dab34698SVladimir Sementsov-Ogievskiy            print('    - run is too slow, stop here')
78dab34698SVladimir Sementsov-Ogievskiy            break
79dab34698SVladimir Sementsov-Ogievskiy
80dab34698SVladimir Sementsov-Ogievskiy    count = len(runs)
81dab34698SVladimir Sementsov-Ogievskiy
827cc8e0a5SVladimir Sementsov-Ogievskiy    result = {'runs': runs}
837cc8e0a5SVladimir Sementsov-Ogievskiy
844a44554aSVladimir Sementsov-Ogievskiy    succeeded = [r for r in runs if ('seconds' in r or 'iops' in r)]
85270124e7SVladimir Sementsov-Ogievskiy    if succeeded:
864a44554aSVladimir Sementsov-Ogievskiy        if 'iops' in succeeded[0]:
874a44554aSVladimir Sementsov-Ogievskiy            assert all('iops' in r for r in succeeded)
884a44554aSVladimir Sementsov-Ogievskiy            dim = 'iops'
894a44554aSVladimir Sementsov-Ogievskiy        else:
904a44554aSVladimir Sementsov-Ogievskiy            assert all('seconds' in r for r in succeeded)
914a44554aSVladimir Sementsov-Ogievskiy            assert all('iops' not in r for r in succeeded)
924a44554aSVladimir Sementsov-Ogievskiy            dim = 'seconds'
934a44554aSVladimir Sementsov-Ogievskiy        result['dimension'] = dim
94f52e1af0SVladimir Sementsov-Ogievskiy        result['average'] = statistics.mean(r[dim] for r in succeeded)
9527eacb39SVladimir Sementsov-Ogievskiy        if len(succeeded) == 1:
9627eacb39SVladimir Sementsov-Ogievskiy            result['stdev'] = 0
9727eacb39SVladimir Sementsov-Ogievskiy        else:
98f52e1af0SVladimir Sementsov-Ogievskiy            result['stdev'] = statistics.stdev(r[dim] for r in succeeded)
997cc8e0a5SVladimir Sementsov-Ogievskiy
100270124e7SVladimir Sementsov-Ogievskiy    if len(succeeded) < count:
101270124e7SVladimir Sementsov-Ogievskiy        result['n-failed'] = count - len(succeeded)
1027cc8e0a5SVladimir Sementsov-Ogievskiy
1037cc8e0a5SVladimir Sementsov-Ogievskiy    return result
1047cc8e0a5SVladimir Sementsov-Ogievskiy
1057cc8e0a5SVladimir Sementsov-Ogievskiy
1067cc8e0a5SVladimir Sementsov-Ogievskiydef bench(test_func, test_envs, test_cases, *args, **vargs):
1077cc8e0a5SVladimir Sementsov-Ogievskiy    """Fill benchmark table
1087cc8e0a5SVladimir Sementsov-Ogievskiy
1097cc8e0a5SVladimir Sementsov-Ogievskiy    test_func -- benchmarking function, see bench_one for description
1107cc8e0a5SVladimir Sementsov-Ogievskiy    test_envs -- list of test environments, see bench_one
1117cc8e0a5SVladimir Sementsov-Ogievskiy    test_cases -- list of test cases, see bench_one
1127cc8e0a5SVladimir Sementsov-Ogievskiy    args, vargs -- additional arguments for bench_one
1137cc8e0a5SVladimir Sementsov-Ogievskiy
1147cc8e0a5SVladimir Sementsov-Ogievskiy    Returns dict with the following fields:
1157cc8e0a5SVladimir Sementsov-Ogievskiy        'envs':  test_envs
1167cc8e0a5SVladimir Sementsov-Ogievskiy        'cases': test_cases
1177cc8e0a5SVladimir Sementsov-Ogievskiy        'tab':   filled 2D array, where cell [i][j] is bench_one result for
1187cc8e0a5SVladimir Sementsov-Ogievskiy                 test_cases[i] for test_envs[j] (i.e., rows are test cases and
1197cc8e0a5SVladimir Sementsov-Ogievskiy                 columns are test environments)
1207cc8e0a5SVladimir Sementsov-Ogievskiy    """
1217cc8e0a5SVladimir Sementsov-Ogievskiy    tab = {}
1227cc8e0a5SVladimir Sementsov-Ogievskiy    results = {
1237cc8e0a5SVladimir Sementsov-Ogievskiy        'envs': test_envs,
1247cc8e0a5SVladimir Sementsov-Ogievskiy        'cases': test_cases,
1257cc8e0a5SVladimir Sementsov-Ogievskiy        'tab': tab
1267cc8e0a5SVladimir Sementsov-Ogievskiy    }
1277cc8e0a5SVladimir Sementsov-Ogievskiy    n = 1
1287cc8e0a5SVladimir Sementsov-Ogievskiy    n_tests = len(test_envs) * len(test_cases)
1297cc8e0a5SVladimir Sementsov-Ogievskiy    for env in test_envs:
1307cc8e0a5SVladimir Sementsov-Ogievskiy        for case in test_cases:
1317cc8e0a5SVladimir Sementsov-Ogievskiy            print('Testing {}/{}: {} :: {}'.format(n, n_tests,
1327cc8e0a5SVladimir Sementsov-Ogievskiy                                                   env['id'], case['id']))
1337cc8e0a5SVladimir Sementsov-Ogievskiy            if case['id'] not in tab:
1347cc8e0a5SVladimir Sementsov-Ogievskiy                tab[case['id']] = {}
1357cc8e0a5SVladimir Sementsov-Ogievskiy            tab[case['id']][env['id']] = bench_one(test_func, env, case,
1367cc8e0a5SVladimir Sementsov-Ogievskiy                                                   *args, **vargs)
1377cc8e0a5SVladimir Sementsov-Ogievskiy            n += 1
1387cc8e0a5SVladimir Sementsov-Ogievskiy
1397cc8e0a5SVladimir Sementsov-Ogievskiy    print('Done')
1407cc8e0a5SVladimir Sementsov-Ogievskiy    return results
141