aboutsummaryrefslogtreecommitdiff
path: root/scripts/simplebench/bench_write_req.py
blob: da601ea2fe508615fb63e07210032f6f4b4a4344 (plain)
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
159
160
161
162
163
164
165
166
167
168
169
170
171
#!/usr/bin/env python3
#
# Test to compare performance of write requests for two qemu-img binary files.
#
# The idea of the test comes from intention to check the benefit of c8bb23cbdbe
# "qcow2: skip writing zero buffers to empty COW areas".
#
# Copyright (c) 2020 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 simplebench
from results_to_text import results_to_text


def bench_func(env, case):
    """ Handle one "cell" of benchmarking table. """
    return bench_write_req(env['qemu_img'], env['image_name'],
                           case['block_size'], case['block_offset'],
                           case['cluster_size'])


def qemu_img_pipe(*args):
    '''Run qemu-img and return its output'''
    subp = subprocess.Popen(list(args),
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            universal_newlines=True)
    exitcode = subp.wait()
    if exitcode < 0:
        sys.stderr.write('qemu-img received signal %i: %s\n'
                         % (-exitcode, ' '.join(list(args))))
    return subp.communicate()[0]


def bench_write_req(qemu_img, image_name, block_size, block_offset,
                    cluster_size):
    """Benchmark write requests

    The function creates a QCOW2 image with the given path/name. Then it runs
    the 'qemu-img bench' command and makes series of write requests on the
    image clusters. Finally, it returns the total time of the write operations
    on the disk.

    qemu_img     -- path to qemu_img executable file
    image_name   -- QCOW2 image name to create
    block_size   -- size of a block to write to clusters
    block_offset -- offset of the block in clusters
    cluster_size -- size of the image cluster

    Returns {'seconds': int} on success and {'error': str} on failure.
    Return value is compatible with simplebench lib.
    """

    if not os.path.isfile(qemu_img):
        print(f'File not found: {qemu_img}')
        sys.exit(1)

    image_dir = os.path.dirname(os.path.abspath(image_name))
    if not os.path.isdir(image_dir):
        print(f'Path not found: {image_name}')
        sys.exit(1)

    image_size = 1024 * 1024 * 1024

    args_create = [qemu_img, 'create', '-f', 'qcow2', '-o',
                   f'cluster_size={cluster_size}',
                   image_name, str(image_size)]

    count = int(image_size / cluster_size) - 1
    step = str(cluster_size)

    args_bench = [qemu_img, 'bench', '-w', '-n', '-t', 'none', '-c',
                  str(count), '-s', f'{block_size}', '-o', str(block_offset),
                  '-S', step, '-f', 'qcow2', image_name]

    try:
        qemu_img_pipe(*args_create)
    except OSError as e:
        os.remove(image_name)
        return {'error': 'qemu_img create failed: ' + str(e)}

    try:
        ret = qemu_img_pipe(*args_bench)
    except OSError as e:
        os.remove(image_name)
        return {'error': 'qemu_img bench failed: ' + str(e)}

    os.remove(image_name)

    if 'seconds' in ret:
        ret_list = ret.split()
        index = ret_list.index('seconds.')
        return {'seconds': float(ret_list[index-1])}
    else:
        return {'error': 'qemu_img bench failed: ' + ret}


if __name__ == '__main__':

    if len(sys.argv) < 4:
        program = os.path.basename(sys.argv[0])
        print(f'USAGE: {program} <path to qemu-img binary file> '
              '<path to another qemu-img to compare performance with> '
              '<full or relative name for QCOW2 image to create>')
        exit(1)

    # Test-cases are "rows" in benchmark resulting table, 'id' is a caption
    # for the row, other fields are handled by bench_func.
    test_cases = [
        {
            'id': '<cluster front>',
            'block_size': 4096,
            'block_offset': 0,
            'cluster_size': 1048576
        },
        {
            'id': '<cluster middle>',
            'block_size': 4096,
            'block_offset': 524288,
            'cluster_size': 1048576
        },
        {
            'id': '<cross cluster>',
            'block_size': 1048576,
            'block_offset': 4096,
            'cluster_size': 1048576
        },
        {
            'id': '<cluster 64K>',
            'block_size': 4096,
            'block_offset': 0,
            'cluster_size': 65536
        },
    ]

    # Test-envs are "columns" in benchmark resulting table, 'id is a caption
    # for the column, other fields are handled by bench_func.
    # Set the paths below to desired values
    test_envs = [
        {
            'id': '<qemu-img binary 1>',
            'qemu_img': f'{sys.argv[1]}',
            'image_name': f'{sys.argv[3]}'
        },
        {
            'id': '<qemu-img binary 2>',
            'qemu_img': f'{sys.argv[2]}',
            'image_name': f'{sys.argv[3]}'
        },
    ]

    result = simplebench.bench(bench_func, test_envs, test_cases, count=3,
                               initial_run=False)
    print(results_to_text(result))