2
0

bench-backup.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. #!/usr/bin/env python3
  2. #
  3. # Bench backup block-job
  4. #
  5. # Copyright (c) 2020 Virtuozzo International GmbH.
  6. #
  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.
  11. #
  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.
  16. #
  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/>.
  19. #
  20. import argparse
  21. import json
  22. import simplebench
  23. from results_to_text import results_to_text
  24. from bench_block_job import bench_block_copy, drv_file, drv_nbd, drv_qcow2
  25. def bench_func(env, case):
  26. """ Handle one "cell" of benchmarking table. """
  27. cmd_options = env['cmd-options'] if 'cmd-options' in env else {}
  28. return bench_block_copy(env['qemu-binary'], env['cmd'],
  29. cmd_options,
  30. case['source'], case['target'])
  31. def bench(args):
  32. test_cases = []
  33. # paths with colon not supported, so we just split by ':'
  34. dirs = dict(d.split(':') for d in args.dir)
  35. nbd_drv = None
  36. if args.nbd:
  37. nbd = args.nbd.split(':')
  38. host = nbd[0]
  39. port = '10809' if len(nbd) == 1 else nbd[1]
  40. nbd_drv = drv_nbd(host, port)
  41. for t in args.test:
  42. src, dst = t.split(':')
  43. if src == 'nbd' and dst == 'nbd':
  44. raise ValueError("Can't use 'nbd' label for both src and dst")
  45. if (src == 'nbd' or dst == 'nbd') and not nbd_drv:
  46. raise ValueError("'nbd' label used but --nbd is not given")
  47. if src == 'nbd':
  48. source = nbd_drv
  49. elif args.qcow2_sources:
  50. source = drv_qcow2(drv_file(dirs[src] + '/test-source.qcow2'))
  51. else:
  52. source = drv_file(dirs[src] + '/test-source')
  53. if dst == 'nbd':
  54. test_cases.append({'id': t, 'source': source, 'target': nbd_drv})
  55. continue
  56. if args.target_cache == 'both':
  57. target_caches = ['direct', 'cached']
  58. else:
  59. target_caches = [args.target_cache]
  60. for c in target_caches:
  61. o_direct = c == 'direct'
  62. fname = dirs[dst] + '/test-target'
  63. if args.compressed:
  64. fname += '.qcow2'
  65. target = drv_file(fname, o_direct=o_direct)
  66. if args.compressed:
  67. target = drv_qcow2(target)
  68. test_id = t
  69. if args.target_cache == 'both':
  70. test_id += f'({c})'
  71. test_cases.append({'id': test_id, 'source': source,
  72. 'target': target})
  73. binaries = [] # list of (<label>, <path>, [<options>])
  74. for i, q in enumerate(args.env):
  75. name_path = q.split(':')
  76. if len(name_path) == 1:
  77. label = f'q{i}'
  78. path_opts = name_path[0].split(',')
  79. else:
  80. assert len(name_path) == 2 # paths with colon not supported
  81. label = name_path[0]
  82. path_opts = name_path[1].split(',')
  83. binaries.append((label, path_opts[0], path_opts[1:]))
  84. test_envs = []
  85. bin_paths = {}
  86. for i, q in enumerate(args.env):
  87. opts = q.split(',')
  88. label_path = opts[0]
  89. opts = opts[1:]
  90. if ':' in label_path:
  91. # path with colon inside is not supported
  92. label, path = label_path.split(':')
  93. bin_paths[label] = path
  94. elif label_path in bin_paths:
  95. label = label_path
  96. path = bin_paths[label]
  97. else:
  98. path = label_path
  99. label = f'q{i}'
  100. bin_paths[label] = path
  101. x_perf = {}
  102. is_mirror = False
  103. for opt in opts:
  104. if opt == 'mirror':
  105. is_mirror = True
  106. elif opt == 'copy-range=on':
  107. x_perf['use-copy-range'] = True
  108. elif opt == 'copy-range=off':
  109. x_perf['use-copy-range'] = False
  110. elif opt.startswith('max-workers='):
  111. x_perf['max-workers'] = int(opt.split('=')[1])
  112. backup_options = {}
  113. if x_perf:
  114. backup_options['x-perf'] = x_perf
  115. if args.compressed:
  116. backup_options['compress'] = True
  117. if is_mirror:
  118. assert not x_perf
  119. test_envs.append({
  120. 'id': f'mirror({label})',
  121. 'cmd': 'blockdev-mirror',
  122. 'qemu-binary': path
  123. })
  124. else:
  125. test_envs.append({
  126. 'id': f'backup({label})\n' + '\n'.join(opts),
  127. 'cmd': 'blockdev-backup',
  128. 'cmd-options': backup_options,
  129. 'qemu-binary': path
  130. })
  131. result = simplebench.bench(bench_func, test_envs, test_cases,
  132. count=args.count, initial_run=args.initial_run,
  133. drop_caches=args.drop_caches)
  134. with open('results.json', 'w') as f:
  135. json.dump(result, f, indent=4)
  136. print(results_to_text(result))
  137. class ExtendAction(argparse.Action):
  138. def __call__(self, parser, namespace, values, option_string=None):
  139. items = getattr(namespace, self.dest) or []
  140. items.extend(values)
  141. setattr(namespace, self.dest, items)
  142. if __name__ == '__main__':
  143. p = argparse.ArgumentParser('Backup benchmark', epilog='''
  144. ENV format
  145. (LABEL:PATH|LABEL|PATH)[,max-workers=N][,use-copy-range=(on|off)][,mirror]
  146. LABEL short name for the binary
  147. PATH path to the binary
  148. max-workers set x-perf.max-workers of backup job
  149. use-copy-range set x-perf.use-copy-range of backup job
  150. mirror use mirror job instead of backup''',
  151. formatter_class=argparse.RawTextHelpFormatter)
  152. p.add_argument('--env', nargs='+', help='''\
  153. Qemu binaries with labels and options, see below
  154. "ENV format" section''',
  155. action=ExtendAction)
  156. p.add_argument('--dir', nargs='+', help='''\
  157. Directories, each containing "test-source" and/or
  158. "test-target" files, raw images to used in
  159. benchmarking. File path with label, like
  160. label:/path/to/directory''',
  161. action=ExtendAction)
  162. p.add_argument('--nbd', help='''\
  163. host:port for remote NBD image, (or just host, for
  164. default port 10809). Use it in tests, label is "nbd"
  165. (but you cannot create test nbd:nbd).''')
  166. p.add_argument('--test', nargs='+', help='''\
  167. Tests, in form source-dir-label:target-dir-label''',
  168. action=ExtendAction)
  169. p.add_argument('--compressed', help='''\
  170. Use compressed backup. It automatically means
  171. automatically creating qcow2 target with
  172. lazy_refcounts for each test run''', action='store_true')
  173. p.add_argument('--qcow2-sources', help='''\
  174. Use test-source.qcow2 images as sources instead of
  175. test-source raw images''', action='store_true')
  176. p.add_argument('--target-cache', help='''\
  177. Setup cache for target nodes. Options:
  178. direct: default, use O_DIRECT and aio=native
  179. cached: use system cache (Qemu default) and aio=threads (Qemu default)
  180. both: generate two test cases for each src:dst pair''',
  181. default='direct', choices=('direct', 'cached', 'both'))
  182. p.add_argument('--count', type=int, default=3, help='''\
  183. Number of test runs per table cell''')
  184. # BooleanOptionalAction helps to support --no-initial-run option
  185. p.add_argument('--initial-run', action=argparse.BooleanOptionalAction,
  186. help='''\
  187. Do additional initial run per cell which doesn't count in result,
  188. default true''')
  189. p.add_argument('--drop-caches', action='store_true', help='''\
  190. Do "sync; echo 3 > /proc/sys/vm/drop_caches" before each test run''')
  191. bench(p.parse_args())