1#!/usr/bin/env python3 2# 3# Benchmark block jobs 4# 5# Copyright (c) 2019 Virtuozzo International GmbH. 6# 7# This program is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation; either version 2 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program. If not, see <http://www.gnu.org/licenses/>. 19# 20 21 22import sys 23import os 24import socket 25import json 26 27sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) 28from qemu.machine import QEMUMachine 29from qemu.qmp import QMPConnectError 30 31 32def bench_block_job(cmd, cmd_args, qemu_args): 33 """Benchmark block-job 34 35 cmd -- qmp command to run block-job (like blockdev-backup) 36 cmd_args -- dict of qmp command arguments 37 qemu_args -- list of Qemu command line arguments, including path to Qemu 38 binary 39 40 Returns {'seconds': int} on success and {'error': str} on failure, dict may 41 contain addional 'vm-log' field. Return value is compatible with 42 simplebench lib. 43 """ 44 45 vm = QEMUMachine(qemu_args[0], args=qemu_args[1:]) 46 47 try: 48 vm.launch() 49 except OSError as e: 50 return {'error': 'popen failed: ' + str(e)} 51 except (QMPConnectError, socket.timeout): 52 return {'error': 'qemu failed: ' + str(vm.get_log())} 53 54 try: 55 res = vm.qmp(cmd, **cmd_args) 56 if res != {'return': {}}: 57 vm.shutdown() 58 return {'error': '"{}" command failed: {}'.format(cmd, str(res))} 59 60 e = vm.event_wait('JOB_STATUS_CHANGE') 61 assert e['data']['status'] == 'created' 62 start_ms = e['timestamp']['seconds'] * 1000000 + \ 63 e['timestamp']['microseconds'] 64 65 e = vm.events_wait((('BLOCK_JOB_READY', None), 66 ('BLOCK_JOB_COMPLETED', None), 67 ('BLOCK_JOB_FAILED', None)), timeout=True) 68 if e['event'] not in ('BLOCK_JOB_READY', 'BLOCK_JOB_COMPLETED'): 69 vm.shutdown() 70 return {'error': 'block-job failed: ' + str(e), 71 'vm-log': vm.get_log()} 72 end_ms = e['timestamp']['seconds'] * 1000000 + \ 73 e['timestamp']['microseconds'] 74 finally: 75 vm.shutdown() 76 77 return {'seconds': (end_ms - start_ms) / 1000000.0} 78 79 80# Bench backup or mirror 81def bench_block_copy(qemu_binary, cmd, cmd_options, source, target): 82 """Helper to run bench_block_job() for mirror or backup""" 83 assert cmd in ('blockdev-backup', 'blockdev-mirror') 84 85 source['node-name'] = 'source' 86 target['node-name'] = 'target' 87 88 cmd_options['job-id'] = 'job0' 89 cmd_options['device'] = 'source' 90 cmd_options['target'] = 'target' 91 cmd_options['sync'] = 'full' 92 93 return bench_block_job(cmd, cmd_options, 94 [qemu_binary, 95 '-blockdev', json.dumps(source), 96 '-blockdev', json.dumps(target)]) 97 98 99def drv_file(filename): 100 return {'driver': 'file', 'filename': filename, 101 'cache': {'direct': True}, 'aio': 'native'} 102 103 104def drv_nbd(host, port): 105 return {'driver': 'nbd', 106 'server': {'type': 'inet', 'host': host, 'port': port}} 107 108 109if __name__ == '__main__': 110 import sys 111 112 if len(sys.argv) < 4: 113 print('USAGE: {} <qmp block-job command name> ' 114 '<json string of arguments for the command> ' 115 '<qemu binary path and arguments>'.format(sys.argv[0])) 116 exit(1) 117 118 res = bench_block_job(sys.argv[1], json.loads(sys.argv[2]), sys.argv[3:]) 119 if 'seconds' in res: 120 print('{:.2f}'.format(res['seconds'])) 121 else: 122 print(res) 123