1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
|
#!/usr/bin/env python3
#
# Benchmark block jobs
#
# 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 <http://www.gnu.org/licenses/>.
#
import sys
import os
import subprocess
import socket
import json
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu.machine import QEMUMachine
from qemu.qmp import ConnectError
def bench_block_job(cmd, cmd_args, qemu_args):
"""Benchmark block-job
cmd -- qmp command to run block-job (like blockdev-backup)
cmd_args -- dict of qmp command arguments
qemu_args -- list of Qemu command line arguments, including path to Qemu
binary
Returns {'seconds': int} on success and {'error': str} on failure, dict may
contain additional 'vm-log' field. Return value is compatible with
simplebench lib.
"""
vm = QEMUMachine(qemu_args[0], args=qemu_args[1:])
try:
vm.launch()
except OSError as e:
return {'error': 'popen failed: ' + str(e)}
except (ConnectError, socket.timeout):
return {'error': 'qemu failed: ' + str(vm.get_log())}
try:
res = vm.qmp(cmd, **cmd_args)
if res != {'return': {}}:
vm.shutdown()
return {'error': '"{}" command failed: {}'.format(cmd, str(res))}
e = vm.event_wait('JOB_STATUS_CHANGE')
assert e['data']['status'] == 'created'
start_ms = e['timestamp']['seconds'] * 1000000 + \
e['timestamp']['microseconds']
e = vm.events_wait((('BLOCK_JOB_READY', None),
('BLOCK_JOB_COMPLETED', None),
('BLOCK_JOB_FAILED', None)), timeout=True)
if e['event'] not in ('BLOCK_JOB_READY', 'BLOCK_JOB_COMPLETED'):
vm.shutdown()
return {'error': 'block-job failed: ' + str(e),
'vm-log': vm.get_log()}
if 'error' in e['data']:
vm.shutdown()
return {'error': 'block-job failed: ' + e['data']['error'],
'vm-log': vm.get_log()}
end_ms = e['timestamp']['seconds'] * 1000000 + \
e['timestamp']['microseconds']
finally:
vm.shutdown()
return {'seconds': (end_ms - start_ms) / 1000000.0}
def get_image_size(path):
out = subprocess.run(['qemu-img', 'info', '--out=json', path],
stdout=subprocess.PIPE, check=True).stdout
return json.loads(out)['virtual-size']
def get_blockdev_size(obj):
img = obj['filename'] if 'filename' in obj else obj['file']['filename']
return get_image_size(img)
# Bench backup or mirror
def bench_block_copy(qemu_binary, cmd, cmd_options, source, target):
"""Helper to run bench_block_job() for mirror or backup"""
assert cmd in ('blockdev-backup', 'blockdev-mirror')
if target['driver'] == 'qcow2':
try:
os.remove(target['file']['filename'])
except OSError:
pass
subprocess.run(['qemu-img', 'create', '-f', 'qcow2',
target['file']['filename'],
str(get_blockdev_size(source))],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL, check=True)
source['node-name'] = 'source'
target['node-name'] = 'target'
cmd_options['job-id'] = 'job0'
cmd_options['device'] = 'source'
cmd_options['target'] = 'target'
cmd_options['sync'] = 'full'
return bench_block_job(cmd, cmd_options,
[qemu_binary,
'-blockdev', json.dumps(source),
'-blockdev', json.dumps(target)])
def drv_file(filename, o_direct=True):
node = {'driver': 'file', 'filename': filename}
if o_direct:
node['cache'] = {'direct': True}
node['aio'] = 'native'
return node
def drv_nbd(host, port):
return {'driver': 'nbd',
'server': {'type': 'inet', 'host': host, 'port': port}}
def drv_qcow2(file):
return {'driver': 'qcow2', 'file': file}
if __name__ == '__main__':
import sys
if len(sys.argv) < 4:
print('USAGE: {} <qmp block-job command name> '
'<json string of arguments for the command> '
'<qemu binary path and arguments>'.format(sys.argv[0]))
exit(1)
res = bench_block_job(sys.argv[1], json.loads(sys.argv[2]), sys.argv[3:])
if 'seconds' in res:
print('{:.2f}'.format(res['seconds']))
else:
print(res)
|