5 # Copyright (c) 2019 Virtuozzo International GmbH.
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.
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.
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/>.
28 sys
.path
.append(os
.path
.join(os
.path
.dirname(__file__
), '..', '..', 'python'))
29 from qemu
.machine
import QEMUMachine
30 from qemu
.qmp
import QMPConnectError
33 def bench_block_job(cmd
, cmd_args
, qemu_args
):
34 """Benchmark block-job
36 cmd -- qmp command to run block-job (like blockdev-backup)
37 cmd_args -- dict of qmp command arguments
38 qemu_args -- list of Qemu command line arguments, including path to Qemu
41 Returns {'seconds': int} on success and {'error': str} on failure, dict may
42 contain addional 'vm-log' field. Return value is compatible with
46 vm
= QEMUMachine(qemu_args
[0], args
=qemu_args
[1:])
51 return {'error': 'popen failed: ' + str(e
)}
52 except (QMPConnectError
, socket
.timeout
):
53 return {'error': 'qemu failed: ' + str(vm
.get_log())}
56 res
= vm
.qmp(cmd
, **cmd_args
)
57 if res
!= {'return': {}}:
59 return {'error': '"{}" command failed: {}'.format(cmd
, str(res
))}
61 e
= vm
.event_wait('JOB_STATUS_CHANGE')
62 assert e
['data']['status'] == 'created'
63 start_ms
= e
['timestamp']['seconds'] * 1000000 + \
64 e
['timestamp']['microseconds']
66 e
= vm
.events_wait((('BLOCK_JOB_READY', None),
67 ('BLOCK_JOB_COMPLETED', None),
68 ('BLOCK_JOB_FAILED', None)), timeout
=True)
69 if e
['event'] not in ('BLOCK_JOB_READY', 'BLOCK_JOB_COMPLETED'):
71 return {'error': 'block-job failed: ' + str(e
),
72 'vm-log': vm
.get_log()}
73 if 'error' in e
['data']:
75 return {'error': 'block-job failed: ' + e
['data']['error'],
76 'vm-log': vm
.get_log()}
77 end_ms
= e
['timestamp']['seconds'] * 1000000 + \
78 e
['timestamp']['microseconds']
82 return {'seconds': (end_ms
- start_ms
) / 1000000.0}
85 def get_image_size(path
):
86 out
= subprocess
.run(['qemu-img', 'info', '--out=json', path
],
87 stdout
=subprocess
.PIPE
, check
=True).stdout
88 return json
.loads(out
)['virtual-size']
91 def get_blockdev_size(obj
):
92 img
= obj
['filename'] if 'filename' in obj
else obj
['file']['filename']
93 return get_image_size(img
)
96 # Bench backup or mirror
97 def bench_block_copy(qemu_binary
, cmd
, cmd_options
, source
, target
):
98 """Helper to run bench_block_job() for mirror or backup"""
99 assert cmd
in ('blockdev-backup', 'blockdev-mirror')
101 if target
['driver'] == 'qcow2':
103 os
.remove(target
['file']['filename'])
107 subprocess
.run(['qemu-img', 'create', '-f', 'qcow2',
108 target
['file']['filename'],
109 str(get_blockdev_size(source
))],
110 stdout
=subprocess
.DEVNULL
,
111 stderr
=subprocess
.DEVNULL
, check
=True)
113 source
['node-name'] = 'source'
114 target
['node-name'] = 'target'
116 cmd_options
['job-id'] = 'job0'
117 cmd_options
['device'] = 'source'
118 cmd_options
['target'] = 'target'
119 cmd_options
['sync'] = 'full'
121 return bench_block_job(cmd
, cmd_options
,
123 '-blockdev', json
.dumps(source
),
124 '-blockdev', json
.dumps(target
)])
127 def drv_file(filename
, o_direct
=True):
128 node
= {'driver': 'file', 'filename': filename
}
130 node
['cache'] = {'direct': True}
131 node
['aio'] = 'native'
136 def drv_nbd(host
, port
):
137 return {'driver': 'nbd',
138 'server': {'type': 'inet', 'host': host
, 'port': port
}}
142 return {'driver': 'qcow2', 'file': file}
145 if __name__
== '__main__':
148 if len(sys
.argv
) < 4:
149 print('USAGE: {} <qmp block-job command name> '
150 '<json string of arguments for the command> '
151 '<qemu binary path and arguments>'.format(sys
.argv
[0]))
154 res
= bench_block_job(sys
.argv
[1], json
.loads(sys
.argv
[2]), sys
.argv
[3:])
156 print('{:.2f}'.format(res
['seconds']))