perfkitbenchmarker/linux_benchmarks/specsfs2014_benchmark.py (231 lines of code) (raw):

# Copyright 2016 PerfKitBenchmarker Authors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Runs SPEC SFS 2014. SPEC SFS 2014 homepage: http://www.spec.org/sfs2014/ In order to run this benchmark copy your 'SPECsfs2014_SP2.iso' and 'netmist_license_key' files into the data/ directory. TODO: This benchmark should be decoupled from Gluster and allow users to run against any file server solution. In addition, Gluster should eventually become a "disk type" so that any benchmark that runs against a filesystem can run against Gluster. """ import posixpath import xml.etree.ElementTree from absl import flags from perfkitbenchmarker import background_tasks from perfkitbenchmarker import configs from perfkitbenchmarker import data from perfkitbenchmarker import flag_util from perfkitbenchmarker import sample from perfkitbenchmarker import vm_util from perfkitbenchmarker.linux_packages import gluster FLAGS = flags.FLAGS BENCHMARKS = ['VDI', 'DATABASE', 'SWBUILD', 'VDA', 'EDA'] flags.DEFINE_string( 'specsfs2014_config', None, 'This flag can be used to specify an alternate SPEC config file to use. ' 'If this option is specified, none of the other benchmark specific flags ' 'which operate on the config file will be used (since the default config ' 'file will be replaced by this one).', ) flags.DEFINE_list( 'specsfs2014_benchmarks', BENCHMARKS, 'The SPEC SFS 2014 benchmarks to run.' ) flags.register_validator( 'specsfs2014_benchmarks', lambda benchmarks: benchmarks and set(benchmarks).issubset(BENCHMARKS), 'Invalid benchmarks list. specsfs2014_benchmarks must be a subset of ' + ', '.join(BENCHMARKS), ) flag_util.DEFINE_integerlist( 'specsfs2014_load', [1], 'The starting load in units of SPEC "business metrics". The meaning of ' 'business metric varies depending on the SPEC benchmark (e.g. VDI has ' 'load measured in virtual desktops).', module_name=__name__, ) flags.DEFINE_integer( 'specsfs2014_incr_load', 1, 'The amount to increment "load" by for each run.', lower_bound=1, ) flags.DEFINE_integer( 'specsfs2014_num_runs', 1, 'The total number of SPEC runs. The load for the nth run is ' '"load" + n * "specsfs_incr_load".', lower_bound=1, ) flags.DEFINE_boolean( 'specsfs2014_auto_mode', False, 'If True, automatically find the max passing score for each benchmark. ' 'This ignores other flags such as specsfs2014_load, specsfs2014_incr_load, ' 'and specsfs2014_num_runs.', ) BENCHMARK_NAME = 'specsfs2014' BENCHMARK_CONFIG = """ specsfs2014: description: > Run SPEC SFS 2014. For a full explanation of all benchmark modes see http://www.spec.org/sfs2014/. In order to run this benchmark copy your 'SPECsfs2014_SP2.iso' and 'netmist_license_key' files into the data/ directory. vm_groups: gluster_servers: vm_spec: *default_dual_core disk_spec: *default_500_gb vm_count: 3 clients: vm_spec: *default_dual_core vm_count: null """ _SPEC_SFS_2014_ISO = 'SPECsfs2014_SP2.iso' _SPEC_SFS_2014_LICENSE = 'netmist_license_key' _SPEC_DIR = 'spec' _SPEC_CONFIG = 'sfs_rc' _VOLUME_NAME = 'gv0' _MOUNT_POINT = '/scratch' _MOUNTPOINTS_FILE = 'mountpoints.txt' _PUBLISHED_METRICS = frozenset([ 'achieved rate', 'average latency', 'overall throughput', 'read throughput', 'write throughput', ]) _METADATA_KEYS = frozenset([ 'op rate', 'run time', 'processes per client', 'file size', 'client data set size', 'starting data set size', 'initial file space', 'maximum file space', ]) BENCHMARK_DATA = { _SPEC_SFS_2014_ISO: ( '666d3f79e9184211736c32c825edb007c6a5ad88eeceb3c99aa01acf733c6fb3' ) } def GetConfig(user_config): return configs.LoadConfig(BENCHMARK_CONFIG, user_config, BENCHMARK_NAME) def CheckPrerequisites(unused_benchmark_config): """Verifies that the required resources are present. Raises: perfkitbenchmarker.data.ResourceNotFound: On missing resource. """ data.ResourcePath(_SPEC_SFS_2014_LICENSE) if FLAGS.specsfs2014_config: data.ResourcePath(FLAGS.specsfs2014_config) def _PrepareSpec(vm): """Prepares a SPEC client by copying SPEC to the VM.""" mount_dir = 'spec_mnt' vm.RemoteCommand('mkdir %s' % mount_dir) vm.RemoteCommand('mkdir %s' % _SPEC_DIR) vm.InstallPreprovisionedBenchmarkData( 'specsfs2014', [_SPEC_SFS_2014_ISO], '~/' ) vm.PushFile(data.ResourcePath(_SPEC_SFS_2014_LICENSE), _SPEC_DIR) vm.RemoteCommand( 'sudo mount -t iso9660 -o loop %s %s' % (_SPEC_SFS_2014_ISO, mount_dir) ) vm.RemoteCommand('cp -r %s/* %s' % (mount_dir, _SPEC_DIR)) vm.RemoteCommand('sudo umount {0} && sudo rm -rf {0}'.format(mount_dir)) def _ConfigureSpec( prime_client, clients, benchmark, load=None, num_runs=None, incr_load=None ): """Configures SPEC SFS 2014 on the prime client. This function modifies the default configuration file (sfs_rc) which can be found either in the SPEC SFS 2014 user guide or within the iso. It also creates a file containing the client mountpoints so that SPEC can run in a distributed manner. Args: prime_client: The VM from which SPEC will be controlled. clients: A list of SPEC client VMs (including the prime_client). benchmark: The sub-benchmark to run. load: List of ints. The LOAD parameter to SPECSFS. num_runs: The NUM_RUNS parameter to SPECSFS. incr_load: The INCR_LOAD parameter to SPECSFS. """ config_path = posixpath.join(_SPEC_DIR, _SPEC_CONFIG) prime_client.RemoteCommand('sudo cp {0}.bak {0}'.format(config_path)) stdout, _ = prime_client.RemoteCommand('pwd') exec_path = posixpath.join( stdout.strip(), _SPEC_DIR, 'binaries', 'linux', 'x86_64', 'netmist' ) load = load or FLAGS.specsfs2014_load num_runs = num_runs or FLAGS.specsfs2014_num_runs incr_load = incr_load or FLAGS.specsfs2014_incr_load configuration_overrides = { 'USER': prime_client.user_name, 'EXEC_PATH': exec_path.replace('/', r'\/'), 'CLIENT_MOUNTPOINTS': _MOUNTPOINTS_FILE, 'BENCHMARK': benchmark, 'LOAD': ' '.join([str(x) for x in load]), 'NUM_RUNS': num_runs, 'INCR_LOAD': incr_load, 'WARMUP_TIME': 60, } # Any special characters in the overrides dictionary should be escaped so # that they don't interfere with sed. sed_expressions = ' '.join([ '-e "s/{0}=.*/{0}={1}/"'.format(k, v) for k, v in configuration_overrides.items() ]) sed_cmd = 'sudo sed -i {} {}'.format(sed_expressions, config_path) prime_client.RemoteCommand(sed_cmd) mount_points = [f'{client.internal_ip} {_MOUNT_POINT}' for client in clients] vm_util.CreateRemoteFile( prime_client, '\n'.join(mount_points), posixpath.join(_SPEC_DIR, _MOUNTPOINTS_FILE), ) def Prepare(benchmark_spec): """Install SPEC SFS 2014. Args: benchmark_spec: The benchmark specification. Contains all data that is required to run the benchmark. """ gluster_servers = benchmark_spec.vm_groups['gluster_servers'] clients = benchmark_spec.vm_groups['clients'] prime_client = clients[0] # Set up Gluster if gluster_servers: gluster.ConfigureServers(gluster_servers, _VOLUME_NAME) args = [ ((client, gluster_servers[0], _VOLUME_NAME, _MOUNT_POINT), {}) for client in clients ] background_tasks.RunThreaded(gluster.MountGluster, args) # Set up SPEC background_tasks.RunThreaded(_PrepareSpec, clients) # Create a backup of the config file. prime_client.RemoteCommand( 'cp {0} {0}.bak'.format(posixpath.join(_SPEC_DIR, _SPEC_CONFIG)) ) prime_client.AuthenticateVm() # Make sure any Static VMs are setup correctly. for client in clients: prime_client.TestAuthentication(client) def _ParseSpecSfsOutput(output, extra_metadata=None): """Returns samples generated from the output of SPEC SFS 2014. Args: output: The stdout from running SPEC. extra_metadata: Dict of metadata to include with results. Returns: List of sample.Sample objects. This parses the contents of the results xml file and creates samples from the achieved operation rate, latency, and throughput metrics. The samples are annotated with metadata collected from the xml file including information about the benchmark name, the load, and data size. """ root = xml.etree.ElementTree.fromstring(output) samples = [] for run in root.findall('run'): metadata = { 'benchmark': run.find('benchmark').attrib['name'], 'business_metric': run.find('business_metric').text, } if extra_metadata: metadata.update(extra_metadata) for key in _METADATA_KEYS: element = run.find('metric[@name="%s"]' % key) units = element.attrib.get('units') label = '%s (%s)' % (key, units) if units else key metadata[label] = element.text if run.find('valid_run').text == 'INVALID_RUN': metadata['valid_run'] = False else: metadata['valid_run'] = True for metric in run.findall('metric'): name = metric.attrib['name'] if name in _PUBLISHED_METRICS: samples.append( sample.Sample( name, float(metric.text), metric.attrib.get('units', ''), metadata, ) ) return samples def _RunSpecSfs(benchmark_spec): """Run SPEC SFS 2014 once. Args: benchmark_spec: The benchmark specification. Contains all data that is required to run the benchmark. Returns: A list of sample.Sample objects. """ prime_client = benchmark_spec.vm_groups['clients'][0] run_cmd = 'cd {} && python3 sfsmanager -r sfs_rc {}'.format( _SPEC_DIR, '-a' if FLAGS.specsfs2014_auto_mode else '' ) prime_client.RobustRemoteCommand(run_cmd, ignore_failure=True) results_file = posixpath.join(_SPEC_DIR, 'results', 'sfssum_sfs2014_SP2.xml') output, _ = prime_client.RemoteCommand('cat %s' % results_file) if benchmark_spec.vm_groups['gluster_servers']: gluster_metadata = { 'gluster_stripes': FLAGS.gluster_stripes, 'gluster_replicas': FLAGS.gluster_replicas, } else: gluster_metadata = {} return _ParseSpecSfsOutput(output, extra_metadata=gluster_metadata) def Run(benchmark_spec): """Run SPEC SFS 2014 for each configuration. Args: benchmark_spec: The benchmark specification. Contains all data that is required to run the benchmark. Returns: A list of sample.Sample objects. """ clients = benchmark_spec.vm_groups['clients'] prime_client = clients[0] results = [] if FLAGS.specsfs2014_config: prime_client.PushFile( data.ResourcePath(FLAGS.specsfs2014_config), posixpath.join(_SPEC_DIR, _SPEC_CONFIG), ) results += _RunSpecSfs(benchmark_spec) else: for benchmark in FLAGS.specsfs2014_benchmarks: _ConfigureSpec(prime_client, clients, benchmark) results += _RunSpecSfs(benchmark_spec) return results def Cleanup(benchmark_spec): """Cleanup SPEC SFS 2014. Args: benchmark_spec: The benchmark specification. Contains all data that is required to run the benchmark. """ clients = benchmark_spec.vm_groups['clients'] gluster_servers = benchmark_spec.vm_groups['gluster_servers'] for client in clients: client.RemoteCommand('sudo umount %s' % _MOUNT_POINT) client.RemoteCommand( 'rm %s && sudo rm -rf %s' % (_SPEC_SFS_2014_ISO, _SPEC_DIR) ) if gluster_servers: gluster.DeleteVolume(gluster_servers[0], _VOLUME_NAME)