xref: /qemu/scripts/simplebench/bench_write_req.py (revision 1b7306f5dd7a80f4797c1644d539409031663782)
1*1b7306f5SAndrey Shinkevich#!/usr/bin/env python3
2*1b7306f5SAndrey Shinkevich#
3*1b7306f5SAndrey Shinkevich# Test to compare performance of write requests for two qemu-img binary files.
4*1b7306f5SAndrey Shinkevich#
5*1b7306f5SAndrey Shinkevich# The idea of the test comes from intention to check the benefit of c8bb23cbdbe
6*1b7306f5SAndrey Shinkevich# "qcow2: skip writing zero buffers to empty COW areas".
7*1b7306f5SAndrey Shinkevich#
8*1b7306f5SAndrey Shinkevich# Copyright (c) 2020 Virtuozzo International GmbH.
9*1b7306f5SAndrey Shinkevich#
10*1b7306f5SAndrey Shinkevich# This program is free software; you can redistribute it and/or modify
11*1b7306f5SAndrey Shinkevich# it under the terms of the GNU General Public License as published by
12*1b7306f5SAndrey Shinkevich# the Free Software Foundation; either version 2 of the License, or
13*1b7306f5SAndrey Shinkevich# (at your option) any later version.
14*1b7306f5SAndrey Shinkevich#
15*1b7306f5SAndrey Shinkevich# This program is distributed in the hope that it will be useful,
16*1b7306f5SAndrey Shinkevich# but WITHOUT ANY WARRANTY; without even the implied warranty of
17*1b7306f5SAndrey Shinkevich# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18*1b7306f5SAndrey Shinkevich# GNU General Public License for more details.
19*1b7306f5SAndrey Shinkevich#
20*1b7306f5SAndrey Shinkevich# You should have received a copy of the GNU General Public License
21*1b7306f5SAndrey Shinkevich# along with this program.  If not, see <http://www.gnu.org/licenses/>.
22*1b7306f5SAndrey Shinkevich#
23*1b7306f5SAndrey Shinkevich
24*1b7306f5SAndrey Shinkevich
25*1b7306f5SAndrey Shinkevichimport sys
26*1b7306f5SAndrey Shinkevichimport os
27*1b7306f5SAndrey Shinkevichimport subprocess
28*1b7306f5SAndrey Shinkevichimport simplebench
29*1b7306f5SAndrey Shinkevich
30*1b7306f5SAndrey Shinkevich
31*1b7306f5SAndrey Shinkevichdef bench_func(env, case):
32*1b7306f5SAndrey Shinkevich    """ Handle one "cell" of benchmarking table. """
33*1b7306f5SAndrey Shinkevich    return bench_write_req(env['qemu_img'], env['image_name'],
34*1b7306f5SAndrey Shinkevich                           case['block_size'], case['block_offset'],
35*1b7306f5SAndrey Shinkevich                           case['cluster_size'])
36*1b7306f5SAndrey Shinkevich
37*1b7306f5SAndrey Shinkevich
38*1b7306f5SAndrey Shinkevichdef qemu_img_pipe(*args):
39*1b7306f5SAndrey Shinkevich    '''Run qemu-img and return its output'''
40*1b7306f5SAndrey Shinkevich    subp = subprocess.Popen(list(args),
41*1b7306f5SAndrey Shinkevich                            stdout=subprocess.PIPE,
42*1b7306f5SAndrey Shinkevich                            stderr=subprocess.STDOUT,
43*1b7306f5SAndrey Shinkevich                            universal_newlines=True)
44*1b7306f5SAndrey Shinkevich    exitcode = subp.wait()
45*1b7306f5SAndrey Shinkevich    if exitcode < 0:
46*1b7306f5SAndrey Shinkevich        sys.stderr.write('qemu-img received signal %i: %s\n'
47*1b7306f5SAndrey Shinkevich                         % (-exitcode, ' '.join(list(args))))
48*1b7306f5SAndrey Shinkevich    return subp.communicate()[0]
49*1b7306f5SAndrey Shinkevich
50*1b7306f5SAndrey Shinkevich
51*1b7306f5SAndrey Shinkevichdef bench_write_req(qemu_img, image_name, block_size, block_offset,
52*1b7306f5SAndrey Shinkevich                    cluster_size):
53*1b7306f5SAndrey Shinkevich    """Benchmark write requests
54*1b7306f5SAndrey Shinkevich
55*1b7306f5SAndrey Shinkevich    The function creates a QCOW2 image with the given path/name. Then it runs
56*1b7306f5SAndrey Shinkevich    the 'qemu-img bench' command and makes series of write requests on the
57*1b7306f5SAndrey Shinkevich    image clusters. Finally, it returns the total time of the write operations
58*1b7306f5SAndrey Shinkevich    on the disk.
59*1b7306f5SAndrey Shinkevich
60*1b7306f5SAndrey Shinkevich    qemu_img     -- path to qemu_img executable file
61*1b7306f5SAndrey Shinkevich    image_name   -- QCOW2 image name to create
62*1b7306f5SAndrey Shinkevich    block_size   -- size of a block to write to clusters
63*1b7306f5SAndrey Shinkevich    block_offset -- offset of the block in clusters
64*1b7306f5SAndrey Shinkevich    cluster_size -- size of the image cluster
65*1b7306f5SAndrey Shinkevich
66*1b7306f5SAndrey Shinkevich    Returns {'seconds': int} on success and {'error': str} on failure.
67*1b7306f5SAndrey Shinkevich    Return value is compatible with simplebench lib.
68*1b7306f5SAndrey Shinkevich    """
69*1b7306f5SAndrey Shinkevich
70*1b7306f5SAndrey Shinkevich    if not os.path.isfile(qemu_img):
71*1b7306f5SAndrey Shinkevich        print(f'File not found: {qemu_img}')
72*1b7306f5SAndrey Shinkevich        sys.exit(1)
73*1b7306f5SAndrey Shinkevich
74*1b7306f5SAndrey Shinkevich    image_dir = os.path.dirname(os.path.abspath(image_name))
75*1b7306f5SAndrey Shinkevich    if not os.path.isdir(image_dir):
76*1b7306f5SAndrey Shinkevich        print(f'Path not found: {image_name}')
77*1b7306f5SAndrey Shinkevich        sys.exit(1)
78*1b7306f5SAndrey Shinkevich
79*1b7306f5SAndrey Shinkevich    image_size = 1024 * 1024 * 1024
80*1b7306f5SAndrey Shinkevich
81*1b7306f5SAndrey Shinkevich    args_create = [qemu_img, 'create', '-f', 'qcow2', '-o',
82*1b7306f5SAndrey Shinkevich                   f'cluster_size={cluster_size}',
83*1b7306f5SAndrey Shinkevich                   image_name, str(image_size)]
84*1b7306f5SAndrey Shinkevich
85*1b7306f5SAndrey Shinkevich    count = int(image_size / cluster_size) - 1
86*1b7306f5SAndrey Shinkevich    step = str(cluster_size)
87*1b7306f5SAndrey Shinkevich
88*1b7306f5SAndrey Shinkevich    args_bench = [qemu_img, 'bench', '-w', '-n', '-t', 'none', '-c',
89*1b7306f5SAndrey Shinkevich                  str(count), '-s', f'{block_size}', '-o', str(block_offset),
90*1b7306f5SAndrey Shinkevich                  '-S', step, '-f', 'qcow2', image_name]
91*1b7306f5SAndrey Shinkevich
92*1b7306f5SAndrey Shinkevich    try:
93*1b7306f5SAndrey Shinkevich        qemu_img_pipe(*args_create)
94*1b7306f5SAndrey Shinkevich    except OSError as e:
95*1b7306f5SAndrey Shinkevich        os.remove(image_name)
96*1b7306f5SAndrey Shinkevich        return {'error': 'qemu_img create failed: ' + str(e)}
97*1b7306f5SAndrey Shinkevich
98*1b7306f5SAndrey Shinkevich    try:
99*1b7306f5SAndrey Shinkevich        ret = qemu_img_pipe(*args_bench)
100*1b7306f5SAndrey Shinkevich    except OSError as e:
101*1b7306f5SAndrey Shinkevich        os.remove(image_name)
102*1b7306f5SAndrey Shinkevich        return {'error': 'qemu_img bench failed: ' + str(e)}
103*1b7306f5SAndrey Shinkevich
104*1b7306f5SAndrey Shinkevich    os.remove(image_name)
105*1b7306f5SAndrey Shinkevich
106*1b7306f5SAndrey Shinkevich    if 'seconds' in ret:
107*1b7306f5SAndrey Shinkevich        ret_list = ret.split()
108*1b7306f5SAndrey Shinkevich        index = ret_list.index('seconds.')
109*1b7306f5SAndrey Shinkevich        return {'seconds': float(ret_list[index-1])}
110*1b7306f5SAndrey Shinkevich    else:
111*1b7306f5SAndrey Shinkevich        return {'error': 'qemu_img bench failed: ' + ret}
112*1b7306f5SAndrey Shinkevich
113*1b7306f5SAndrey Shinkevich
114*1b7306f5SAndrey Shinkevichif __name__ == '__main__':
115*1b7306f5SAndrey Shinkevich
116*1b7306f5SAndrey Shinkevich    if len(sys.argv) < 4:
117*1b7306f5SAndrey Shinkevich        program = os.path.basename(sys.argv[0])
118*1b7306f5SAndrey Shinkevich        print(f'USAGE: {program} <path to qemu-img binary file> '
119*1b7306f5SAndrey Shinkevich              '<path to another qemu-img to compare performance with> '
120*1b7306f5SAndrey Shinkevich              '<full or relative name for QCOW2 image to create>')
121*1b7306f5SAndrey Shinkevich        exit(1)
122*1b7306f5SAndrey Shinkevich
123*1b7306f5SAndrey Shinkevich    # Test-cases are "rows" in benchmark resulting table, 'id' is a caption
124*1b7306f5SAndrey Shinkevich    # for the row, other fields are handled by bench_func.
125*1b7306f5SAndrey Shinkevich    test_cases = [
126*1b7306f5SAndrey Shinkevich        {
127*1b7306f5SAndrey Shinkevich            'id': '<cluster front>',
128*1b7306f5SAndrey Shinkevich            'block_size': 4096,
129*1b7306f5SAndrey Shinkevich            'block_offset': 0,
130*1b7306f5SAndrey Shinkevich            'cluster_size': 1048576
131*1b7306f5SAndrey Shinkevich        },
132*1b7306f5SAndrey Shinkevich        {
133*1b7306f5SAndrey Shinkevich            'id': '<cluster middle>',
134*1b7306f5SAndrey Shinkevich            'block_size': 4096,
135*1b7306f5SAndrey Shinkevich            'block_offset': 524288,
136*1b7306f5SAndrey Shinkevich            'cluster_size': 1048576
137*1b7306f5SAndrey Shinkevich        },
138*1b7306f5SAndrey Shinkevich        {
139*1b7306f5SAndrey Shinkevich            'id': '<cross cluster>',
140*1b7306f5SAndrey Shinkevich            'block_size': 1048576,
141*1b7306f5SAndrey Shinkevich            'block_offset': 4096,
142*1b7306f5SAndrey Shinkevich            'cluster_size': 1048576
143*1b7306f5SAndrey Shinkevich        },
144*1b7306f5SAndrey Shinkevich        {
145*1b7306f5SAndrey Shinkevich            'id': '<cluster 64K>',
146*1b7306f5SAndrey Shinkevich            'block_size': 4096,
147*1b7306f5SAndrey Shinkevich            'block_offset': 0,
148*1b7306f5SAndrey Shinkevich            'cluster_size': 65536
149*1b7306f5SAndrey Shinkevich        },
150*1b7306f5SAndrey Shinkevich    ]
151*1b7306f5SAndrey Shinkevich
152*1b7306f5SAndrey Shinkevich    # Test-envs are "columns" in benchmark resulting table, 'id is a caption
153*1b7306f5SAndrey Shinkevich    # for the column, other fields are handled by bench_func.
154*1b7306f5SAndrey Shinkevich    # Set the paths below to desired values
155*1b7306f5SAndrey Shinkevich    test_envs = [
156*1b7306f5SAndrey Shinkevich        {
157*1b7306f5SAndrey Shinkevich            'id': '<qemu-img binary 1>',
158*1b7306f5SAndrey Shinkevich            'qemu_img': f'{sys.argv[1]}',
159*1b7306f5SAndrey Shinkevich            'image_name': f'{sys.argv[3]}'
160*1b7306f5SAndrey Shinkevich        },
161*1b7306f5SAndrey Shinkevich        {
162*1b7306f5SAndrey Shinkevich            'id': '<qemu-img binary 2>',
163*1b7306f5SAndrey Shinkevich            'qemu_img': f'{sys.argv[2]}',
164*1b7306f5SAndrey Shinkevich            'image_name': f'{sys.argv[3]}'
165*1b7306f5SAndrey Shinkevich        },
166*1b7306f5SAndrey Shinkevich    ]
167*1b7306f5SAndrey Shinkevich
168*1b7306f5SAndrey Shinkevich    result = simplebench.bench(bench_func, test_envs, test_cases, count=3,
169*1b7306f5SAndrey Shinkevich                               initial_run=False)
170*1b7306f5SAndrey Shinkevich    print(simplebench.ascii(result))
171