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 29 30 31def bench_func(env, case): 32 """ Handle one "cell" of benchmarking table. """ 33 return bench_write_req(env['qemu_img'], env['image_name'], 34 case['block_size'], case['block_offset'], 35 case['cluster_size']) 36 37 38def qemu_img_pipe(*args): 39 '''Run qemu-img and return its output''' 40 subp = subprocess.Popen(list(args), 41 stdout=subprocess.PIPE, 42 stderr=subprocess.STDOUT, 43 universal_newlines=True) 44 exitcode = subp.wait() 45 if exitcode < 0: 46 sys.stderr.write('qemu-img received signal %i: %s\n' 47 % (-exitcode, ' '.join(list(args)))) 48 return subp.communicate()[0] 49 50 51def bench_write_req(qemu_img, image_name, block_size, block_offset, 52 cluster_size): 53 """Benchmark write requests 54 55 The function creates a QCOW2 image with the given path/name. Then it runs 56 the 'qemu-img bench' command and makes series of write requests on the 57 image clusters. Finally, it returns the total time of the write operations 58 on the disk. 59 60 qemu_img -- path to qemu_img executable file 61 image_name -- QCOW2 image name to create 62 block_size -- size of a block to write to clusters 63 block_offset -- offset of the block in clusters 64 cluster_size -- size of the image cluster 65 66 Returns {'seconds': int} on success and {'error': str} on failure. 67 Return value is compatible with simplebench lib. 68 """ 69 70 if not os.path.isfile(qemu_img): 71 print(f'File not found: {qemu_img}') 72 sys.exit(1) 73 74 image_dir = os.path.dirname(os.path.abspath(image_name)) 75 if not os.path.isdir(image_dir): 76 print(f'Path not found: {image_name}') 77 sys.exit(1) 78 79 image_size = 1024 * 1024 * 1024 80 81 args_create = [qemu_img, 'create', '-f', 'qcow2', '-o', 82 f'cluster_size={cluster_size}', 83 image_name, str(image_size)] 84 85 count = int(image_size / cluster_size) - 1 86 step = str(cluster_size) 87 88 args_bench = [qemu_img, 'bench', '-w', '-n', '-t', 'none', '-c', 89 str(count), '-s', f'{block_size}', '-o', str(block_offset), 90 '-S', step, '-f', 'qcow2', image_name] 91 92 try: 93 qemu_img_pipe(*args_create) 94 except OSError as e: 95 os.remove(image_name) 96 return {'error': 'qemu_img create failed: ' + str(e)} 97 98 try: 99 ret = qemu_img_pipe(*args_bench) 100 except OSError as e: 101 os.remove(image_name) 102 return {'error': 'qemu_img bench failed: ' + str(e)} 103 104 os.remove(image_name) 105 106 if 'seconds' in ret: 107 ret_list = ret.split() 108 index = ret_list.index('seconds.') 109 return {'seconds': float(ret_list[index-1])} 110 else: 111 return {'error': 'qemu_img bench failed: ' + ret} 112 113 114if __name__ == '__main__': 115 116 if len(sys.argv) < 4: 117 program = os.path.basename(sys.argv[0]) 118 print(f'USAGE: {program} <path to qemu-img binary file> ' 119 '<path to another qemu-img to compare performance with> ' 120 '<full or relative name for QCOW2 image to create>') 121 exit(1) 122 123 # Test-cases are "rows" in benchmark resulting table, 'id' is a caption 124 # for the row, other fields are handled by bench_func. 125 test_cases = [ 126 { 127 'id': '<cluster front>', 128 'block_size': 4096, 129 'block_offset': 0, 130 'cluster_size': 1048576 131 }, 132 { 133 'id': '<cluster middle>', 134 'block_size': 4096, 135 'block_offset': 524288, 136 'cluster_size': 1048576 137 }, 138 { 139 'id': '<cross cluster>', 140 'block_size': 1048576, 141 'block_offset': 4096, 142 'cluster_size': 1048576 143 }, 144 { 145 'id': '<cluster 64K>', 146 'block_size': 4096, 147 'block_offset': 0, 148 'cluster_size': 65536 149 }, 150 ] 151 152 # Test-envs are "columns" in benchmark resulting table, 'id is a caption 153 # for the column, other fields are handled by bench_func. 154 # Set the paths below to desired values 155 test_envs = [ 156 { 157 'id': '<qemu-img binary 1>', 158 'qemu_img': f'{sys.argv[1]}', 159 'image_name': f'{sys.argv[3]}' 160 }, 161 { 162 'id': '<qemu-img binary 2>', 163 'qemu_img': f'{sys.argv[2]}', 164 'image_name': f'{sys.argv[3]}' 165 }, 166 ] 167 168 result = simplebench.bench(bench_func, test_envs, test_cases, count=3, 169 initial_run=False) 170 print(simplebench.ascii(result)) 171