bench_write_req.py 5.5 KB

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