perfkitbenchmarker/linux_benchmarks/specjbb2015_benchmark.py (351 lines of code) (raw):

# Copyright 2024 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 the Spec JBB 2015 benchmark https://www.spec.org/jbb2015/. User guide: https://www.spec.org/jbb2015/docs/userguide.pdf. """ import re from absl import flags from perfkitbenchmarker import configs from perfkitbenchmarker import errors from perfkitbenchmarker import flag_util from perfkitbenchmarker import sample from perfkitbenchmarker import vm_util from perfkitbenchmarker.linux_packages import numactl from perfkitbenchmarker.linux_packages import openjdk from perfkitbenchmarker.linux_packages import openjdk_neoverse from perfkitbenchmarker.linux_packages import specjbb BENCHMARK_NAME = 'specjbb2015' BENCHMARK_CONFIG = """ specjbb2015: description: Run specjbb2015 vm_groups: default: vm_spec: *default_dual_core disk_spec: *default_50_gb flags: openjdk_version: 21 enable_transparent_hugepages: True """ FLAGS = flags.FLAGS _FOUR_HOURS = 60 * 60 * 4 _DEFAULT_JVM_ARGS = '' _DEFAULT_JVM_CONT_TXI_ARGS = ( '-Xms2g -Xmx2g -Xmn1536m -XX:+AlwaysPreTouch -XX:ParallelGCThreads=2' ) _DEFAULT_COMPOSITE_MEMORY_RATIO = 0.8 _DEFAULT_WORKERS_RATIO = 1.0 _DEFAULT_NUM_GROUPS = 1 _DEFAULT_MEMORY_RATIO = 0.75 _SPEC_JBB_2015_ISO = specjbb.SPEC_JBB_2015_ISO _SPEC_DIR = specjbb.SPEC_DIR _LOG_FILE = '~/specjbb2015.log' _JAR_FILE = 'specjbb2015.jar' _PROPS_FILE = 'config/specjbb2015.props' BENCHMARK_DATA = { _SPEC_JBB_2015_ISO: ( '524bc1588a579ddf35cfada5e07a408c78b5939e72ee5f02b05422d5c0d214bd' ) } BACKEND_MODE = 'backend' MULTIJVM_MODE = 'MultiJVM' COMPOSITE_MODE = 'COMPOSITE' MULTICONTROLLER_MODE = 'multicontroller' TXINJECTOR_MODE = 'txinjector' NEW_MAX_RATIO = 0.94 # Taken from customer script flags.DEFINE_float( 'specjbb_workers_ratio', _DEFAULT_WORKERS_RATIO, 'A number indicating number of workers per vCPU per group.', ) flags.DEFINE_enum( 'specjbb_run_mode', MULTIJVM_MODE, [MULTIJVM_MODE, COMPOSITE_MODE], 'String representing run mode. COMPOSITE or MultiJVM.', ) flags.DEFINE_boolean( 'specjbb_auto_groups', True, 'Used in MultiJVM. If true, sets groups equal to NUMA nodes and overrides ' 'specjbb_num_groups.', ) flags.DEFINE_integer( 'specjbb_num_groups', _DEFAULT_NUM_GROUPS, 'Used in MultiJVM, number of groups.', ) flags.DEFINE_float( 'specjbb_memory_ratio', _DEFAULT_MEMORY_RATIO, 'Used in MultiJVM, is overridden by specjbb_ram_mb_per_core. ' 'This value determines the total memory usage of the JVM backends by ' "multplying with the VM's memory. For example, a memory ratio of 0.75 " 'and a machine with 64 GB memory and 2 backend groups means that each ' 'backend will have 24 GB memory allotted.', ) flags.DEFINE_integer( 'specjbb_ram_mb_per_core', None, 'Used in MultiJVM. Setting this to 1500 means that the total memory usage ' 'of the JVM backends will be num_cpus * 1.5 GB. This is useful for ' 'obtaining a comparable performance number between machines with ' 'different memory ratios, e.g. 1 vCPU : 2 GB and 1 vCPU : 8 GB, or ' 'AWS EC2 C/M/R and GCE highcpu/standard/highmem. ' 'Overrides specjbb_memory_ratio if set', ) flags.DEFINE_bool( 'specjbb_numa_aware', True, 'Whether to have MultiJVM backends pinned to specific NUMA nodes.', ) flags.DEFINE_bool( 'build_openjdk_neoverse', False, 'Whether to build OpenJDK optimized for ARM Neoverse.' 'Requires Ubuntu 1804 and OpenJDK 11.', ) flag_util.DEFINE_integerlist( 'specjbb_multijvm_nodes', None, 'By default specjbb jvm groups are bound to all numa nodes in ' 'host in a round robin way. When specjbb_multijvm_nodes is specified, ' 'specjbb jvm groups are bound to the specified numa nodes in a ' 'round robin way. For example, if user want to bind 4 specjbb ' 'jvm groups to numa node 0 and 1 in the following order: 1, 0, ' '1, 0, user can specify the specjbb_multijvm_nodes flag as following:' '--specjbb_multijvm_nodes=1,0', ) flags.DEFINE_integer( 'specjbb_file_descriptors_limit', 64 * 1024, 'Set FD limit for specjbb backend java processes. Specjbb ' 'background processes tends to create a lot of network connections. ' 'It could fail if FD limit is too low. Please search for "Too many ' 'open files" in the web page for detailed info: ' 'http://spec.org/jbb2015/docs/knownissues.html' 'Please be careful when setting this flag. its value should be ' 'smaller than sysctl flag fs.nr_open. Benchmark will fail if this ' 'flag is set to a value bigger than fs.nr_open.', lower_bound=0, ) flags.DEFINE_integer( 'specjbb_connection_pool_size', 256, 'User can use this flag to set connection pool size for all specjbb ' 'agents. Please refer to section 16.1 of specjbb userguide:' 'https://www.spec.org/jbb2015/docs/userguide.pdf', lower_bound=0, ) def GetConfig(user_config): return configs.LoadConfig(BENCHMARK_CONFIG, user_config, BENCHMARK_NAME) def Prepare(benchmark_spec): """Install Specjbb2015 on the target vm. Args: benchmark_spec: The benchmark specification. """ vm = benchmark_spec.vms[0] vm.Install('specjbb') vm.Install('openjdk') # Used on m6g (AWS Graviton 2) machines for optimal performance if FLAGS.build_openjdk_neoverse: openjdk_neoverse.InstallNeoverseCompiledOpenJDK( vm, openjdk.OPENJDK_VERSION.value ) vm.InstallPackages('numactl') # swap only if necessary; free local node memory and avoid remote memory; # reset caches; set stack size to unlimited # Also consider setting enable_transparent_hugepages flag to true cmd = ( 'echo 1 | sudo tee /proc/sys/vm/swappiness && ' 'echo 1 | sudo tee /proc/sys/vm/zone_reclaim_mode && ' 'sync ; echo 3 | sudo tee /proc/sys/vm/drop_caches && ' 'ulimit -s unlimited' ) vm.RemoteCommand(cmd) def _GetNumGroups(vm): """Returns integer number of backend groups to use for MultiJVM.""" if FLAGS.specjbb_auto_groups: return len(numactl.GetNuma(vm)) return FLAGS.specjbb_num_groups def _MaxHeapMB(vm, mode): """Returns max heap size in MB as an int.""" if mode == BACKEND_MODE: if FLAGS.specjbb_ram_mb_per_core: # overrides --specjbb_memory_ratio if set return ( int(vm.NumCpusForBenchmark() // _GetNumGroups(vm)) * FLAGS.specjbb_ram_mb_per_core ) else: return int( vm.total_memory_kb * FLAGS.specjbb_memory_ratio // _GetNumGroups(vm) // 1024 ) elif mode == COMPOSITE_MODE: return int(vm.total_memory_kb * _DEFAULT_COMPOSITE_MEMORY_RATIO / 1024) def _JVMArgs(vm, mode): """Determines JVM args and returns them as a string.""" if mode in (TXINJECTOR_MODE, MULTICONTROLLER_MODE): return _DEFAULT_JVM_CONT_TXI_ARGS gc_size = int(vm.NumCpusForBenchmark() / _GetNumGroups(vm)) jvm_backend_gc_arg = f'-XX:ParallelGCThreads={gc_size}' # Determine max/new heap arguments. max per group = 3/8 * vCPU GB. jvm_backend_mem_arg = '-Xms{max_}m -Xmx{max_}m -Xmn{new_}m '.format( max_=_MaxHeapMB(vm, BACKEND_MODE), new_=int(_MaxHeapMB(vm, BACKEND_MODE) * NEW_MAX_RATIO), ) jvm_composite_mem_arg = '-Xms{max_}m -Xmx{max_}m -Xmn{new_}m '.format( max_=_MaxHeapMB(vm, COMPOSITE_MODE), new_=int(_MaxHeapMB(vm, COMPOSITE_MODE) * NEW_MAX_RATIO), ) if mode == BACKEND_MODE: return ' '.join( [jvm_backend_gc_arg, jvm_backend_mem_arg, _DEFAULT_JVM_ARGS] ) elif mode == COMPOSITE_MODE: return ' '.join([jvm_composite_mem_arg, _DEFAULT_JVM_ARGS]) else: raise errors.Benchmarks.RunError('Invalid specjbb mode!') def _SpecArgs(vm, mode): """Determines Spec args and returns them as a string.""" num_workers = ( vm.NumCpusForBenchmark() * FLAGS.specjbb_workers_ratio / _GetNumGroups(vm) ) spec_num_workers_arg = f' -Dspecjbb.forkjoin.workers={int(num_workers)}' spec_num_groups_arg = f' -Dspecjbb.group.count={_GetNumGroups(vm)}' spec_rt_curve_arg = '-Dspecjbb.controller.rtcurve.warmup.step=0.5' spec_mr_arg = f'-Dspecjbb.mapreducer.pool.size={_GetNumGroups(vm) * 2}' spec_connect_pool_size_arg = f'-Dspecjbb.comm.connect.client.pool.size={FLAGS.specjbb_connection_pool_size}' if mode == TXINJECTOR_MODE: return '' elif mode == MULTICONTROLLER_MODE: return ' '.join([ spec_rt_curve_arg, spec_mr_arg, spec_num_workers_arg, spec_num_groups_arg, spec_connect_pool_size_arg, ]) elif mode == BACKEND_MODE: return '' elif mode == COMPOSITE_MODE: return spec_num_workers_arg else: raise errors.Benchmarks.RunError('Invalid specjbb mode!') def _CollectSLAMetrics(vm): """Gathers SLA metrics from specjbb output files.""" # The log file reports the location of the report.html file. Since date/time # are part of the report filename, we must determine it at runtime. The .raw # file is easier to parse than the .html file, so parse that instead. grep_stdout, _ = vm.RemoteCommand( "grep -oE '[^ ]+html' ~/specjbb2015.log", ignore_failure=True ) file_prefix = grep_stdout.split('.')[0] filename = f'spec/{file_prefix}.raw' cmd = f'cat {filename} | grep SLA-' sla_stdout, _ = vm.RemoteCommand(cmd, ignore_failure=True) return sla_stdout def ParseJbbOutput(stdout, metadata): """Generates samples from the RUN RESULT string.""" samples = [] regex = re.compile( r'RUN\sRESULT:.*?max\-jOPS\s=\s(?P<maxjops>\d+),\s+' r'critical-jOPS\s=\s(?P<crjops>\d+)' ) jops = regex.search(stdout) if jops: samples.append( sample.Sample('max_jOPS', int(jops.group('maxjops')), 'jops', metadata) ) samples.append( sample.Sample( 'critical_jOPS', int(jops.group('crjops')), 'jops', metadata ) ) else: raise errors.Benchmarks.RunError('No specjbb results found!') return samples def _RunBackgroundNumaPinnedCommand(vm, cmd_list, node_id): """In a shell session, cd and run a numa pinned background command. A user may opt to not NUMA pin with the specjbb_numa_aware flag. Args: vm: VM to run the command on cmd_list: list of commands to be joined together node_id: NUMA node to pin command on. """ fd_limit_cmd = f'ulimit -n {FLAGS.specjbb_file_descriptors_limit}' if FLAGS.specjbb_numa_aware: # Persist the nohup command past the ssh session, and numa pin. # "sh -c 'cd /whereever; nohup ./whatever > /dev/null 2>&1 &'" # "numa --cpunodebind 0 --membind 0 cmd" cmd = ( "sh -c '{fd_limit_cmd} && cd {dir} && nohup numactl " '--cpunodebind {node_id} ' "--membind {node_id} {cmd} 2>&1 &'" ).format( fd_limit_cmd=fd_limit_cmd, node_id=node_id, dir=_SPEC_DIR, cmd=' '.join(cmd_list), ) else: cmd = ("sh -c '{fd_limit_cmd} && cd {dir} && nohup {cmd} 2>&1 &'").format( fd_limit_cmd=fd_limit_cmd, dir=_SPEC_DIR, cmd=' '.join(cmd_list) ) vm.RemoteCommand(cmd) def Run(benchmark_spec): """Runs Specjbb2015 on the target vm. Args: benchmark_spec: The benchmark specification. Returns: A list of sample.Sample objects with the performance results. Raises: Benchmarks.RunError: If no results are found. """ vm = benchmark_spec.vms[0] if FLAGS.specjbb_run_mode == MULTIJVM_MODE: if FLAGS.specjbb_multijvm_nodes: node_ids = FLAGS.specjbb_multijvm_nodes else: numa_map = numactl.GetNuma(vm) node_ids = list(numa_map.keys()) # Run backends and txinjectors as background commands # java -jar specjbb2015.jar -m txinjector -G GRP1 -J JVM1 > grp1jvm1.log # java -jar specjbb2015.jar -m backend -G GRP1 -J JVM1 > grp1jvm2.log for group in range(1, _GetNumGroups(vm) + 1): node_id = node_ids[(group - 1) % len(node_ids)] txinjector_cmd = [ 'java', _JVMArgs(vm, TXINJECTOR_MODE), '-jar', _JAR_FILE, '-m', TXINJECTOR_MODE, '-G', f'GRP{group}', '-J', 'JVM1', '>', f'grp{group}jvm1.log', ] _RunBackgroundNumaPinnedCommand(vm, txinjector_cmd, node_id) backend_cmd = [ 'java', _JVMArgs(vm, BACKEND_MODE), '-jar', _JAR_FILE, '-m', BACKEND_MODE, '-G', f'GRP{group}', '-J', 'JVM2', '>', f'grp{group}jvm2.log', ] _RunBackgroundNumaPinnedCommand(vm, backend_cmd, node_id) # Run multicontroller as a foreground command controller_cmd = [ 'java', _JVMArgs(vm, MULTICONTROLLER_MODE), _SpecArgs(vm, MULTICONTROLLER_MODE), '-jar', _JAR_FILE, '-m', MULTICONTROLLER_MODE, '-p', _PROPS_FILE, ] run_cmd = ('cd {dir} && {cmd} 2>&1 | tee {log_file}').format( dir=_SPEC_DIR, cmd=' '.join(controller_cmd), log_file=_LOG_FILE ) stdout, _ = vm.RobustRemoteCommand(run_cmd) max_heap_size_gb = _MaxHeapMB(vm, BACKEND_MODE) / 1000.0 # for metadata else: # COMPOSITE mode run_cmd = [ 'java', _JVMArgs(vm, COMPOSITE_MODE), _SpecArgs(vm, COMPOSITE_MODE), '-jar', _JAR_FILE, '-m', COMPOSITE_MODE, '-p', _PROPS_FILE, ] cmd = ('cd {dir} && {cmd} 2>&1 | tee {log_file}').format( dir=_SPEC_DIR, cmd=' '.join(run_cmd), log_file=_LOG_FILE ) stdout, _ = vm.RemoteCommand(cmd, timeout=_FOUR_HOURS) max_heap_size_gb = _MaxHeapMB(vm, COMPOSITE_MODE) / 1000.0 # for metadata jdk_metadata = FLAGS.openjdk_version if FLAGS.build_openjdk_neoverse: jdk_metadata += '_neoverse_optimized' metadata = { 'OpenJDK_version': jdk_metadata, 'iso_hash': BENCHMARK_DATA[_SPEC_JBB_2015_ISO], 'num_workers': int( vm.NumCpusForBenchmark() * FLAGS.specjbb_workers_ratio / _GetNumGroups(vm) ), 'num_groups': _GetNumGroups(vm), 'worker_ratio': FLAGS.specjbb_workers_ratio, 'memory_ratio': FLAGS.specjbb_memory_ratio, 'ram_mb_per_core': FLAGS.specjbb_ram_mb_per_core, 'max_heap_size': f'{max_heap_size_gb}g', 'specjbb_mode': FLAGS.specjbb_run_mode, 'sla_metrics': _CollectSLAMetrics(vm), 'specjbb_numa_aware': FLAGS.specjbb_numa_aware, } vm.PullFile(vm_util.GetTempDir(), _LOG_FILE) return ParseJbbOutput(stdout, metadata) def Cleanup(benchmark_spec): """Cleanup Specjbb2015 on the target vm. Args: benchmark_spec: The benchmark specification. """ vm = benchmark_spec.vms[0] specjbb.Uninstall(vm)