1#!/usr/bin/env python3 2# 3# Test to compare performance of write requests for two qemu-img binary files. 4# 5# The idea of the test comes from intention to check the benefit of c8bb23cbdbe 6# "qcow2: skip writing zero buffers to empty COW areas". 7# 8# Copyright (c) 2020 Virtuozzo International GmbH. 9# 10# This program is free software; you can redistribute it and/or modify 11# it under the terms of the GNU General Public License as published by 12# the Free Software Foundation; either version 2 of the License, or 13# (at your option) any later version. 14# 15# This program is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU General Public License for more details. 19# 20# You should have received a copy of the GNU General Public License 21# along with this program. If not, see <http://www.gnu.org/licenses/>. 22# 23 24 25import sys 26import os 27import subprocess 28import simplebench 29from results_to_text import results_to_text 30 31 32def bench_func(env, case): 33 """ Handle one "cell" of benchmarking table. """ 34 return bench_write_req(env['qemu_img'], env['image_name'], 35 case['block_size'], case['block_offset'], 36 case['cluster_size']) 37 38 39def qemu_img_pipe(*args): 40 '''Run qemu-img and return its output''' 41 subp = subprocess.Popen(list(args), 42 stdout=subprocess.PIPE, 43 stderr=subprocess.STDOUT, 44 universal_newlines=True) 45 exitcode = subp.wait() 46 if exitcode < 0: 47 sys.stderr.write('qemu-img received signal %i: %s\n' 48 % (-exitcode, ' '.join(list(args)))) 49 return subp.communicate()[0] 50 51 52def bench_write_req(qemu_img, image_name, block_size, block_offset, 53 cluster_size): 54 """Benchmark write requests 55 56 The function creates a QCOW2 image with the given path/name. Then it runs 57 the 'qemu-img bench' command and makes series of write requests on the 58 image clusters. Finally, it returns the total time of the write operations 59 on the disk. 60 61 qemu_img -- path to qemu_img executable file 62 image_name -- QCOW2 image name to create 63 block_size -- size of a block to write to clusters 64 block_offset -- offset of the block in clusters 65 cluster_size -- size of the image cluster 66 67 Returns {'seconds': int} on success and {'error': str} on failure. 68 Return value is compatible with simplebench lib. 69 """ 70 71 if not os.path.isfile(qemu_img): 72 print(f'File not found: {qemu_img}') 73 sys.exit(1) 74 75 image_dir = os.path.dirname(os.path.abspath(image_name)) 76 if not os.path.isdir(image_dir): 77 print(f'Path not found: {image_name}') 78 sys.exit(1) 79 80 image_size = 1024 * 1024 * 1024 81 82 args_create = [qemu_img, 'create', '-f', 'qcow2', '-o', 83 f'cluster_size={cluster_size}', 84 image_name, str(image_size)] 85 86 count = int(image_size / cluster_size) - 1 87 step = str(cluster_size) 88 89 args_bench = [qemu_img, 'bench', '-w', '-n', '-t', 'none', '-c', 90 str(count), '-s', f'{block_size}', '-o', str(block_offset), 91 '-S', step, '-f', 'qcow2', image_name] 92 93 try: 94 qemu_img_pipe(*args_create) 95 except OSError as e: 96 os.remove(image_name) 97 return {'error': 'qemu_img create failed: ' + str(e)} 98 99 try: 100 ret = qemu_img_pipe(*args_bench) 101 except OSError as e: 102 os.remove(image_name) 103 return {'error': 'qemu_img bench failed: ' + str(e)} 104 105 os.remove(image_name) 106 107 if 'seconds' in ret: 108 ret_list = ret.split() 109 index = ret_list.index('seconds.') 110 return {'seconds': float(ret_list[index-1])} 111 else: 112 return {'error': 'qemu_img bench failed: ' + ret} 113 114 115if __name__ == '__main__': 116 117 if len(sys.argv) < 4: 118 program = os.path.basename(sys.argv[0]) 119 print(f'USAGE: {program} <path to qemu-img binary file> ' 120 '<path to another qemu-img to compare performance with> ' 121 '<full or relative name for QCOW2 image to create>') 122 exit(1) 123 124 # Test-cases are "rows" in benchmark resulting table, 'id' is a caption 125 # for the row, other fields are handled by bench_func. 126 test_cases = [ 127 { 128 'id': '<cluster front>', 129 'block_size': 4096, 130 'block_offset': 0, 131 'cluster_size': 1048576 132 }, 133 { 134 'id': '<cluster middle>', 135 'block_size': 4096, 136 'block_offset': 524288, 137 'cluster_size': 1048576 138 }, 139 { 140 'id': '<cross cluster>', 141 'block_size': 1048576, 142 'block_offset': 4096, 143 'cluster_size': 1048576 144 }, 145 { 146 'id': '<cluster 64K>', 147 'block_size': 4096, 148 'block_offset': 0, 149 'cluster_size': 65536 150 }, 151 ] 152 153 # Test-envs are "columns" in benchmark resulting table, 'id is a caption 154 # for the column, other fields are handled by bench_func. 155 # Set the paths below to desired values 156 test_envs = [ 157 { 158 'id': '<qemu-img binary 1>', 159 'qemu_img': f'{sys.argv[1]}', 160 'image_name': f'{sys.argv[3]}' 161 }, 162 { 163 'id': '<qemu-img binary 2>', 164 'qemu_img': f'{sys.argv[2]}', 165 'image_name': f'{sys.argv[3]}' 166 }, 167 ] 168 169 result = simplebench.bench(bench_func, test_envs, test_cases, count=3, 170 initial_run=False) 171 print(results_to_text(result)) 172