From 270124e7efcaaef68c492d1293af975992138606 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Wed, 21 Oct 2020 17:58:51 +0300 Subject: scripts/simplebench: fix grammar: s/successed/succeeded/ Signed-off-by: Vladimir Sementsov-Ogievskiy Message-Id: <20201021145859.11201-14-vsementsov@virtuozzo.com> Reviewed-by: Max Reitz Signed-off-by: Max Reitz --- scripts/simplebench/simplebench.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'scripts') diff --git a/scripts/simplebench/simplebench.py b/scripts/simplebench/simplebench.py index 59e7314ff6..2445932fc2 100644 --- a/scripts/simplebench/simplebench.py +++ b/scripts/simplebench/simplebench.py @@ -54,14 +54,14 @@ def bench_one(test_func, test_env, test_case, count=5, initial_run=True): result = {'runs': runs} - successed = [r for r in runs if ('seconds' in r)] - if successed: - avg = sum(r['seconds'] for r in successed) / len(successed) + succeeded = [r for r in runs if ('seconds' in r)] + if succeeded: + avg = sum(r['seconds'] for r in succeeded) / len(succeeded) result['average'] = avg - result['delta'] = max(abs(r['seconds'] - avg) for r in successed) + result['delta'] = max(abs(r['seconds'] - avg) for r in succeeded) - if len(successed) < count: - result['n-failed'] = count - len(successed) + if len(succeeded) < count: + result['n-failed'] = count - len(succeeded) return result -- cgit v1.2.3-55-g7522 From 4a44554a65dfc5ccd5dad428981d0ab2959b4b8f Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Wed, 21 Oct 2020 17:58:52 +0300 Subject: scripts/simplebench: support iops Support benchmarks returning not seconds but iops. We'll use it for further new test. Signed-off-by: Vladimir Sementsov-Ogievskiy Message-Id: <20201021145859.11201-15-vsementsov@virtuozzo.com> Reviewed-by: Max Reitz Signed-off-by: Max Reitz --- scripts/simplebench/simplebench.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) (limited to 'scripts') diff --git a/scripts/simplebench/simplebench.py b/scripts/simplebench/simplebench.py index 2445932fc2..2251cd34ea 100644 --- a/scripts/simplebench/simplebench.py +++ b/scripts/simplebench/simplebench.py @@ -24,9 +24,12 @@ def bench_one(test_func, test_env, test_case, count=5, initial_run=True): test_func -- benchmarking function with prototype test_func(env, case), which takes test_env and test_case - arguments and returns {'seconds': int} (which is benchmark - result) on success and {'error': str} on error. Returned - dict may contain any other additional fields. + arguments and on success returns dict with 'seconds' or + 'iops' (or both) fields, specifying the benchmark result. + If both 'iops' and 'seconds' provided, the 'iops' is + considered the main, and 'seconds' is just an additional + info. On failure test_func should return {'error': str}. + Returned dict may contain any other additional fields. test_env -- test environment - opaque first argument for test_func test_case -- test case - opaque second argument for test_func count -- how many times to call test_func, to calculate average @@ -34,8 +37,9 @@ def bench_one(test_func, test_env, test_case, count=5, initial_run=True): Returns dict with the following fields: 'runs': list of test_func results - 'average': average seconds per run (exists only if at least one run - succeeded) + 'dimension': dimension of results, may be 'seconds' or 'iops' + 'average': average value (iops or seconds) per run (exists only if at + least one run succeeded) 'delta': maximum delta between test_func result and the average (exists only if at least one run succeeded) 'n-failed': number of failed runs (exists only if at least one run @@ -54,11 +58,19 @@ def bench_one(test_func, test_env, test_case, count=5, initial_run=True): result = {'runs': runs} - succeeded = [r for r in runs if ('seconds' in r)] + succeeded = [r for r in runs if ('seconds' in r or 'iops' in r)] if succeeded: - avg = sum(r['seconds'] for r in succeeded) / len(succeeded) + if 'iops' in succeeded[0]: + assert all('iops' in r for r in succeeded) + dim = 'iops' + else: + assert all('seconds' in r for r in succeeded) + assert all('iops' not in r for r in succeeded) + dim = 'seconds' + avg = sum(r[dim] for r in succeeded) / len(succeeded) + result['dimension'] = dim result['average'] = avg - result['delta'] = max(abs(r['seconds'] - avg) for r in succeeded) + result['delta'] = max(abs(r[dim] - avg) for r in succeeded) if len(succeeded) < count: result['n-failed'] = count - len(succeeded) @@ -118,11 +130,17 @@ def ascii(results): """Return ASCII representation of bench() returned dict.""" from tabulate import tabulate + dim = None tab = [[""] + [c['id'] for c in results['envs']]] for case in results['cases']: row = [case['id']] for env in results['envs']: - row.append(ascii_one(results['tab'][case['id']][env['id']])) + res = results['tab'][case['id']][env['id']] + if dim is None: + dim = res['dimension'] + else: + assert dim == res['dimension'] + row.append(ascii_one(res)) tab.append(row) - return tabulate(tab) + return f'All results are in {dim}\n\n' + tabulate(tab) -- cgit v1.2.3-55-g7522 From f52e1af0b08af93b5354fe2648eccaec6bb8a2b2 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Wed, 21 Oct 2020 17:58:53 +0300 Subject: scripts/simplebench: use standard deviation for +- error Standard deviation is more usual to see after +- than current maximum of deviations. Signed-off-by: Vladimir Sementsov-Ogievskiy Message-Id: <20201021145859.11201-16-vsementsov@virtuozzo.com> Reviewed-by: Max Reitz Signed-off-by: Max Reitz --- scripts/simplebench/simplebench.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'scripts') diff --git a/scripts/simplebench/simplebench.py b/scripts/simplebench/simplebench.py index 2251cd34ea..55ec1ad5db 100644 --- a/scripts/simplebench/simplebench.py +++ b/scripts/simplebench/simplebench.py @@ -18,6 +18,8 @@ # along with this program. If not, see . # +import statistics + def bench_one(test_func, test_env, test_case, count=5, initial_run=True): """Benchmark one test-case @@ -40,7 +42,7 @@ def bench_one(test_func, test_env, test_case, count=5, initial_run=True): 'dimension': dimension of results, may be 'seconds' or 'iops' 'average': average value (iops or seconds) per run (exists only if at least one run succeeded) - 'delta': maximum delta between test_func result and the average + 'stdev': standard deviation of results (exists only if at least one run succeeded) 'n-failed': number of failed runs (exists only if at least one run failed) @@ -67,10 +69,9 @@ def bench_one(test_func, test_env, test_case, count=5, initial_run=True): assert all('seconds' in r for r in succeeded) assert all('iops' not in r for r in succeeded) dim = 'seconds' - avg = sum(r[dim] for r in succeeded) / len(succeeded) result['dimension'] = dim - result['average'] = avg - result['delta'] = max(abs(r[dim] - avg) for r in succeeded) + result['average'] = statistics.mean(r[dim] for r in succeeded) + result['stdev'] = statistics.stdev(r[dim] for r in succeeded) if len(succeeded) < count: result['n-failed'] = count - len(succeeded) @@ -81,7 +82,7 @@ def bench_one(test_func, test_env, test_case, count=5, initial_run=True): def ascii_one(result): """Return ASCII representation of bench_one() returned dict.""" if 'average' in result: - s = '{:.2f} +- {:.2f}'.format(result['average'], result['delta']) + s = '{:.2f} +- {:.2f}'.format(result['average'], result['stdev']) if 'n-failed' in result: s += '\n({} failed)'.format(result['n-failed']) return s -- cgit v1.2.3-55-g7522 From bfccfa62ac771400a146dfe768a900f9f6e64467 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Wed, 21 Oct 2020 17:58:54 +0300 Subject: simplebench: rename ascii() to results_to_text() Next patch will use utf8 plus-minus symbol, let's use more generic (and more readable) name. Signed-off-by: Vladimir Sementsov-Ogievskiy Message-Id: <20201021145859.11201-17-vsementsov@virtuozzo.com> Reviewed-by: Max Reitz Signed-off-by: Max Reitz --- scripts/simplebench/bench-example.py | 2 +- scripts/simplebench/bench_write_req.py | 2 +- scripts/simplebench/simplebench.py | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'scripts') diff --git a/scripts/simplebench/bench-example.py b/scripts/simplebench/bench-example.py index c642a5b891..f24cf22fe9 100644 --- a/scripts/simplebench/bench-example.py +++ b/scripts/simplebench/bench-example.py @@ -77,4 +77,4 @@ test_envs = [ ] result = simplebench.bench(bench_func, test_envs, test_cases, count=3) -print(simplebench.ascii(result)) +print(simplebench.results_to_text(result)) diff --git a/scripts/simplebench/bench_write_req.py b/scripts/simplebench/bench_write_req.py index ca1178fd68..e175bcd7a4 100755 --- a/scripts/simplebench/bench_write_req.py +++ b/scripts/simplebench/bench_write_req.py @@ -167,4 +167,4 @@ if __name__ == '__main__': result = simplebench.bench(bench_func, test_envs, test_cases, count=3, initial_run=False) - print(simplebench.ascii(result)) + print(simplebench.results_to_text(result)) diff --git a/scripts/simplebench/simplebench.py b/scripts/simplebench/simplebench.py index 55ec1ad5db..aa74b78a04 100644 --- a/scripts/simplebench/simplebench.py +++ b/scripts/simplebench/simplebench.py @@ -79,8 +79,8 @@ def bench_one(test_func, test_env, test_case, count=5, initial_run=True): return result -def ascii_one(result): - """Return ASCII representation of bench_one() returned dict.""" +def result_to_text(result): + """Return text representation of bench_one() returned dict.""" if 'average' in result: s = '{:.2f} +- {:.2f}'.format(result['average'], result['stdev']) if 'n-failed' in result: @@ -127,8 +127,8 @@ def bench(test_func, test_envs, test_cases, *args, **vargs): return results -def ascii(results): - """Return ASCII representation of bench() returned dict.""" +def results_to_text(results): + """Return text representation of bench() returned dict.""" from tabulate import tabulate dim = None @@ -141,7 +141,7 @@ def ascii(results): dim = res['dimension'] else: assert dim == res['dimension'] - row.append(ascii_one(res)) + row.append(result_to_text(res)) tab.append(row) return f'All results are in {dim}\n\n' + tabulate(tab) -- cgit v1.2.3-55-g7522 From 8e979febb01222edb1e53fb61a93a4c803924869 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Wed, 21 Oct 2020 17:58:55 +0300 Subject: simplebench: move results_to_text() into separate file Let's keep view part in separate: this way it's better to improve it in the following commits. Signed-off-by: Vladimir Sementsov-Ogievskiy Message-Id: <20201021145859.11201-18-vsementsov@virtuozzo.com> Reviewed-by: Max Reitz Signed-off-by: Max Reitz --- scripts/simplebench/bench-example.py | 3 ++- scripts/simplebench/bench_write_req.py | 3 ++- scripts/simplebench/results_to_text.py | 48 ++++++++++++++++++++++++++++++++++ scripts/simplebench/simplebench.py | 31 ---------------------- 4 files changed, 52 insertions(+), 33 deletions(-) create mode 100644 scripts/simplebench/results_to_text.py (limited to 'scripts') diff --git a/scripts/simplebench/bench-example.py b/scripts/simplebench/bench-example.py index f24cf22fe9..d9c7f7bc17 100644 --- a/scripts/simplebench/bench-example.py +++ b/scripts/simplebench/bench-example.py @@ -19,6 +19,7 @@ # import simplebench +from results_to_text import results_to_text from bench_block_job import bench_block_copy, drv_file, drv_nbd @@ -77,4 +78,4 @@ test_envs = [ ] result = simplebench.bench(bench_func, test_envs, test_cases, count=3) -print(simplebench.results_to_text(result)) +print(results_to_text(result)) diff --git a/scripts/simplebench/bench_write_req.py b/scripts/simplebench/bench_write_req.py index e175bcd7a4..da601ea2fe 100755 --- a/scripts/simplebench/bench_write_req.py +++ b/scripts/simplebench/bench_write_req.py @@ -26,6 +26,7 @@ import sys import os import subprocess import simplebench +from results_to_text import results_to_text def bench_func(env, case): @@ -167,4 +168,4 @@ if __name__ == '__main__': result = simplebench.bench(bench_func, test_envs, test_cases, count=3, initial_run=False) - print(simplebench.results_to_text(result)) + print(results_to_text(result)) diff --git a/scripts/simplebench/results_to_text.py b/scripts/simplebench/results_to_text.py new file mode 100644 index 0000000000..58d909ffd9 --- /dev/null +++ b/scripts/simplebench/results_to_text.py @@ -0,0 +1,48 @@ +# 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 . +# + + +def result_to_text(result): + """Return text representation of bench_one() returned dict.""" + if 'average' in result: + s = '{:.2f} +- {:.2f}'.format(result['average'], result['stdev']) + if 'n-failed' in result: + s += '\n({} failed)'.format(result['n-failed']) + return s + else: + return 'FAILED' + + +def results_to_text(results): + """Return text representation of bench() returned dict.""" + from tabulate import tabulate + + dim = None + tab = [[""] + [c['id'] for c in results['envs']]] + for case in results['cases']: + row = [case['id']] + for env in results['envs']: + res = results['tab'][case['id']][env['id']] + if dim is None: + dim = res['dimension'] + else: + assert dim == res['dimension'] + row.append(result_to_text(res)) + tab.append(row) + + return f'All results are in {dim}\n\n' + tabulate(tab) diff --git a/scripts/simplebench/simplebench.py b/scripts/simplebench/simplebench.py index aa74b78a04..f61513af90 100644 --- a/scripts/simplebench/simplebench.py +++ b/scripts/simplebench/simplebench.py @@ -79,17 +79,6 @@ def bench_one(test_func, test_env, test_case, count=5, initial_run=True): return result -def result_to_text(result): - """Return text representation of bench_one() returned dict.""" - if 'average' in result: - s = '{:.2f} +- {:.2f}'.format(result['average'], result['stdev']) - if 'n-failed' in result: - s += '\n({} failed)'.format(result['n-failed']) - return s - else: - return 'FAILED' - - def bench(test_func, test_envs, test_cases, *args, **vargs): """Fill benchmark table @@ -125,23 +114,3 @@ def bench(test_func, test_envs, test_cases, *args, **vargs): print('Done') return results - - -def results_to_text(results): - """Return text representation of bench() returned dict.""" - from tabulate import tabulate - - dim = None - tab = [[""] + [c['id'] for c in results['envs']]] - for case in results['cases']: - row = [case['id']] - for env in results['envs']: - res = results['tab'][case['id']][env['id']] - if dim is None: - dim = res['dimension'] - else: - assert dim == res['dimension'] - row.append(result_to_text(res)) - tab.append(row) - - return f'All results are in {dim}\n\n' + tabulate(tab) -- cgit v1.2.3-55-g7522 From 96be1aeec73a53364a0a95cd24a9cb70a973a0fd Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Wed, 21 Oct 2020 17:58:56 +0300 Subject: simplebench/results_to_text: improve view of the table Move to generic format for floats and percentage for error. Signed-off-by: Vladimir Sementsov-Ogievskiy Message-Id: <20201021145859.11201-19-vsementsov@virtuozzo.com> Acked-by: Max Reitz Signed-off-by: Max Reitz --- scripts/simplebench/results_to_text.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/simplebench/results_to_text.py b/scripts/simplebench/results_to_text.py index 58d909ffd9..479f7ac1d4 100644 --- a/scripts/simplebench/results_to_text.py +++ b/scripts/simplebench/results_to_text.py @@ -16,11 +16,22 @@ # along with this program. If not, see . # +import math + + +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 = '{:.2f} +- {:.2f}'.format(result['average'], result['stdev']) + s = format_value(result['average'], result['stdev']) if 'n-failed' in result: s += '\n({} failed)'.format(result['n-failed']) return s -- cgit v1.2.3-55-g7522 From aa362403f46848c4377ffa9702008e6a2d5f876e Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Wed, 21 Oct 2020 17:58:57 +0300 Subject: simplebench/results_to_text: add difference line to the table Performance improvements / degradations are usually discussed in percentage. Let's make the script calculate it for us. Signed-off-by: Vladimir Sementsov-Ogievskiy Message-Id: <20201021145859.11201-20-vsementsov@virtuozzo.com> Reviewed-by: Max Reitz [mreitz: 'seconds' instead of 'secs'] Signed-off-by: Max Reitz --- scripts/simplebench/results_to_text.py | 67 ++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 7 deletions(-) (limited to 'scripts') diff --git a/scripts/simplebench/results_to_text.py b/scripts/simplebench/results_to_text.py index 479f7ac1d4..e46940fdf2 100644 --- a/scripts/simplebench/results_to_text.py +++ b/scripts/simplebench/results_to_text.py @@ -17,6 +17,10 @@ # import math +import tabulate + +# We want leading whitespace for difference row cells (see below) +tabulate.PRESERVE_WHITESPACE = True def format_value(x, stdev): @@ -39,21 +43,70 @@ def result_to_text(result): return 'FAILED' -def results_to_text(results): - """Return text representation of bench() returned dict.""" - from tabulate import tabulate - +def results_dimension(results): dim = None - tab = [[""] + [c['id'] for c in results['envs']]] for case in results['cases']: - row = [case['id']] 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) - return f'All results are in {dim}\n\n' + tabulate(tab) + # 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) -- cgit v1.2.3-55-g7522 From 181f60c8c73e60af89b42483b54c14dfebfbc384 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Wed, 21 Oct 2020 17:58:58 +0300 Subject: simplebench/results_to_text: make executable Make results_to_text a tool to dump results saved in JSON file. Signed-off-by: Vladimir Sementsov-Ogievskiy Message-Id: <20201021145859.11201-21-vsementsov@virtuozzo.com> Reviewed-by: Max Reitz Signed-off-by: Max Reitz --- scripts/simplebench/results_to_text.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) mode change 100644 => 100755 scripts/simplebench/results_to_text.py (limited to 'scripts') diff --git a/scripts/simplebench/results_to_text.py b/scripts/simplebench/results_to_text.py old mode 100644 new mode 100755 index e46940fdf2..d561e5e2db --- a/scripts/simplebench/results_to_text.py +++ b/scripts/simplebench/results_to_text.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 +# # Simple benchmarking framework # # Copyright (c) 2019 Virtuozzo International GmbH. @@ -110,3 +112,15 @@ def results_to_text(results): 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))) -- cgit v1.2.3-55-g7522 From cff6d3ca43cdc8da0104204a52b0e4bd644e16e1 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Wed, 21 Oct 2020 17:58:59 +0300 Subject: scripts/simplebench: add bench_prealloc.py Benchmark for new preallocate filter. Example usage: ./bench_prealloc.py ../../build/qemu-img \ ssd-ext4:/path/to/mount/point \ ssd-xfs:/path2 hdd-ext4:/path3 hdd-xfs:/path4 The benchmark shows performance improvement (or degradation) when use new preallocate filter with qcow2 image. Signed-off-by: Vladimir Sementsov-Ogievskiy Message-Id: <20201021145859.11201-22-vsementsov@virtuozzo.com> Reviewed-by: Max Reitz Signed-off-by: Max Reitz --- scripts/simplebench/bench_prealloc.py | 132 ++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100755 scripts/simplebench/bench_prealloc.py (limited to 'scripts') diff --git a/scripts/simplebench/bench_prealloc.py b/scripts/simplebench/bench_prealloc.py new file mode 100755 index 0000000000..85f588c597 --- /dev/null +++ b/scripts/simplebench/bench_prealloc.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# +# Benchmark preallocate filter +# +# Copyright (c) 2020 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 . +# + + +import sys +import os +import subprocess +import re +import json + +import simplebench +from results_to_text import results_to_text + + +def qemu_img_bench(args): + p = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + universal_newlines=True) + + if p.returncode == 0: + try: + m = re.search(r'Run completed in (\d+.\d+) seconds.', p.stdout) + return {'seconds': float(m.group(1))} + except Exception: + return {'error': f'failed to parse qemu-img output: {p.stdout}'} + else: + return {'error': f'qemu-img failed: {p.returncode}: {p.stdout}'} + + +def bench_func(env, case): + fname = f"{case['dir']}/prealloc-test.qcow2" + try: + os.remove(fname) + except OSError: + pass + + subprocess.run([env['qemu-img-binary'], 'create', '-f', 'qcow2', fname, + '16G'], stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, check=True) + + args = [env['qemu-img-binary'], 'bench', '-c', str(case['count']), + '-d', '64', '-s', case['block-size'], '-t', 'none', '-n', '-w'] + if env['prealloc']: + args += ['--image-opts', + 'driver=qcow2,file.driver=preallocate,file.file.driver=file,' + f'file.file.filename={fname}'] + else: + args += ['-f', 'qcow2', fname] + + return qemu_img_bench(args) + + +def auto_count_bench_func(env, case): + case['count'] = 100 + while True: + res = bench_func(env, case) + if 'error' in res: + return res + + if res['seconds'] >= 1: + break + + case['count'] *= 10 + + if res['seconds'] < 5: + case['count'] = round(case['count'] * 5 / res['seconds']) + res = bench_func(env, case) + if 'error' in res: + return res + + res['iops'] = case['count'] / res['seconds'] + return res + + +if __name__ == '__main__': + if len(sys.argv) < 2: + print(f'USAGE: {sys.argv[0]} ' + 'DISK_NAME:DIR_PATH ...') + exit(1) + + qemu_img = sys.argv[1] + + envs = [ + { + 'id': 'no-prealloc', + 'qemu-img-binary': qemu_img, + 'prealloc': False + }, + { + 'id': 'prealloc', + 'qemu-img-binary': qemu_img, + 'prealloc': True + } + ] + + aligned_cases = [] + unaligned_cases = [] + + for disk in sys.argv[2:]: + name, path = disk.split(':') + aligned_cases.append({ + 'id': f'{name}, aligned sequential 16k', + 'block-size': '16k', + 'dir': path + }) + unaligned_cases.append({ + 'id': f'{name}, unaligned sequential 64k', + 'block-size': '16k', + 'dir': path + }) + + result = simplebench.bench(auto_count_bench_func, envs, + aligned_cases + unaligned_cases, count=5) + print(results_to_text(result)) + with open('results.json', 'w') as f: + json.dump(result, f, indent=4) -- cgit v1.2.3-55-g7522