From 8076169579215d01230767c363ea521e09402545 Mon Sep 17 00:00:00 2001 From: Chris Harris Date: Thu, 10 Oct 2024 11:31:29 +0100 Subject: [PATCH 1/6] cbt: creating a common base class for fio subclasses Signed-off-by: Chris Harris --- benchmark/benchmark.py | 116 ++++++----- benchmark/fio.py | 358 ++++++++++++--------------------- benchmark/fio_common.py | 194 ++++++++++++++++++ cluster/cluster.py | 30 ++- tests/test_bm_cephtestrados.py | 2 +- tests/test_bm_fio.py | 200 ++++-------------- tests/test_bm_getput.py | 2 +- tests/test_bm_hsbench.py | 2 +- tests/test_bm_kvmrbdfio.py | 2 +- tests/test_bm_librbdfio.py | 2 +- tests/test_bm_nullbench.py | 2 +- tests/test_bm_radosbench.py | 2 +- tests/test_bm_rawfio.py | 2 +- tests/test_bm_rbdfio.py | 2 +- tools/baseline.json | 175 +++++++++++++--- tools/serialise_benchmark.py | 76 +++---- 16 files changed, 649 insertions(+), 518 deletions(-) create mode 100644 benchmark/fio_common.py diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py index f0cf4513..4211a08b 100644 --- a/benchmark/benchmark.py +++ b/benchmark/benchmark.py @@ -1,39 +1,39 @@ +import hashlib +import json import logging +import os from abc import ABC, abstractmethod -import hashlib -import os -import json import yaml -import settings + import common +import settings -logger = logging.getLogger('cbt') +logger = logging.getLogger("cbt") class Benchmark(object): def __init__(self, archive_dir, cluster, config): - self.acceptable = config.pop('acceptable', {}) + self.acceptable = config.pop("acceptable", {}) self.config = config self.cluster = cluster hashable = json.dumps(sorted(self.config.items())).encode() digest = hashlib.sha1(hashable).hexdigest()[:8] - self.archive_dir = os.path.join(archive_dir, - 'results', - '{:0>8}'.format(config.get('iteration')), - 'id-{}'.format(digest)) + self.archive_dir = os.path.join( + archive_dir, "results", "{:0>8}".format(config.get("iteration")), "id-{}".format(digest) + ) # This would show several dirs if run continuously - logger.info("Results dir: %s", self.archive_dir ) - self.run_dir = os.path.join(settings.cluster.get('tmp_dir'), - '{:0>8}'.format(config.get('iteration')), - self.getclass()) - self.osd_ra = config.get('osd_ra', '0') - self.cmd_path = '' - self.valgrind = config.get('valgrind', None) - self.cmd_path_full = '' - self.log_iops = config.get('log_iops', True) - self.log_bw = config.get('log_bw', True) - self.log_lat = config.get('log_lat', True) + logger.info("Results dir: %s", self.archive_dir) + self.run_dir = os.path.join( + settings.cluster.get("tmp_dir"), "{:0>8}".format(config.get("iteration")), self.getclass() + ) + self.osd_ra = config.get("osd_ra", "0") + self.cmd_path = "" + self.valgrind = config.get("valgrind", None) + self.cmd_path_full = "" + self.log_iops = config.get("log_iops", True) + self.log_bw = config.get("log_bw", True) + self.log_lat = config.get("log_lat", True) if self.valgrind is not None: self.cmd_path_full = common.setup_valgrind(self.valgrind, self.getclass(), self.run_dir) @@ -47,22 +47,25 @@ def create_data_analyzer(self, run, host, proc): pass def _compare_client_results(self, client_run, self_analyzer, baseline_analyzer): - from .lis import Lispy, Env + from .lis import Env, Lispy + # normalize the names - aliases = {'bandwidth': 'Bandwidth (MB/sec)', - 'iops_avg': 'Average IOPS', - 'iops_stddev': 'Stddev IOPS', - 'latency_avg': 'Average Latency(s)', - 'cpu_cycles_per_op': 'Cycles per operation'} + aliases = { + "bandwidth": "Bandwidth (MB/sec)", + "iops_avg": "Average IOPS", + "iops_stddev": "Stddev IOPS", + "latency_avg": "Average Latency(s)", + "cpu_cycles_per_op": "Cycles per operation", + } res_outputs = [] # list of dictionaries containing the self and baseline benchmark results compare_results = [] self_analyzer_res = {} baseline_analyzer_res = {} for alias in self.acceptable: name = aliases[alias] - self_getter = getattr(self_analyzer, 'get_' + alias) + self_getter = getattr(self_analyzer, "get_" + alias) if self_getter == None: - logger.info('CPU Cycles Per Operation metric is not configured for this benchmark') + logger.info("CPU Cycles Per Operation metric is not configured for this benchmark") continue self_analyzer_res[name] = self_getter() if self_analyzer_res[name] is None: @@ -70,12 +73,14 @@ def _compare_client_results(self, client_run, self_analyzer, baseline_analyzer): with open(paranoid_path) as f: paranoid_level = int(f.read()) if paranoid_level >= 1: - msg = ('''Perf must be run by user with CAP_SYS_ADMIN to extract''' - '''CPU related metrics. Or you could set %s to 0,''' - '''which is %d now''') - logger.warning('%s. %s is %d', msg, paranoid_path, paranoid_level) + msg = ( + """Perf must be run by user with CAP_SYS_ADMIN to extract""" + """CPU related metrics. Or you could set %s to 0,""" + """which is %d now""" + ) + logger.warning("%s. %s is %d", msg, paranoid_path, paranoid_level) continue - baseline_getter = getattr(baseline_analyzer, 'get_' + alias) + baseline_getter = getattr(baseline_analyzer, "get_" + alias) baseline_analyzer_res[name] = baseline_getter() res_outputs.append(self_analyzer_res) res_outputs.append(baseline_analyzer_res) @@ -93,19 +98,19 @@ def _compare_client_results(self, client_run, self_analyzer, baseline_analyzer): def evaluate(self, baseline): runs = [] if self.prefill_time or self.prefill_objects: - runs.append('prefill') + runs.append("prefill") if not self.read_only: - runs.append('write') + runs.append("write") if not self.write_only: runs.append(self.readmode) results = [] for run in runs: - for client in settings.getnodes('clients').split(','): + for client in settings.getnodes("clients").split(","): host = settings.host_info(client)["host"] for proc in range(self.concurrent_procs): self_analyzer = self.create_data_analyzer(run, host, proc) baseline_analyzer = baseline.create_data_analyzer(run, host, proc) - client_run = '{run}/{client}/{proc}'.format(run=run, client=client, proc=proc) + client_run = "{run}/{client}/{proc}".format(run=run, client=client, proc=proc) compare_results = self._compare_client_results(client_run, self_analyzer, baseline_analyzer) results.extend(compare_results) # TODO: check results from monitors @@ -130,27 +135,27 @@ def prefill(self): def run(self): if self.osd_ra and self.osd_ra_changed: - logger.info('Setting OSD Read Ahead to: %s', self.osd_ra) - self.cluster.set_osd_param('read_ahead_kb', self.osd_ra) + logger.info("Setting OSD Read Ahead to: %s", self.osd_ra) + self.cluster.set_osd_param("read_ahead_kb", self.osd_ra) - logger.debug('Cleaning existing temporary run directory: %s', self.run_dir) - common.pdsh(settings.getnodes('clients', 'osds', 'mons', 'rgws'), 'sudo rm -rf %s' % self.run_dir).communicate() + logger.debug("Cleaning existing temporary run directory: %s", self.run_dir) + common.pdsh(settings.getnodes("clients", "osds", "mons", "rgws"), "sudo rm -rf %s" % self.run_dir).communicate() if self.valgrind is not None: - logger.debug('Adding valgrind to the command path.') + logger.debug("Adding valgrind to the command path.") self.cmd_path_full = common.setup_valgrind(self.valgrind, self.getclass(), self.run_dir) # Set the full command path self.cmd_path_full += self.cmd_path # Store the parameters of the test run - config_file = os.path.join(self.archive_dir, 'benchmark_config.yaml') + config_file = os.path.join(self.archive_dir, "benchmark_config.yaml") if not os.path.exists(self.archive_dir): os.makedirs(self.archive_dir) if not os.path.exists(config_file): config_dict = dict(cluster=self.config) - with open(config_file, 'w') as fd: + with open(config_file, "w") as fd: yaml.dump(config_dict, fd, default_flow_style=False) - def exists(self): + def exists(self) -> bool: return False def compare(self, baseline): @@ -160,10 +165,10 @@ def cleanup(self): pass def dropcaches(self): - nodes = settings.getnodes('clients', 'osds') + nodes = settings.getnodes("clients", "osds") - common.pdsh(nodes, 'sync').communicate() - common.pdsh(nodes, 'echo 3 | sudo tee /proc/sys/vm/drop_caches').communicate() + common.pdsh(nodes, "sync").communicate() + common.pdsh(nodes, "echo 3 | sudo tee /proc/sys/vm/drop_caches").communicate() def __str__(self): return str(self.config) @@ -205,7 +210,12 @@ def __init__(self, run, alias, result, baseline, stmt, accepted): self.accepted = accepted def __str__(self): - fmt = '{run}: {alias}: {stmt}:: {result}/{baseline} => {status}' - return fmt.format(run=self.run, alias=self.alias, stmt=self.stmt, - result=self.result, baseline=self.baseline, - status="accepted" if self.accepted else "rejected") + fmt = "{run}: {alias}: {stmt}:: {result}/{baseline} => {status}" + return fmt.format( + run=self.run, + alias=self.alias, + stmt=self.stmt, + result=self.result, + baseline=self.baseline, + status="accepted" if self.accepted else "rejected", + ) diff --git a/benchmark/fio.py b/benchmark/fio.py index 9407dbb1..fd82f0da 100644 --- a/benchmark/fio.py +++ b/benchmark/fio.py @@ -1,273 +1,178 @@ +""" +A benchark run using the fio I/O exercisor +""" + +from logging import Logger, getLogger +from time import sleep +from typing import Any, Dict, List, Union + +import client_endpoints_factory import common -import settings import monitoring -import os -import time -import logging -import pathlib -import client_endpoints_factory +import settings +from benchmark.fio_common import FioCommon +from cluster.cluster import Cluster -from .benchmark import Benchmark - -logger = logging.getLogger("cbt") - - -class Fio(Benchmark): - def __init__(self, archive_dir, cluster, config): - super(Fio, self).__init__(archive_dir, cluster, config) - - # FIXME there are too many permutations, need to put results in SQLITE3 - self.cmd_path = config.get('cmd_path', '/usr/bin/fio') - self.direct = str(config.get('direct', 1)) - self.time = config.get('time', None) - self.time_based = bool(config.get('time_based', False)) - self.ramp = config.get('ramp', None) - self.iodepth = config.get('iodepth', 16) - self.prefill_iodepth = config.get('prefill_iodepth', 16) - self.numjobs = config.get('numjobs', 1) - self.sync = config.get('sync', None) - self.end_fsync = config.get('end_fsync', 0) - self.mode = config.get('mode', 'write') - self.rwmixread = config.get('rwmixread', 50) - self.rwmixwrite = 100 - self.rwmixread - self.logging = config.get('logging', True) - self.log_avg_msec = config.get('log_avg_msec', None) - self.ioengine = config.get('ioengine', 'libaio') - self.bssplit = config.get('bssplit', None) - self.bsrange = config.get('bsrange', None) - self.bs = config.get('bs', None) - self.op_size = config.get('op_size', 4194304) # Deprecated, please use bs - self.size = config.get('size', 4096) - self.procs_per_endpoint = config.get('procs_per_endpoint', 1) - self.random_distribution = config.get('random_distribution', None) - self.rate_iops = config.get('rate_iops', None) - self.fio_out_format = "json,normal" - self.prefill_flag = config.get('prefill', True) - self.norandommap = config.get("norandommap", False) - self.out_dir = self.archive_dir - self.client_endpoints = config.get("client_endpoints", None) - self.recov_test_type = config.get('recov_test_type', 'blocking') - - def exists(self): - if os.path.exists(self.out_dir): - logger.info('Skipping existing test in %s.', self.out_dir) - return True - return False - - def initialize(self): - super(Fio, self).initialize() - - # Clean and Create the run directory - common.clean_remote_dir(self.run_dir) - common.make_remote_dir(self.run_dir) - - def initialize_endpoints(self): - super(Fio, self).initialize_endpoints() +log: Logger = getLogger("cbt") + +class Fio(FioCommon): + def __init__(self, archive_dir: str, cluster: Cluster, configuration: Dict[str, Any]): + self._defaults = {"prefill_iodepth": "16"} + super().__init__(archive_dir, cluster, configuration) + + self._procs_per_endpoint: int = self._configuration_with_defaults.get("procs_per_endpoint", 1) + self._recovery_test_type: str = self._configuration_with_defaults.get("recov_test_type", "blocking") + self._endpoints_per_client: int + self._endpoint_type: str = "" + self._endpoints: List[str] = [] + + def initialize(self) -> None: + common.clean_remote_dir(self.run_dir) # type: ignore [no-untyped-call] + common.make_remote_dir(self.run_dir) # type: ignore [no-untyped-call] + + def initialize_endpoints(self) -> None: # Get the client_endpoints and set them up - if self.client_endpoints is None: - raise ValueError('No client_endpoints defined!') - self.client_endpoints_object = client_endpoints_factory.get(self.cluster, self.client_endpoints) + if self._client_endpoints is None: + raise ValueError("No client_endpoints defined!") + self.client_endpoints_object = client_endpoints_factory.get(self._cluster, self._client_endpoints) # type: ignore [no-untyped-call] # Create the recovery image based on test type requested - if 'recovery_test' in self.cluster.config and self.recov_test_type == 'background': - self.client_endpoints_object.create_recovery_image() + if "recovery_test" in self._cluster.config and self._recovery_test_type == "background": + self.client_endpoints_object.create_recovery_image() # type: ignore [no-untyped-call] self.create_endpoints() - def create_endpoints(self): - if not self.client_endpoints_object.get_initialized(): - self.client_endpoints_object.initialize() - new_ep = True + def create_endpoints(self) -> None: + if not self.client_endpoints_object.get_initialized(): # type: ignore [no-untyped-call] + self.client_endpoints_object.initialize() # type: ignore [no-untyped-call] - self.endpoint_type = self.client_endpoints_object.get_endpoint_type() - self.endpoints_per_client = self.client_endpoints_object.get_endpoints_per_client() - self.endpoints = self.client_endpoints_object.get_endpoints() + self._endpoint_type = self.client_endpoints_object.get_endpoint_type() # type: ignore [no-untyped-call] + self._endpoints_per_client = self.client_endpoints_object.get_endpoints_per_client() # type: ignore [no-untyped-call] + self._endpoints = self.client_endpoints_object.get_endpoints() # type: ignore [no-untyped-call] # Error out if the aggregate fio size is going to be larger than the endpoint size - aggregate_size = self.numjobs * self.procs_per_endpoint * self.size - endpoint_size = self.client_endpoints_object.get_endpoint_size() + assert self._cli_options["size"] is not None and self._cli_options["numjobs"] is not None + aggregate_size = ( + int(self._cli_options["numjobs"]) * self._procs_per_endpoint * int(self._cli_options["size"][:1]) + ) + endpoint_size = self.client_endpoints_object.get_endpoint_size() # type: ignore [no-untyped-call] if aggregate_size > endpoint_size: - raise ValueError("Aggregate fio data size (%dKB) exceeds end_point size (%dKB)! Please check numjobs, procs_per_endpoint, and size settings." % (aggregate_size, endpoint_size)) + raise ValueError( + "Aggregate fio data size (%dKB) exceeds end_point size (%dKB)! Please check numjobs, procs_per_endpoint, and size settings." + % (aggregate_size, endpoint_size) + ) - if self.endpoint_type == 'rbd' and self.ioengine != 'rbd': - logger.warn('rbd endpoints must use the librbd fio engine! Setting ioengine=rbd') - self.ioengine = 'rbd' - if self.endpoint_type == 'rbd' and self.direct != '1': - logger.warn('rbd endpoints must use O_DIRECT. Setting direct=1') - self.direct = '1' + if self._endpoint_type == "rbd" and self._cli_options["ioengine"] != "rbd": + log.warning("rbd endpoints must use the librbd fio engine! Setting ioengine=rbd") + self.ioengine = "rbd" + if self._endpoint_type == "rbd" and self._cli_options["direct"] != "1": + log.warning("rbd endpoints must use O_DIRECT. Setting direct=1") + self.direct = "1" - def fio_command_extra(self, ep_num): - cmd = '' + def fio_command_extra(self, endpoint_number: int) -> str: + command: str = "" # typical directory endpoints - if self.endpoint_type == 'directory': - for proc_num in range(self.procs_per_endpoint): - cmd += ' --name=%s/`%s`-%s-%s' % (self.endpoints[ep_num], common.get_fqdn_cmd(), ep_num, proc_num) + if self._endpoint_type == "directory": + for proc_num in range(self._procs_per_endpoint): + command = ( + f"--name={self._endpoints[endpoint_number]}/`{common.get_fqdn_cmd()}" # type: ignore [no-untyped-call] + + "`-{endpoint_number}-{proc_num} " + ) # handle rbd endpoints with the librbbd engine. - elif self.endpoint_type == 'rbd': - pool_name, rbd_name = self.endpoints[ep_num].split("/") - cmd += ' --clientname=admin' - cmd += ' --pool=%s' % pool_name - cmd += ' --rbdname=%s' % rbd_name - cmd += ' --invalidate=0' - for proc_num in range(self.procs_per_endpoint): - rbd_name = '%s-%d' % (self.endpoints[ep_num], proc_num) - cmd += ' --name=%s' % rbd_name - return cmd - - def prefill_command(self, ep_num): - cmd = 'sudo %s' % self.cmd_path - cmd += ' --ioengine=%s' % self.ioengine - cmd += ' --rw=write' - cmd += ' --numjobs=%d' % self.numjobs - cmd += ' --bs=4M' - cmd += ' --iodepth=%d' % self.prefill_iodepth - cmd += ' --size %dM' % self.size - cmd += ' --output-format=%s' % self.fio_out_format - cmd += self.fio_command_extra(ep_num) - return cmd - - def prefill(self): - super(Fio, self).prefill() - if not self.prefill_flag: + elif self._endpoint_type == "rbd": + pool_name, rbd_name = self._endpoints[endpoint_number].split("/") + command += " --clientname=admin --invalidate=0" + command += f" --pool={pool_name} --rbdname={rbd_name}" + for proc_num in range(self._procs_per_endpoint): + rbd_name = f"{self._endpoints[endpoint_number]}-{proc_num}" + command += f" --name={rbd_name}" + return command + + def _build_prefill_command(self, endpoint_number: int) -> str: + command = f"sudo {self.cmd_path} --rw=write --bs=4M --iodepth={self._configuration_with_defaults.get('prefill_iodepth')}" + + for option in ["ioengine", "numjobs", "size", "output-format"]: + command += f"--{option}={self._cli_options[option]}" + + command += self.fio_command_extra(endpoint_number) + return command + + def prefill(self) -> None: + if not self._configuration_with_defaults.get("prefill", True): return - # populate the fio files - ps = [] - logger.info('Attempting to prefill fio files...') - for ep_num in range(self.endpoints_per_client): - p = common.pdsh(settings.getnodes('clients'), self.prefill_command(ep_num)) - ps.append(p) - for p in ps: - p.wait() - - def run_command(self, ep_num): - out_file = '%s/output.%d' % (self.run_dir, ep_num) - - # cmd_path_full includes any valgrind or other preprocessors vs cmd_path - cmd = 'sudo %s' % self.cmd_path_full - - # IO options - cmd += ' --ioengine=%s' % self.ioengine - cmd += ' --direct=%s' % self.direct - if self.bssplit is not None: - cmd += ' --bssplit=%s' % self.bssplit - if self.bsrange is not None: - cmd += ' --bsrange=%s' % self.bsrange - if self.bs is not None: - cmd += ' --bs=%s' % self.bs - elif self.op_size is not None: - logger.warn('op_size is deprecated, please use bs in the future') - cmd += ' --bs=%s' % self.op_size - cmd += ' --iodepth=%d' % self.iodepth - if self.sync is not None: - cmd += ' --sync=%s' % self.sync - cmd += ' --end_fsync=%d' % self.end_fsync - cmd += ' --rw=%s' % self.mode - if (self.mode == 'readwrite' or self.mode == 'randrw'): - cmd += ' --rwmixread=%s --rwmixwrite=%s' % (self.rwmixread, self.rwmixwrite) - if self.random_distribution is not None: - cmd += ' --random_distribution=%s' % self.random_distribution - if self.rate_iops is not None: - cmd += ' --rate_iops=%d' % self.rate_iops - if self.norandommap: - cmd += ' --norandommap' - - # Set the output size - if self.size: - cmd += ' --size=%dM' % self.size - cmd += ' --numjobs=%d' % self.numjobs - - # Time options - if self.time is not None: - cmd += ' --runtime=%d' % self.time - if self.time_based is True: - cmd += ' --time_based' - if self.ramp is not None: - cmd += ' --ramp_time=%d' % self.ramp - - # Put extra options before logging and output for conveneince of debugging - cmd += self.fio_command_extra(ep_num) - - # Logging and output options - if self.logging: - cmd += ' --write_iops_log=%s' % out_file - cmd += ' --write_bw_log=%s' % out_file - cmd += ' --write_lat_log=%s' % out_file - if self.log_avg_msec is not None: - cmd += ' --log_avg_msec=%d' % self.log_avg_msec - cmd += ' --output-format=%s' % self.fio_out_format - - # End the fio_cmd - cmd += ' > %s' % (out_file) - return cmd - - def run(self): - super(Fio, self).run() + # pre-populate the fio files + processes: List[Union[common.CheckedPopen, common.CheckedPopenLocal]] = [] + log.info("Attempting to prefill fio files") + for endpoint_number in range(self._endpoints_per_client): + process = common.pdsh(settings.getnodes("clients"), self._build_prefill_command(endpoint_number)) # type: ignore [no-untyped-call] + processes.append(process) + for process in processes: + process.wait() + + def run(self) -> None: + super().run() # type: ignore [no-untyped-call] # We'll always drop caches for rados bench - self.dropcaches() + self.dropcaches() # type: ignore [no-untyped-call] # Create the run directory - common.make_remote_dir(self.run_dir) + common.make_remote_dir(self.run_dir) # type: ignore [no-untyped-call] # dump the cluster config - self.cluster.dump_config(self.run_dir) + self._cluster.dump_config(self.run_dir) # type: ignore [no-untyped-call] - time.sleep(5) + sleep(5) # Run the backfill testing thread if requested - if 'recovery_test' in self.cluster.config: - if self.recov_test_type == 'blocking': + if "recovery_test" in self._cluster.config: + if self._recovery_test_type == "blocking": recovery_callback = self.recovery_callback_blocking - elif self.recov_test_type == 'background': + elif self._recovery_test_type == "background": recovery_callback = self.recovery_callback_background - self.cluster.create_recovery_test(self.run_dir, recovery_callback, self.recov_test_type) + self._cluster.create_recovery_test(self.run_dir, recovery_callback, self._recovery_test_type) # type: ignore [no-untyped-call] - if 'recovery_test' in self.cluster.config and self.recov_test_type == 'background': + if "recovery_test" in self._cluster.config and self._recovery_test_type == "background": # Wait for signal to start client IO - self.cluster.wait_start_io() + self._cluster.wait_start_io() # type: ignore [no-untyped-call] - monitoring.start(self.run_dir) + monitoring.start(self.run_dir) # type: ignore [no-untyped-call] - logger.info('Running fio %s test.', self.mode) - ps = [] - for i in range(self.endpoints_per_client): - p = common.pdsh(settings.getnodes('clients'), self.run_command(i)) - ps.append(p) - for p in ps: - p.wait() + log.info("Running fio %s test.", self._cli_options["rw"]) + processes: List[Union[common.CheckedPopen, common.CheckedPopenLocal]] = [] + for i in range(self._endpoints_per_client): + process = common.pdsh(settings.getnodes("clients"), self._generate_command_line(i)) # type: ignore [no-untyped-call] + processes.append(process) + for process in processes: + process.wait() # If we were doing recovery, wait until it's done. - if 'recovery_test' in self.cluster.config: - self.cluster.wait_recovery_done() + if "recovery_test" in self._cluster.config: + self._cluster.wait_recovery_done() # type: ignore [no-untyped-call] - monitoring.stop(self.run_dir) + monitoring.stop(self.run_dir) # type: ignore [no-untyped-call] # Finally, get the historic ops - self.cluster.dump_historic_ops(self.run_dir) - common.sync_files('%s/*' % self.run_dir, self.out_dir) - self.analyze(self.out_dir) - - def cleanup(self): - cmd_name = pathlib.PurePath(self.cmd_path).name - common.pdsh(settings.getnodes('clients'), 'sudo killall -2 %s' % cmd_name).communicate() + self._cluster.dump_historic_ops(self.run_dir) # type: ignore [no-untyped-call] + common.sync_files("%s/*" % self.run_dir, self._output_directory) # type: ignore [no-untyped-call] + self.analyze(self._output_directory) - def recovery_callback_blocking(self): + def recovery_callback_blocking(self) -> None: self.cleanup() - def recovery_callback_background(self): - logger.info('Recovery thread completed!') + def recovery_callback_background(self) -> None: + log.info("Recovery thread completed!") - def analyze(self, out_dir): - logger.info('Convert results to json format.') - for client in settings.getnodes('clients').split(','): - host = settings.host_info(client)["host"] - for i in range(self.endpoints_per_client): + def analyze(self, output_directory: str) -> None: + log.info("Converting results to json format.") + for client in settings.getnodes("clients").split(","): # type: ignore [no-untyped-call] + host = settings.host_info(client)["host"] # type: ignore [no-untyped-call] + for i in range(self._endpoints_per_client): found = 0 - out_file = '%s/output.%d.%s' % (out_dir, i, host) - json_out_file = '%s/json_output.%d.%s' % (out_dir, i, host) + out_file = "%s/output.%d.%s" % (output_directory, i, host) + json_out_file = "%s/json_output.%d.%s" % (output_directory, i, host) with open(out_file) as fd: - with open(json_out_file, 'w') as json_fd: + with open(json_out_file, "w") as json_fd: for line in fd.readlines(): if len(line.strip()) == 0: found = 0 @@ -277,6 +182,3 @@ def analyze(self, out_dir): if found == 0: if "Starting" in line: found = 1 - - def __str__(self): - return "%s\n%s\n%s" % (self.run_dir, self.out_dir, super(Fio, self).__str__()) diff --git a/benchmark/fio_common.py b/benchmark/fio_common.py new file mode 100644 index 00000000..56b9b4db --- /dev/null +++ b/benchmark/fio_common.py @@ -0,0 +1,194 @@ +""" +The methods common to all fio subclasses +""" + +import os +from abc import ABC, abstractmethod +from logging import Logger, getLogger +from pathlib import PurePath +from typing import Any, Dict, List, Optional + +import common +import settings +from benchmark.benchmark import Benchmark +from cluster.cluster import Cluster + +log: Logger = getLogger("cbt") + + +class FioCommon(Benchmark, ABC): + """ + A base class that defines the form of all of the fio sub classes + """ + + VALID_FLAGS: List[str] = ["norandommap", "time_based"] + + def __init__(self, archive_dir: str, cluster: Cluster, configuration: Dict[str, Any]) -> None: + super().__init__(archive_dir, cluster, configuration) # type: ignore [no-untyped-call] + + self._cluster: Cluster = cluster + self._output_directory: str = archive_dir + + self._defaults: Dict[str, str] + + self._configuration_with_defaults: Dict[str, Any] = { + "output-format": "json,normal", + "cmd_path": "/usr/bin/fio", + "iodepth": "16", + "end_fsync": "0", + } + self._create_configuration_with_defaults(configuration) + + self._cli_options: Dict[str, Optional[str]] = {} + self._cli_flags: List[str] = [] + self._set_cli_options_from_configuration() + self._set_cli_flags_from_configuration() + + self.cmd_path = f"{self._cli_options.get('cmd_path')}" + + self._client_endpoints: Optional[str] = self._configuration_with_defaults.get("client_endpoints", None) + + @abstractmethod + def initialize(self) -> None: + """ + Set up any pre-confitions required for the test + """ + + @abstractmethod + def initialize_endpoints(self) -> None: + """ + Initialise the endpoints for this test. If they are not passed + then get them from the cluster details + """ + + @abstractmethod + def fio_command_extra(self, endpoint_number: int) -> str: + """ + Extra parameters that are required for running with endpoints + """ + + @abstractmethod + def _build_prefill_command(self, endpoint_number: int) -> str: + """ + Build the CLI to be used to prefill any volumes used for this test + """ + + def exists(self) -> bool: + if os.path.exists(self._output_directory): + log.info("Skipping existing test in %s.", self._output_directory) + return True + return False + + def cleanup(self) -> None: + cmd_name = PurePath(self.cmd_path).name + common.pdsh(settings.getnodes("clients"), f"sudo killall -2 {cmd_name}").communicate() # type: ignore [no-untyped-call] + + def _get_full_command_path(self) -> str: + """ + Work out the full path to the fio command, including sudo if + required + """ + cmd_path: str = f"{self.cmd_path_full} " + + if cmd_path[:4] != "sudo": + cmd_path = f"sudo {cmd_path}" + + return cmd_path + + def _create_configuration_with_defaults(self, configuration: Dict[str, Any]) -> None: + """ + Create the full configuration for this test including + any default values + """ + self._configuration_with_defaults.update(self._defaults) + self._configuration_with_defaults.update(configuration) + + def _set_cli_options_from_configuration(self) -> None: + """ + Convert the configuration passed in into our own internal structure + that we can use to build the CLI to send to fio + """ + ############################### Test options ######################### + self._cli_options["numjobs"] = self._configuration_with_defaults.get("numjobs", "1") + self._cli_options["runtime"] = self._configuration_with_defaults.get("time", None) + self._cli_options["ramp_time"] = self._configuration_with_defaults.get("ramp", None) + self._cli_options["end_fsync"] = self._configuration_with_defaults.get("end_fsync", None) + self._cli_options["random_distribution"] = self._configuration_with_defaults.get("random_distribution", None) + self._cli_options["output-format"] = self._configuration_with_defaults.get("output-format") + + self._cli_options["rw"] = self._configuration_with_defaults.get("mode", "write") + if self._cli_options["rw"] == "readwrite" or self._cli_options["rw"] == "randrw": + rwmixread: int = self._configuration_with_defaults.get("rwmixread", 50) + rwmixwrite: int = 100 - rwmixread + self._cli_options["rwmixread"] = f"{rwmixread}" + self._cli_options["rwmixwrite"] = f"{rwmixwrite}" + + ############################### I/O Options ########################## + self._cli_options["bs"] = self._configuration_with_defaults.get("bs", None) + if self._cli_options["bs"] is None: + log.warning( + "bs option is not set in configuration yaml file. Checking for the deprecated op_size option instead" + ) + self._cli_options["bs"] = self._configuration_with_defaults.get("op_size", "4194304") + + self._cli_options["ioengine"] = self._configuration_with_defaults.get("ioengine", "libaio") + self._cli_options["direct"] = self._configuration_with_defaults.get("direct", "1") + self._cli_options["bssplit"] = self._configuration_with_defaults.get("bssplit", None) + self._cli_options["bsrange"] = self._configuration_with_defaults.get("bsrange", None) + self._cli_options["iodepth"] = self._configuration_with_defaults.get("iodepth") + self._cli_options["rate_iops"] = self._configuration_with_defaults.get("rate_iops", None) + self._cli_options["sync"] = self._configuration_with_defaults.get("sync", None) + self._cli_options["end_fsync"] = self._configuration_with_defaults.get("end_fsync") + self._cli_options["size"] = self._configuration_with_defaults.get("size", "4096") + # This assumes M is the unit - do we want to add an option in future to set the + # units? + self._cli_options["size"] = f"{self._cli_options['size']}M" + + ############################# Logging Options ######################## + self._cli_options["log_avg_msec"] = self._configuration_with_defaults.get("log_avg_msec", None) + + def _generate_logging_options(self, output_file: str) -> None: + """ + Set the logging options for the fio cli + """ + if self._configuration_with_defaults.get("logging", True) is True: + self._cli_options["write_iops_log"] = output_file + self._cli_options["write_bw_log"] = output_file + self._cli_options["write_lat_log"] = output_file + + def _set_cli_flags_from_configuration(self) -> None: + """ + Convert any flags in the configuration to our internal structure + """ + + for flag in self.VALID_FLAGS: + if self._configuration_with_defaults.get(flag, False): + self._cli_flags.append(flag) + + def _generate_command_line(self, endpoint_number: int) -> str: + """ + Actually create the commnad line that will be used to run fio I/O for + this test + """ + out_file: str = f"{self.run_dir}/output.{endpoint_number}" + + self._generate_logging_options(out_file) + + full_command: str = self._get_full_command_path() + + for option, value in self._cli_options.items(): + if value is not None: + full_command += f"--{option}={value} " + + for flag in self._cli_flags: + full_command += f"--{flag} " + + full_command += self.fio_command_extra(endpoint_number) + + # Make sure we log the output to out_file + full_command += f" > {out_file}" + + return full_command + + def __str__(self) -> str: + return f"Run directory: {self.run_dir}\nOutput: {self._output_directory}\nConfiguration: {self._configuration_with_defaults}" diff --git a/cluster/cluster.py b/cluster/cluster.py index 904afdc2..d6de5a82 100644 --- a/cluster/cluster.py +++ b/cluster/cluster.py @@ -1,11 +1,11 @@ -class Cluster(object): +class Cluster: def __init__(self, config): self.config = config - base_tmp = config.get('tmp_dir', '/tmp/cbt') - self.mnt_dir = config.get('mnt_dir', "%s/%s" % (base_tmp, 'mnt')) - self.tmp_dir = "%s/%s" % (base_tmp, config.get('clusterid')) - self.archive_dir = "%s/%s" % (config.get('archive_dir'), config.get('clusterid')) - self.tmp_conf = config.get('tmp_conf', '/tmp/cbt') + base_tmp = config.get("tmp_dir", "/tmp/cbt") + self.mnt_dir = config.get("mnt_dir", "%s/%s" % (base_tmp, "mnt")) + self.tmp_dir = "%s/%s" % (base_tmp, config.get("clusterid")) + self.archive_dir = "%s/%s" % (config.get("archive_dir"), config.get("clusterid")) + self.tmp_conf = config.get("tmp_conf", "/tmp/cbt") def get_mnt_dir(self): return self.mnt_dir @@ -19,5 +19,23 @@ def initialize(self): def cleanup(self): pass + # Adding these 4 in here for fio refactor. Ideally the cluster class will + # eventually be an abstract base class (ABC), but that is work for the + # future + def dump_config(self, run_dir): + pass + + def create_recovery_test(self, run_dir, callback, test_type="blocking"): + pass + + def wait_start_io(self): + pass + + def wait_recovery_done(self): + pass + + def dump_historic_ops(self, run_dir): + pass + def __str__(self): return str(self.config) diff --git a/tests/test_bm_cephtestrados.py b/tests/test_bm_cephtestrados.py index c7342256..7aafacb9 100644 --- a/tests/test_bm_cephtestrados.py +++ b/tests/test_bm_cephtestrados.py @@ -16,7 +16,7 @@ class TestBenchmarkcephtestrados(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = 'e6b6fcd2be74bd08939c64a249ab2125' + bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' md5_returned = None @classmethod diff --git a/tests/test_bm_fio.py b/tests/test_bm_fio.py index a0efa8dc..bf7dbeee 100644 --- a/tests/test_bm_fio.py +++ b/tests/test_bm_fio.py @@ -16,7 +16,7 @@ class TestBenchmarkfio(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = 'e6b6fcd2be74bd08939c64a249ab2125' + bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' md5_returned = None @classmethod @@ -40,89 +40,89 @@ def test_valid_baseline(self): """ Verify the baseline has not been compromised """ self.assertEqual( self.bl_md5, str(self.md5_returned) ) - def test_valid_archive_dir(self): - """ Basic sanity attribute identity archive_dir check""" + def test_valid__cli_flags(self): + """ Basic sanity attribute identity _cli_flags check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['archive_dir'], b.__dict__['archive_dir']) + self.assertEqual(self.bl_json['fio']['_cli_flags'], b.__dict__['_cli_flags']) - def test_valid_bs(self): - """ Basic sanity attribute identity bs check""" + def test_valid__cli_options(self): + """ Basic sanity attribute identity _cli_options check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['bs'], b.__dict__['bs']) + self.assertEqual(self.bl_json['fio']['_cli_options'], b.__dict__['_cli_options']) - def test_valid_bsrange(self): - """ Basic sanity attribute identity bsrange check""" + def test_valid__client_endpoints(self): + """ Basic sanity attribute identity _client_endpoints check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['bsrange'], b.__dict__['bsrange']) + self.assertEqual(self.bl_json['fio']['_client_endpoints'], b.__dict__['_client_endpoints']) - def test_valid_bssplit(self): - """ Basic sanity attribute identity bssplit check""" + def test_valid__configuration_with_defaults(self): + """ Basic sanity attribute identity _configuration_with_defaults check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['bssplit'], b.__dict__['bssplit']) + self.assertEqual(self.bl_json['fio']['_configuration_with_defaults'], b.__dict__['_configuration_with_defaults']) - def test_valid_client_endpoints(self): - """ Basic sanity attribute identity client_endpoints check""" + def test_valid__defaults(self): + """ Basic sanity attribute identity _defaults check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['client_endpoints'], b.__dict__['client_endpoints']) + self.assertEqual(self.bl_json['fio']['_defaults'], b.__dict__['_defaults']) - def test_valid_cmd_path(self): - """ Basic sanity attribute identity cmd_path check""" + def test_valid__endpoint_type(self): + """ Basic sanity attribute identity _endpoint_type check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['cmd_path'], b.__dict__['cmd_path']) + self.assertEqual(self.bl_json['fio']['_endpoint_type'], b.__dict__['_endpoint_type']) - def test_valid_cmd_path_full(self): - """ Basic sanity attribute identity cmd_path_full check""" + def test_valid__endpoints(self): + """ Basic sanity attribute identity _endpoints check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['cmd_path_full'], b.__dict__['cmd_path_full']) + self.assertEqual(self.bl_json['fio']['_endpoints'], b.__dict__['_endpoints']) - def test_valid_config(self): - """ Basic sanity attribute identity config check""" + def test_valid__output_directory(self): + """ Basic sanity attribute identity _output_directory check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['config'], b.__dict__['config']) + self.assertEqual(self.bl_json['fio']['_output_directory'], b.__dict__['_output_directory']) - def test_valid_direct(self): - """ Basic sanity attribute identity direct check""" + def test_valid__procs_per_endpoint(self): + """ Basic sanity attribute identity _procs_per_endpoint check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['direct'], b.__dict__['direct']) + self.assertEqual(self.bl_json['fio']['_procs_per_endpoint'], b.__dict__['_procs_per_endpoint']) - def test_valid_end_fsync(self): - """ Basic sanity attribute identity end_fsync check""" + def test_valid__recovery_test_type(self): + """ Basic sanity attribute identity _recovery_test_type check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['end_fsync'], b.__dict__['end_fsync']) + self.assertEqual(self.bl_json['fio']['_recovery_test_type'], b.__dict__['_recovery_test_type']) - def test_valid_fio_out_format(self): - """ Basic sanity attribute identity fio_out_format check""" + def test_valid_archive_dir(self): + """ Basic sanity attribute identity archive_dir check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['fio_out_format'], b.__dict__['fio_out_format']) + self.assertEqual(self.bl_json['fio']['archive_dir'], b.__dict__['archive_dir']) - def test_valid_iodepth(self): - """ Basic sanity attribute identity iodepth check""" + def test_valid_cmd_path(self): + """ Basic sanity attribute identity cmd_path check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['iodepth'], b.__dict__['iodepth']) + self.assertEqual(self.bl_json['fio']['cmd_path'], b.__dict__['cmd_path']) - def test_valid_ioengine(self): - """ Basic sanity attribute identity ioengine check""" + def test_valid_cmd_path_full(self): + """ Basic sanity attribute identity cmd_path_full check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['ioengine'], b.__dict__['ioengine']) + self.assertEqual(self.bl_json['fio']['cmd_path_full'], b.__dict__['cmd_path_full']) - def test_valid_log_avg_msec(self): - """ Basic sanity attribute identity log_avg_msec check""" + def test_valid_config(self): + """ Basic sanity attribute identity config check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['log_avg_msec'], b.__dict__['log_avg_msec']) + self.assertEqual(self.bl_json['fio']['config'], b.__dict__['config']) def test_valid_log_bw(self): """ Basic sanity attribute identity log_bw check""" @@ -142,36 +142,6 @@ def test_valid_log_lat(self): self.cluster, 'fio', self.iteration) self.assertEqual(self.bl_json['fio']['log_lat'], b.__dict__['log_lat']) - def test_valid_logging(self): - """ Basic sanity attribute identity logging check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['logging'], b.__dict__['logging']) - - def test_valid_mode(self): - """ Basic sanity attribute identity mode check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['mode'], b.__dict__['mode']) - - def test_valid_norandommap(self): - """ Basic sanity attribute identity norandommap check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['norandommap'], b.__dict__['norandommap']) - - def test_valid_numjobs(self): - """ Basic sanity attribute identity numjobs check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['numjobs'], b.__dict__['numjobs']) - - def test_valid_op_size(self): - """ Basic sanity attribute identity op_size check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['op_size'], b.__dict__['op_size']) - def test_valid_osd_ra(self): """ Basic sanity attribute identity osd_ra check""" b = benchmarkfactory.get_object(self.archive_dir, @@ -184,96 +154,12 @@ def test_valid_osd_ra_changed(self): self.cluster, 'fio', self.iteration) self.assertEqual(self.bl_json['fio']['osd_ra_changed'], b.__dict__['osd_ra_changed']) - def test_valid_out_dir(self): - """ Basic sanity attribute identity out_dir check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['out_dir'], b.__dict__['out_dir']) - - def test_valid_prefill_flag(self): - """ Basic sanity attribute identity prefill_flag check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['prefill_flag'], b.__dict__['prefill_flag']) - - def test_valid_prefill_iodepth(self): - """ Basic sanity attribute identity prefill_iodepth check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['prefill_iodepth'], b.__dict__['prefill_iodepth']) - - def test_valid_procs_per_endpoint(self): - """ Basic sanity attribute identity procs_per_endpoint check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['procs_per_endpoint'], b.__dict__['procs_per_endpoint']) - - def test_valid_ramp(self): - """ Basic sanity attribute identity ramp check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['ramp'], b.__dict__['ramp']) - - def test_valid_random_distribution(self): - """ Basic sanity attribute identity random_distribution check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['random_distribution'], b.__dict__['random_distribution']) - - def test_valid_rate_iops(self): - """ Basic sanity attribute identity rate_iops check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['rate_iops'], b.__dict__['rate_iops']) - - def test_valid_recov_test_type(self): - """ Basic sanity attribute identity recov_test_type check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['recov_test_type'], b.__dict__['recov_test_type']) - def test_valid_run_dir(self): """ Basic sanity attribute identity run_dir check""" b = benchmarkfactory.get_object(self.archive_dir, self.cluster, 'fio', self.iteration) self.assertEqual(self.bl_json['fio']['run_dir'], b.__dict__['run_dir']) - def test_valid_rwmixread(self): - """ Basic sanity attribute identity rwmixread check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['rwmixread'], b.__dict__['rwmixread']) - - def test_valid_rwmixwrite(self): - """ Basic sanity attribute identity rwmixwrite check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['rwmixwrite'], b.__dict__['rwmixwrite']) - - def test_valid_size(self): - """ Basic sanity attribute identity size check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['size'], b.__dict__['size']) - - def test_valid_sync(self): - """ Basic sanity attribute identity sync check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['sync'], b.__dict__['sync']) - - def test_valid_time(self): - """ Basic sanity attribute identity time check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['time'], b.__dict__['time']) - - def test_valid_time_based(self): - """ Basic sanity attribute identity time_based check""" - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, 'fio', self.iteration) - self.assertEqual(self.bl_json['fio']['time_based'], b.__dict__['time_based']) - def test_valid_valgrind(self): """ Basic sanity attribute identity valgrind check""" b = benchmarkfactory.get_object(self.archive_dir, diff --git a/tests/test_bm_getput.py b/tests/test_bm_getput.py index 02918d39..90ca27a8 100644 --- a/tests/test_bm_getput.py +++ b/tests/test_bm_getput.py @@ -16,7 +16,7 @@ class TestBenchmarkgetput(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = 'e6b6fcd2be74bd08939c64a249ab2125' + bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' md5_returned = None @classmethod diff --git a/tests/test_bm_hsbench.py b/tests/test_bm_hsbench.py index 1d39cfe1..b47f73b5 100644 --- a/tests/test_bm_hsbench.py +++ b/tests/test_bm_hsbench.py @@ -16,7 +16,7 @@ class TestBenchmarkhsbench(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = 'e6b6fcd2be74bd08939c64a249ab2125' + bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' md5_returned = None @classmethod diff --git a/tests/test_bm_kvmrbdfio.py b/tests/test_bm_kvmrbdfio.py index fe167c84..91a4038e 100644 --- a/tests/test_bm_kvmrbdfio.py +++ b/tests/test_bm_kvmrbdfio.py @@ -16,7 +16,7 @@ class TestBenchmarkkvmrbdfio(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = 'e6b6fcd2be74bd08939c64a249ab2125' + bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' md5_returned = None @classmethod diff --git a/tests/test_bm_librbdfio.py b/tests/test_bm_librbdfio.py index fb410261..edb0585e 100644 --- a/tests/test_bm_librbdfio.py +++ b/tests/test_bm_librbdfio.py @@ -16,7 +16,7 @@ class TestBenchmarklibrbdfio(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = 'e6b6fcd2be74bd08939c64a249ab2125' + bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' md5_returned = None @classmethod diff --git a/tests/test_bm_nullbench.py b/tests/test_bm_nullbench.py index 6df4af60..0e1a52cc 100644 --- a/tests/test_bm_nullbench.py +++ b/tests/test_bm_nullbench.py @@ -16,7 +16,7 @@ class TestBenchmarknullbench(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = 'e6b6fcd2be74bd08939c64a249ab2125' + bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' md5_returned = None @classmethod diff --git a/tests/test_bm_radosbench.py b/tests/test_bm_radosbench.py index 7ba6fb98..d78dbdd1 100644 --- a/tests/test_bm_radosbench.py +++ b/tests/test_bm_radosbench.py @@ -16,7 +16,7 @@ class TestBenchmarkradosbench(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = 'e6b6fcd2be74bd08939c64a249ab2125' + bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' md5_returned = None @classmethod diff --git a/tests/test_bm_rawfio.py b/tests/test_bm_rawfio.py index f1165ab2..ba316714 100644 --- a/tests/test_bm_rawfio.py +++ b/tests/test_bm_rawfio.py @@ -16,7 +16,7 @@ class TestBenchmarkrawfio(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = 'e6b6fcd2be74bd08939c64a249ab2125' + bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' md5_returned = None @classmethod diff --git a/tests/test_bm_rbdfio.py b/tests/test_bm_rbdfio.py index a0f29ebb..80c2dfd0 100644 --- a/tests/test_bm_rbdfio.py +++ b/tests/test_bm_rbdfio.py @@ -16,7 +16,7 @@ class TestBenchmarkrbdfio(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = 'e6b6fcd2be74bd08939c64a249ab2125' + bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' md5_returned = None @classmethod diff --git a/tools/baseline.json b/tools/baseline.json index ae5db2b0..545ffdf9 100644 --- a/tools/baseline.json +++ b/tools/baseline.json @@ -145,12 +145,152 @@ } }, "fio": { + "_cli_flags": [], + "_cli_options": { + "bs": "4194304", + "bsrange": null, + "bssplit": null, + "direct": "1", + "end_fsync": "0", + "iodepth": "16", + "ioengine": "libaio", + "log_avg_msec": null, + "numjobs": "1", + "output-format": "json,normal", + "ramp_time": null, + "random_distribution": null, + "rate_iops": null, + "runtime": null, + "rw": "write", + "size": "4096M", + "sync": null + }, + "_client_endpoints": null, + "_cluster": { + "archive_dir": "/tmp/ceph", + "auth_urls": [], + "ceph_authtool_cmd": "/usr/local/bin/ceph-authtool", + "ceph_cmd": "/usr/local/bin/ceph", + "ceph_fuse_cmd": "/usr/bin/ceph-fuse", + "ceph_mds_cmd": "/usr/bin/ceph-mds", + "ceph_mgr_cmd": "/usr/local/bin/ceph-mgr", + "ceph_mon_cmd": "/usr/local/bin/ceph-mon", + "ceph_osd_cmd": "env -i TCMALLOC_MAX_TOTAL_THREAD_CACHE_BYTES=134217728 /usr/local/bin/ceph-osd", + "ceph_osd_online_rate": 10, + "ceph_osd_online_tmo": 120, + "ceph_osd_parallel_creates": null, + "ceph_rgw_cmd": "env -i TCMALLOC_MAX_TOTAL_THREAD_CACHE_BYTES=134217728 /usr/local/bin/radosgw", + "ceph_run_cmd": "/usr/local/bin/ceph-run", + "client_keyring": "/etc/ceph/ceph.keyring", + "client_secret": "/etc/ceph/ceph.secret", + "config": { + "archive_dir": "/tmp", + "ceph-authtool_cmd": "/usr/local/bin/ceph-authtool", + "ceph-mgr_cmd": "/usr/local/bin/ceph-mgr", + "ceph-mon_cmd": "/usr/local/bin/ceph-mon", + "ceph-osd_cmd": "env -i TCMALLOC_MAX_TOTAL_THREAD_CACHE_BYTES=134217728 /usr/local/bin/ceph-osd", + "ceph-rgw_cmd": "env -i TCMALLOC_MAX_TOTAL_THREAD_CACHE_BYTES=134217728 /usr/local/bin/radosgw", + "ceph-run_cmd": "/usr/local/bin/ceph-run", + "ceph_cmd": "/usr/local/bin/ceph", + "clients": [ + "localhost" + ], + "clusterid": "ceph", + "conf_file": "/etc/ceph/ceph.conf", + "fs": "xfs", + "head": "localhost", + "iterations": 1, + "mkfs_opts": "-f -i size=2048", + "mount_opts": "-o inode64,noatime,logbsize=256k", + "osd_ra": "0", + "osd_valgrind": "massif", + "osds": [ + "localhost" + ], + "osds_per_node": 1, + "pool_profiles": { + "rgw": { + "pg_size": 128, + "pgp_size": 128, + "replication": 1 + } + }, + "rados_cmd": "/usr/local/bin/rados", + "radosgw-admin_cmd": "/usr/local/bin/radosgw-admin", + "rbd_cmd": "/usr/local/bin/rbd", + "rgw_pools": { + "buckets": "rgw", + "buckets_data": "rgw", + "buckets_index": "rgw", + "control": "rgw", + "log": "rgw", + "meta": "rgw" + }, + "rgws": { + "localhost": { + "client.radosgw.gateway": { + "host": "127.0.0.1" + } + } + }, + "tmp_conf": "/etc/ceph/ceph.conf", + "tmp_dir": "/tmp/cbt.XYZ", + "use_existing": false, + "user": "user" + }, + "core_dir": "/tmp/cbt.XYZ/ceph/core", + "crimson_cpusets": [], + "cur_ruleset": 1, + "disable_bal": false, + "health_wait": 5, + "idle_duration": 0, + "log_dir": "/tmp/cbt.XYZ/ceph/log", + "mgr_valgrind": null, + "mnt_dir": "/tmp/cbt.XYZ/mnt", + "mon_valgrind": null, + "monitoring_dir": "/tmp/cbt.XYZ/ceph/monitoring", + "monmap_fn": "/tmp/cbt.XYZ/ceph/monmap", + "mount_cmd": "/usr/sbin/ceph.mount", + "newstore_block": false, + "osd_valgrind": "massif", + "osdmap_fn": "/tmp/cbt.XYZ/ceph/osdmap", + "pid_dir": "/tmp/cbt.XYZ/ceph/pid", + "prefill_recov_object_size": 0, + "prefill_recov_objects": 0, + "prefill_recov_time": 0, + "rados_cmd": "/usr/local/bin/rados", + "radosgw_admin_cmd": "/usr/local/bin/radosgw-admin", + "rbd_cmd": "/usr/local/bin/rbd", + "rbd_fuse_cmd": "/usr/bin/rbd-fuse", + "rbd_nbd_cmd": "/usr/bin/rbd-nbd", + "recov_pool_name": "", + "rgw_valgrind": null, + "ruleset_map": {}, + "tiering": false, + "tmp_conf": "/tmp/cbt.XYZ/ceph/ceph.conf", + "tmp_dir": "/tmp/cbt.XYZ/ceph", + "urls": [], + "use_existing": false, + "version_compat": "" + }, + "_configuration_with_defaults": { + "cmd_path": "/usr/bin/fio", + "end_fsync": "0", + "iodepth": "16", + "iteration": 0, + "output-format": "json,normal", + "prefill_iodepth": "16" + }, + "_defaults": { + "prefill_iodepth": "16" + }, + "_endpoint_type": "", + "_endpoints": [], + "_output_directory": "/tmp", + "_procs_per_endpoint": 1, + "_recovery_test_type": "blocking", "acceptable": {}, "archive_dir": "/tmp/results/00000000/id-83a653b5", - "bs": null, - "bsrange": null, - "bssplit": null, - "client_endpoints": null, "cluster": { "archive_dir": "/tmp/ceph", "auth_urls": [], @@ -258,42 +398,17 @@ "use_existing": false, "version_compat": "" }, - "cmd_path": "/usr/bin/fio", + "cmd_path": "None", "cmd_path_full": "", "config": { "iteration": 0 }, - "direct": "1", - "end_fsync": 0, - "fio_out_format": "json,normal", - "iodepth": 16, - "ioengine": "libaio", - "log_avg_msec": null, "log_bw": true, "log_iops": true, "log_lat": true, - "logging": true, - "mode": "write", - "norandommap": false, - "numjobs": 1, - "op_size": 4194304, "osd_ra": "0", "osd_ra_changed": true, - "out_dir": "/tmp/results/00000000/id-83a653b5", - "prefill_flag": true, - "prefill_iodepth": 16, - "procs_per_endpoint": 1, - "ramp": null, - "random_distribution": null, - "rate_iops": null, - "recov_test_type": "blocking", "run_dir": "/tmp/cbt.XYZ/00000000/Fio", - "rwmixread": 50, - "rwmixwrite": 50, - "size": 4096, - "sync": null, - "time": null, - "time_based": false, "valgrind": null }, "getput": { diff --git a/tools/serialise_benchmark.py b/tools/serialise_benchmark.py index fe47a498..f4608c19 100755 --- a/tools/serialise_benchmark.py +++ b/tools/serialise_benchmark.py @@ -4,38 +4,43 @@ # and automated creation of unit tests for them. # import argparse -import os, sys -import pprint +import hashlib import json +import os +import pprint +import sys from json import JSONEncoder + import yaml -import hashlib + import benchmarkfactory import settings from cluster.ceph import Ceph from log_support import setup_loggers -log_fname = '/tmp/cbt-utest.log' +log_fname = "/tmp/cbt-utest.log" + class BenchGenerator(object): """ Class used for the serialisation of the benchmark classes and automated generation or unit tests """ + all_benchmarks = [ - 'nullbench', - 'fio', - 'hsbench', - 'radosbench', - 'kvmrbdfio', - 'rawfio', - 'librbdfio', - 'cephtestrados', - 'rbdfio', - 'getput' - ] + "nullbench", + "fio", + "hsbench", + "radosbench", + "kvmrbdfio", + "rawfio", + "librbdfio", + "cephtestrados", + "rbdfio", + "getput", + ] archive_dir = "/tmp" - iteration = {'acceptable': [1,2,3], 'iteration': 0} + iteration = {"acceptable": [1, 2, 3], "iteration": 0} cluster = {} bl_name = "tools/baseline.json" bl_md5 = None @@ -45,33 +50,32 @@ class BenchGenerator(object): current = {} def __init__(self): - """ Init using mock constructors for a fixed cluster """ + """Init using mock constructors for a fixed cluster""" settings.mock_initialize(config_file=BenchGenerator.cl_name) BenchGenerator.cluster = Ceph.mockinit(settings.cluster) def get_md5_bl(self): - """ Calculate the MD5sum from baseline contents """ - with open(self.bl_name, 'rb') as f: + """Calculate the MD5sum from baseline contents""" + with open(self.bl_name, "rb") as f: data = f.read() f.close() return hashlib.md5(data).hexdigest() - #bl_md5 = hashlib.md5(data.encode("utf-8")).hexdigest() + # bl_md5 = hashlib.md5(data.encode("utf-8")).hexdigest() def gen_json(self): - """ Serialise the object into a json file""" + """Serialise the object into a json file""" result = {} for bm in self.all_benchmarks: - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, bm, self.iteration) + b = benchmarkfactory.get_object(self.archive_dir, self.cluster, bm, self.iteration) result[bm] = b.__dict__ - with open(self.bl_name, 'w', encoding='utf-8') as f: + with open(self.bl_name, "w", encoding="utf-8") as f: json.dump(result, f, sort_keys=True, indent=4, cls=BenchJSONEncoder) f.close() # data from json.dump() does not support buffer API self.bl_md5 = self.get_md5_bl() def verify_md5(self): - """ Verify the MD5SUM of the baseline.json is correct """ + """Verify the MD5SUM of the baseline.json is correct""" md5_returned = self.get_md5_bl() if self.bl_md5 == md5_returned: print("MD5 verified.") @@ -81,13 +85,12 @@ def verify_md5(self): return False def verify_json(self): - """ Verify the baseline json against the current benchmark classes """ - with open(self.bl_name, 'r') as f: + """Verify the baseline json against the current benchmark classes""" + with open(self.bl_name, "r") as f: self.djson = json.load(f) f.close() for bm in self.all_benchmarks: - b = benchmarkfactory.get_object(self.archive_dir, - self.cluster, bm, self.iteration) + b = benchmarkfactory.get_object(self.archive_dir, self.cluster, bm, self.iteration) self.current[bm] = b.__dict__ # This loop verifies that the active classes have the same attributes # as the baseline: no complains would happen if new attributes have been @@ -95,8 +98,9 @@ def verify_json(self): for bm in self.djson.keys(): if isinstance(self.djson[bm], dict): for k in self.djson[bm].keys(): + print(f"looking at key {k} for benchmark {bm}") # Skip Cluster since its a Ceph object, and acceptable was removed - if k == "cluster" or k == "acceptable": + if k == "cluster" or k == "acceptable" or k == "_cluster": continue if not self.djson[bm][k] == self.current[bm][k]: if isinstance(self.djson[bm][k], dict): @@ -108,7 +112,7 @@ def verify_json(self): print(f"{bm}[{k}]: {self.djson[bm][k]} vs {self.current[bm][k]}") def gen_utests(self): - """ Generate the unit tests from baseline json against the self.current benchmark classes """ + """Generate the unit tests from baseline json against the self.current benchmark classes""" djson = self.djson for bm in djson.keys(): if isinstance(djson[bm], dict): @@ -116,12 +120,12 @@ def gen_utests(self): input = "tools/test_bm_template.py" out = f"tests/test_bm_{bm}.py" cmd = f"{subst} {input} > {out}" - #print(cmd) + # print(cmd) os.system(cmd) with open(out, "a") as f: for k in djson[bm].keys(): # Skip Cluster since its a Ceph object, and acceptable is removed - if k == "cluster" or k == "acceptable": + if k == "cluster" or k == "acceptable" or k == "_cluster": continue ut = f""" def test_valid_{k}(self): @@ -143,8 +147,9 @@ class BenchJSONEncoder(JSONEncoder): def default(self, obj): return obj.__dict__ + def main(argv): - setup_loggers(log_fname='/tmp/cbt-utest.log') + setup_loggers(log_fname="/tmp/cbt-utest.log") bg = BenchGenerator() bg.gen_json() bg.verify_json() @@ -152,5 +157,6 @@ def main(argv): bg.gen_utests() return 0 -if __name__ == '__main__': + +if __name__ == "__main__": exit(main(sys.argv)) From 3c75e3d7ef034bce4bf4c07db5f883cf9c6e12e0 Mon Sep 17 00:00:00 2001 From: Chris Harris Date: Thu, 10 Oct 2024 11:31:29 +0100 Subject: [PATCH 2/6] cbt: creating a common base class for fio subclasses Signed-off-by: Chris Harris --- benchmark/fio.py | 8 ++++---- benchmark/fio_common.py | 9 ++++++++- cluster/cluster.py | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/benchmark/fio.py b/benchmark/fio.py index fd82f0da..76950599 100644 --- a/benchmark/fio.py +++ b/benchmark/fio.py @@ -164,11 +164,14 @@ def recovery_callback_background(self) -> None: log.info("Recovery thread completed!") def analyze(self, output_directory: str) -> None: + """ + Convert the results from the run to a json format + """ log.info("Converting results to json format.") for client in settings.getnodes("clients").split(","): # type: ignore [no-untyped-call] host = settings.host_info(client)["host"] # type: ignore [no-untyped-call] for i in range(self._endpoints_per_client): - found = 0 + found = 1 out_file = "%s/output.%d.%s" % (output_directory, i, host) json_out_file = "%s/json_output.%d.%s" % (output_directory, i, host) with open(out_file) as fd: @@ -179,6 +182,3 @@ def analyze(self, output_directory: str) -> None: break if found == 1: json_fd.write(line) - if found == 0: - if "Starting" in line: - found = 1 diff --git a/benchmark/fio_common.py b/benchmark/fio_common.py index 56b9b4db..96baf4dc 100644 --- a/benchmark/fio_common.py +++ b/benchmark/fio_common.py @@ -44,7 +44,7 @@ def __init__(self, archive_dir: str, cluster: Cluster, configuration: Dict[str, self._set_cli_options_from_configuration() self._set_cli_flags_from_configuration() - self.cmd_path = f"{self._cli_options.get('cmd_path')}" + self.cmd_path = f"{self._configuration_with_defaults.get('cmd_path')}" self._client_endpoints: Optional[str] = self._configuration_with_defaults.get("client_endpoints", None) @@ -74,12 +74,19 @@ def _build_prefill_command(self, endpoint_number: int) -> str: """ def exists(self) -> bool: + """ + Make sure we do not overwrite results from a previous run of the tool + """ if os.path.exists(self._output_directory): log.info("Skipping existing test in %s.", self._output_directory) return True return False def cleanup(self) -> None: + """ + Make sure that all the processes for this test have completed or been + stopped + """ cmd_name = PurePath(self.cmd_path).name common.pdsh(settings.getnodes("clients"), f"sudo killall -2 {cmd_name}").communicate() # type: ignore [no-untyped-call] diff --git a/cluster/cluster.py b/cluster/cluster.py index d6de5a82..bfcb3998 100644 --- a/cluster/cluster.py +++ b/cluster/cluster.py @@ -19,7 +19,7 @@ def initialize(self): def cleanup(self): pass - # Adding these 4 in here for fio refactor. Ideally the cluster class will + # Adding these in here for fio refactor. Ideally the cluster class will # eventually be an abstract base class (ABC), but that is work for the # future def dump_config(self, run_dir): From d9e2abd31646219e4198d3ec1b5bade481e023bd Mon Sep 17 00:00:00 2001 From: Chris Harris Date: Mon, 21 Oct 2024 09:54:21 +0100 Subject: [PATCH 3/6] Revert "cbt: creating a common base class for fio subclasses" Reverting the change to fio.py as it was made in error This reverts commit 3c75e3d7ef034bce4bf4c07db5f883cf9c6e12e0. Signed-off-by: Chris Harris --- benchmark/fio.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmark/fio.py b/benchmark/fio.py index 76950599..fd82f0da 100644 --- a/benchmark/fio.py +++ b/benchmark/fio.py @@ -164,14 +164,11 @@ def recovery_callback_background(self) -> None: log.info("Recovery thread completed!") def analyze(self, output_directory: str) -> None: - """ - Convert the results from the run to a json format - """ log.info("Converting results to json format.") for client in settings.getnodes("clients").split(","): # type: ignore [no-untyped-call] host = settings.host_info(client)["host"] # type: ignore [no-untyped-call] for i in range(self._endpoints_per_client): - found = 1 + found = 0 out_file = "%s/output.%d.%s" % (output_directory, i, host) json_out_file = "%s/json_output.%d.%s" % (output_directory, i, host) with open(out_file) as fd: @@ -182,3 +179,6 @@ def analyze(self, output_directory: str) -> None: break if found == 1: json_fd.write(line) + if found == 0: + if "Starting" in line: + found = 1 From 73d5c0d5a4475bcdae1025ed1080686fc902d9f2 Mon Sep 17 00:00:00 2001 From: Chris Harris Date: Mon, 21 Oct 2024 09:54:21 +0100 Subject: [PATCH 4/6] Revert "cbt: creating a common base class for fio subclasses" Reverting the change to fio.py as it was made in error This reverts commit 3c75e3d7ef034bce4bf4c07db5f883cf9c6e12e0. Signed-off-by: Chris Harris --- benchmark/fio.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/benchmark/fio.py b/benchmark/fio.py index fd82f0da..37c265bf 100644 --- a/benchmark/fio.py +++ b/benchmark/fio.py @@ -164,6 +164,9 @@ def recovery_callback_background(self) -> None: log.info("Recovery thread completed!") def analyze(self, output_directory: str) -> None: + """ + Converts the output files from each run to a JSON format + """ log.info("Converting results to json format.") for client in settings.getnodes("clients").split(","): # type: ignore [no-untyped-call] host = settings.host_info(client)["host"] # type: ignore [no-untyped-call] From 4e2071bdef41cf506e8e02c5a6f7824fec5e72b0 Mon Sep 17 00:00:00 2001 From: Chris Harris Date: Mon, 21 Oct 2024 09:54:21 +0100 Subject: [PATCH 5/6] Revert "cbt: creating a common base class for fio subclasses" Reverting the change to fio.py as it was made in error This reverts commit 3c75e3d7ef034bce4bf4c07db5f883cf9c6e12e0. Signed-off-by: Chris Harris --- benchmark/fio.py | 4 ++-- benchmark/fio_common.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmark/fio.py b/benchmark/fio.py index 37c265bf..ef73b9b5 100644 --- a/benchmark/fio.py +++ b/benchmark/fio.py @@ -64,10 +64,10 @@ def create_endpoints(self) -> None: if self._endpoint_type == "rbd" and self._cli_options["ioengine"] != "rbd": log.warning("rbd endpoints must use the librbd fio engine! Setting ioengine=rbd") - self.ioengine = "rbd" + self._cli_options["ioengine"] = "rbd" if self._endpoint_type == "rbd" and self._cli_options["direct"] != "1": log.warning("rbd endpoints must use O_DIRECT. Setting direct=1") - self.direct = "1" + self._cli_options["direct"] = "1" def fio_command_extra(self, endpoint_number: int) -> str: command: str = "" diff --git a/benchmark/fio_common.py b/benchmark/fio_common.py index 96baf4dc..b1ecc9fb 100644 --- a/benchmark/fio_common.py +++ b/benchmark/fio_common.py @@ -27,7 +27,7 @@ def __init__(self, archive_dir: str, cluster: Cluster, configuration: Dict[str, super().__init__(archive_dir, cluster, configuration) # type: ignore [no-untyped-call] self._cluster: Cluster = cluster - self._output_directory: str = archive_dir + self._output_directory: str = self.archive_dir self._defaults: Dict[str, str] From c7666d2b6991dbccbe9ac3ded42e8e8c472769d8 Mon Sep 17 00:00:00 2001 From: Chris Harris Date: Mon, 21 Oct 2024 09:54:21 +0100 Subject: [PATCH 6/6] Revert "cbt: creating a common base class for fio subclasses" Reverting the change to fio.py as it was made in error This reverts commit 3c75e3d7ef034bce4bf4c07db5f883cf9c6e12e0. Signed-off-by: Chris Harris --- tests/test_bm_cephtestrados.py | 2 +- tests/test_bm_fio.py | 2 +- tests/test_bm_getput.py | 2 +- tests/test_bm_hsbench.py | 2 +- tests/test_bm_kvmrbdfio.py | 2 +- tests/test_bm_librbdfio.py | 2 +- tests/test_bm_nullbench.py | 2 +- tests/test_bm_radosbench.py | 2 +- tests/test_bm_rawfio.py | 2 +- tests/test_bm_rbdfio.py | 2 +- tools/baseline.json | 4 ++-- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/test_bm_cephtestrados.py b/tests/test_bm_cephtestrados.py index 7aafacb9..8c380739 100644 --- a/tests/test_bm_cephtestrados.py +++ b/tests/test_bm_cephtestrados.py @@ -16,7 +16,7 @@ class TestBenchmarkcephtestrados(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' + bl_md5 = 'aa42ab3c2da0e01ecec18add853f7d79' md5_returned = None @classmethod diff --git a/tests/test_bm_fio.py b/tests/test_bm_fio.py index bf7dbeee..7b96474e 100644 --- a/tests/test_bm_fio.py +++ b/tests/test_bm_fio.py @@ -16,7 +16,7 @@ class TestBenchmarkfio(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' + bl_md5 = 'aa42ab3c2da0e01ecec18add853f7d79' md5_returned = None @classmethod diff --git a/tests/test_bm_getput.py b/tests/test_bm_getput.py index 90ca27a8..1b16baea 100644 --- a/tests/test_bm_getput.py +++ b/tests/test_bm_getput.py @@ -16,7 +16,7 @@ class TestBenchmarkgetput(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' + bl_md5 = 'aa42ab3c2da0e01ecec18add853f7d79' md5_returned = None @classmethod diff --git a/tests/test_bm_hsbench.py b/tests/test_bm_hsbench.py index b47f73b5..8aa125ee 100644 --- a/tests/test_bm_hsbench.py +++ b/tests/test_bm_hsbench.py @@ -16,7 +16,7 @@ class TestBenchmarkhsbench(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' + bl_md5 = 'aa42ab3c2da0e01ecec18add853f7d79' md5_returned = None @classmethod diff --git a/tests/test_bm_kvmrbdfio.py b/tests/test_bm_kvmrbdfio.py index 91a4038e..0e6cbacb 100644 --- a/tests/test_bm_kvmrbdfio.py +++ b/tests/test_bm_kvmrbdfio.py @@ -16,7 +16,7 @@ class TestBenchmarkkvmrbdfio(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' + bl_md5 = 'aa42ab3c2da0e01ecec18add853f7d79' md5_returned = None @classmethod diff --git a/tests/test_bm_librbdfio.py b/tests/test_bm_librbdfio.py index edb0585e..703310b5 100644 --- a/tests/test_bm_librbdfio.py +++ b/tests/test_bm_librbdfio.py @@ -16,7 +16,7 @@ class TestBenchmarklibrbdfio(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' + bl_md5 = 'aa42ab3c2da0e01ecec18add853f7d79' md5_returned = None @classmethod diff --git a/tests/test_bm_nullbench.py b/tests/test_bm_nullbench.py index 0e1a52cc..d5e6a649 100644 --- a/tests/test_bm_nullbench.py +++ b/tests/test_bm_nullbench.py @@ -16,7 +16,7 @@ class TestBenchmarknullbench(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' + bl_md5 = 'aa42ab3c2da0e01ecec18add853f7d79' md5_returned = None @classmethod diff --git a/tests/test_bm_radosbench.py b/tests/test_bm_radosbench.py index d78dbdd1..fb1862a8 100644 --- a/tests/test_bm_radosbench.py +++ b/tests/test_bm_radosbench.py @@ -16,7 +16,7 @@ class TestBenchmarkradosbench(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' + bl_md5 = 'aa42ab3c2da0e01ecec18add853f7d79' md5_returned = None @classmethod diff --git a/tests/test_bm_rawfio.py b/tests/test_bm_rawfio.py index ba316714..848cfd19 100644 --- a/tests/test_bm_rawfio.py +++ b/tests/test_bm_rawfio.py @@ -16,7 +16,7 @@ class TestBenchmarkrawfio(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' + bl_md5 = 'aa42ab3c2da0e01ecec18add853f7d79' md5_returned = None @classmethod diff --git a/tests/test_bm_rbdfio.py b/tests/test_bm_rbdfio.py index 80c2dfd0..b0f25e4d 100644 --- a/tests/test_bm_rbdfio.py +++ b/tests/test_bm_rbdfio.py @@ -16,7 +16,7 @@ class TestBenchmarkrbdfio(unittest.TestCase): cl_name = "tools/invariant.yaml" bl_name = "tools/baseline.json" bl_json = {} - bl_md5 = '30f2e8cc8a8aca6538d818919834ef27' + bl_md5 = 'aa42ab3c2da0e01ecec18add853f7d79' md5_returned = None @classmethod diff --git a/tools/baseline.json b/tools/baseline.json index 545ffdf9..4771db05 100644 --- a/tools/baseline.json +++ b/tools/baseline.json @@ -286,7 +286,7 @@ }, "_endpoint_type": "", "_endpoints": [], - "_output_directory": "/tmp", + "_output_directory": "/tmp/results/00000000/id-83a653b5", "_procs_per_endpoint": 1, "_recovery_test_type": "blocking", "acceptable": {}, @@ -398,7 +398,7 @@ "use_existing": false, "version_compat": "" }, - "cmd_path": "None", + "cmd_path": "/usr/bin/fio", "cmd_path_full": "", "config": { "iteration": 0