37094b6dd5
Now that we are fully switched over to the new QMP library, move it back over the old namespace. This is being done primarily so that we may upload this package simply as "qemu.qmp" without introducing confusion over whether or not "aqmp" is a new protocol or not. The trade-off is increased confusion inside the QEMU developer tree. Sorry! Note: the 'private' member "_aqmp" in legacy.py also changes to "_qmp"; not out of necessity, but just to remove any traces of the "aqmp" name. Signed-off-by: John Snow <jsnow@redhat.com> Reviewed-by: Beraldo Leal <bleal@redhat.com> Acked-by: Hanna Reitz <hreitz@redhat.com> Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@openvz.org> Message-id: 20220330172812.3427355-8-jsnow@redhat.com Signed-off-by: John Snow <jsnow@redhat.com>
159 lines
5.0 KiB
Python
Executable File
159 lines
5.0 KiB
Python
Executable File
#!/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 addional '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)
|