perfkitbenchmarker/linux_benchmarks/hpcc_benchmark.py (474 lines of code) (raw):

# Copyright 2020 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 HPC Challenge. Homepage: http://icl.cs.utk.edu/hpcc/ Most of the configuration of the HPC-Challenge revolves around HPL, the rest of the HPCC piggybacks upon the HPL configuration. Homepage: http://www.netlib.org/benchmark/hpl/ HPL requires a BLAS library (Basic Linear Algebra Subprograms) OpenBlas: http://www.openblas.net/ Intel MKL: https://software.intel.com/en-us/mkl HPL also requires a MPI (Message Passing Interface) Library OpenMPI: http://www.open-mpi.org/ MPI needs to be configured: Configuring MPI: http://techtinkering.com/2009/12/02/setting-up-a-beowulf-cluster-using-open-mpi-on-linux/ Once HPL is built the configuration file must be created: Configuring HPL.dat: http://www.advancedclustering.com/faq/how-do-i-tune-my-hpldat-file.html http://www.netlib.org/benchmark/hpl/faqs.html """ import dataclasses import inspect import logging import math import re from typing import Any, Dict, List, Tuple from absl import flags from perfkitbenchmarker import background_tasks from perfkitbenchmarker import benchmark_spec as bm_spec from perfkitbenchmarker import configs from perfkitbenchmarker import data from perfkitbenchmarker import errors from perfkitbenchmarker import hpc_util from perfkitbenchmarker import linux_virtual_machine as linux_vm from perfkitbenchmarker import regex_util from perfkitbenchmarker import sample from perfkitbenchmarker import vm_util from perfkitbenchmarker.linux_packages import hpcc from perfkitbenchmarker.linux_packages import intel_repo from perfkitbenchmarker.linux_packages import intelmpi from perfkitbenchmarker.linux_packages import mkl from perfkitbenchmarker.linux_packages import numactl from perfkitbenchmarker.linux_packages import openblas FLAGS = flags.FLAGS LOCAL_HPCCINF_FILE = 'hpccinf.j2' HPCCINF_FILE = 'hpccinf.txt' MACHINEFILE = 'machinefile' BLOCK_SIZE = 192 STREAM_METRICS = ['Copy', 'Scale', 'Add', 'Triad'] MKL_TGZ = 'l_mkl_2018.2.199.tgz' BENCHMARK_DATA = { # Intel MKL package downloaded from: # https://software.intel.com/en-us/mkl # In order to get "l_mkl_2018.2.199.tgz", please choose the product # "Intel Performance Libraries for Linux*", choose the version # "2018 Update 2" and choose the download option "Intel # Math Kernel Library(Intel Mkl)". MKL_TGZ: 'e28d12173bef9e615b0ded2f95f59a42b3e9ad0afa713a79f8801da2bfb31936', } # File for mpirun to run that calls ./hpcc HPCC_WRAPPER = 'hpcc_wrapper.sh' BENCHMARK_NAME = 'hpcc' BENCHMARK_CONFIG = """ hpcc: description: Runs HPCC. Specify the number of VMs with --num_vms vm_groups: default: vm_spec: *default_dual_core vm_count: null """ SECONDS_PER_HOUR = 60 * 60 @dataclasses.dataclass(frozen=True) class HpccDimensions: """Dimensions for the run. Replaces values in the data/hpccinf.txt file. For more details see http://www.netlib.org/benchmark/hpl/tuning.html . The value in quotes after the field name is the corresponding attribute name in the hpccinf.txt file. Attributes: problem_size: 'Ns': the problem size. block_size: 'NBs': number of blocks. num_rows: 'Ps': number of rows for each grid. num_columns: 'Qs': number of columns for each grid. pfacts: 'PFACTs': matrix-vector operation based factorization. nbmins: 'NBMINs': the number of columns at which to stop factorization. rfacts: 'RFACTs': type of recursive panel factorization. bcasts: 'BCASTs': methodology to broadcast the current panel. depths: 'DEPTHs': look ahread depth. swap: swapping algorithm to use. l1: 'L1': whether the upper triangle of the panel of columns should be stored in transposed form. u: 'U': whether the panel of rows U should be stored in transposed form. equilibration: whether to enable the equilibration phase. """ problem_size: int block_size: int num_rows: int num_columns: int pfacts: int nbmins: int rfacts: int bcasts: int depths: int swap: int l1: int u: int equilibration: int # Translating the --hpcc_ flags into numbers in the HPL configuration file PFACT_RFACT_MAPPING = {'left': 0, 'crout': 1, 'right': 2} BCAST_MAPPING = {'1rg': 0, '1rM': 1, '2rg': 2, '2rM': 3, 'Lng': 4, 'LnM': 5} SWAP_MAPPING = {'bin-exch': 0, 'long': 1, 'mix': 2} L1_U_MAPPING = {True: 0, False: 1} EQUILIBRATION_MAPPING = {True: 1, False: 0} flags.DEFINE_integer( 'memory_size_mb', None, 'The amount of memory in MB on each machine to use. By ' "default it will use the entire system's memory.", ) flags.DEFINE_string( 'hpcc_binary', None, 'The path of prebuilt hpcc binary to use. If not provided, ' 'this benchmark built its own using OpenBLAS.', ) flags.DEFINE_list( 'hpcc_mpi_env', [], 'Comma separated list containing environment variables ' 'to use with mpirun command. e.g. ' 'MKL_DEBUG_CPU_TYPE=7,MKL_ENABLE_INSTRUCTIONS=AVX512', ) flags.DEFINE_float( 'hpcc_timeout_hours', 4, 'The number of hours to wait for the HPCC binary to ' 'complete before timing out and assuming it failed.', ) flags.DEFINE_boolean( 'hpcc_numa_binding', False, 'If True, attempt numa binding with membind and cpunodebind.', ) # HPL.dat configuration parameters CONFIG_PROBLEM_SIZE = flags.DEFINE_integer( 'hpcc_problem_size', None, 'Size of problems to solve. Leave as None to run one single problem ' 'whose size is based on the amount of memory.', ) CONFIG_BLOCK_SIZE = flags.DEFINE_integer( 'hpcc_block_size', None, 'Block size. Left as None to be based on the amount of memory.', ) CONFIG_DIMENSIONS = flags.DEFINE_string( 'hpcc_dimensions', None, 'Number of rows and columns in the array: "1,2" is 1 row, 2 columns. ' 'Leave as None for computer to select based on number of CPUs.', ) CONFIG_PFACTS = flags.DEFINE_enum( 'hpcc_pfacts', 'right', sorted(PFACT_RFACT_MAPPING), 'What type of matrix-vector operation based factorization to use.', ) CONFIG_NBMINS = flags.DEFINE_integer( 'hpcc_nbmins', 4, 'The number of columns at which to stop panel factorization.', ) CONFIG_RFACTS = flags.DEFINE_enum( 'hpcc_rfacts', 'crout', sorted(PFACT_RFACT_MAPPING), 'The type of recursive panel factorization to use.', ) CONFIG_BCASTS = flags.DEFINE_enum( 'hpcc_bcasts', '1rM', sorted(BCAST_MAPPING), 'The broadcast methodology to use on the current panel.', ) CONFIG_DEPTHS = flags.DEFINE_integer( 'hpcc_depths', 1, 'Look ahead depth. ' '0: next panel is factorized after current completely finished. ' '1: next panel is immediately factorized after current is updated.', ) CONFIG_SWAP = flags.DEFINE_enum( 'hpcc_swap', 'mix', sorted(SWAP_MAPPING), 'Swapping algorithm to use.' ) CONFIG_L1 = flags.DEFINE_boolean( 'hpcc_l1', True, 'Whether to store the upper triangle as transposed.' ) CONFIG_U = flags.DEFINE_boolean( 'hpcc_u', True, 'Whether to store the U column as transposed.' ) CONFIG_EQUILIBRATION = flags.DEFINE_boolean( 'hpcc_equilibration', True, 'Whether to enable the equilibration phase.' ) def GetConfig(user_config: Dict[Any, Any]) -> Dict[Any, Any]: return configs.LoadConfig(BENCHMARK_CONFIG, user_config, BENCHMARK_NAME) def CheckPrerequisites(_) -> None: """Verifies that the required resources are present. Raises: perfkitbenchmarker.data.ResourceNotFound: On missing resource. NotImplementedError: On certain flag combination not currently supported. """ data.ResourcePath(LOCAL_HPCCINF_FILE) if FLAGS['hpcc_binary'].present: data.ResourcePath(FLAGS.hpcc_binary) if FLAGS.hpcc_numa_binding and FLAGS.num_vms > 1: raise errors.Setup.InvalidFlagConfigurationError( 'Numa binding with multiple hpcc vm not supported.' ) if CONFIG_DIMENSIONS.value: parts = CONFIG_DIMENSIONS.value.split(',') if len(parts) != 2: raise errors.Setup.InvalidFlagConfigurationError( 'For --hpcc_dimensions must have two values like "1,2" ' f'not "{CONFIG_DIMENSIONS.value}"' ) if not (parts[0].isnumeric() and parts[1].isnumeric()): raise errors.Setup.InvalidFlagConfigurationError( '--hpcc_dimensions must be integers like "1,2" not ' f'"{parts[0]},{parts[1]}"' ) if hpcc.USE_INTEL_COMPILED_HPL.value: if FLAGS.hpcc_benchmarks != ['HPL']: raise errors.Setup.InvalidFlagConfigurationError( 'Intel compiled HPCC can only run linpack (--hpcc_benchmarks=HPL)' ) def _CalculateHpccDimensions( num_vms: int, num_cpus: int, vm_memory_size_actual: int ) -> HpccDimensions: """Calculates the HPCC dimensions for the run.""" if FLAGS.memory_size_mb: total_memory = FLAGS.memory_size_mb * 1024 * 1024 * num_vms else: total_memory = vm_memory_size_actual * 1024 * num_vms total_cpus = num_cpus * num_vms block_size = CONFIG_BLOCK_SIZE.value or BLOCK_SIZE if CONFIG_PROBLEM_SIZE.value: problem_size = CONFIG_PROBLEM_SIZE.value else: # Finds a problem size that will fit in memory and is a multiple of the # block size. base_problem_size = math.sqrt(total_memory * 0.1) blocks = int(base_problem_size / block_size) blocks = blocks if (blocks % 2) == 0 else blocks - 1 problem_size = block_size * blocks if CONFIG_DIMENSIONS.value: num_rows, num_columns = ( int(item) for item in CONFIG_DIMENSIONS.value.split(',') ) else: # Makes the grid as 'square' as possible, with rows < columns sqrt_cpus = int(math.sqrt(total_cpus)) + 1 num_rows = 0 num_columns = 0 for i in reversed(list(range(sqrt_cpus))): if total_cpus % i == 0: num_rows = i num_columns = total_cpus // i break return HpccDimensions( problem_size=problem_size, block_size=block_size, num_rows=num_rows, num_columns=num_columns, pfacts=PFACT_RFACT_MAPPING[CONFIG_PFACTS.value], nbmins=CONFIG_NBMINS.value, rfacts=PFACT_RFACT_MAPPING[CONFIG_RFACTS.value], bcasts=BCAST_MAPPING[CONFIG_BCASTS.value], depths=CONFIG_DEPTHS.value, swap=SWAP_MAPPING[CONFIG_SWAP.value], l1=L1_U_MAPPING[CONFIG_L1.value], u=L1_U_MAPPING[CONFIG_U.value], equilibration=EQUILIBRATION_MAPPING[CONFIG_EQUILIBRATION.value], ) def CreateHpccinf( vm: linux_vm.BaseLinuxVirtualMachine, benchmark_spec: bm_spec.BenchmarkSpec ) -> HpccDimensions: """Creates the HPCC input file.""" dimensions = _CalculateHpccDimensions( len(benchmark_spec.vms), vm.NumCpusForBenchmark(), vm.total_free_memory_kb ) vm.RemoteCommand(f'rm -f {HPCCINF_FILE}') vm.RenderTemplate( data.ResourcePath(LOCAL_HPCCINF_FILE), remote_path=HPCCINF_FILE, context=dataclasses.asdict(dimensions), ) return dimensions def PrepareHpcc(vm: linux_vm.BaseLinuxVirtualMachine) -> None: """Builds HPCC on a single vm.""" logging.info('Building HPCC on %s', vm) vm.Install('hpcc') if FLAGS.hpcc_numa_binding: vm.Install('numactl') def PrepareBinaries(vms: List[linux_vm.BaseLinuxVirtualMachine]) -> None: """Prepare binaries on all vms.""" if hpcc.USE_INTEL_COMPILED_HPL.value: intelmpi.NfsExportIntelDirectory(vms) background_tasks.RunThreaded(lambda vm: vm.Install('numactl'), vms) return headnode_vm = vms[0] if FLAGS.hpcc_binary: headnode_vm.PushFile(data.ResourcePath(FLAGS.hpcc_binary), './hpcc') else: headnode_vm.RemoteCommand(f'cp {hpcc.HPCC_DIR}/hpcc hpcc') background_tasks.RunThreaded( lambda vm: _PrepareBinaries(headnode_vm, vm), vms[1:] ) def _PrepareBinaries( headnode_vm: linux_vm.BaseLinuxVirtualMachine, vm: linux_vm.BaseLinuxVirtualMachine, ) -> None: """Prepares the binaries on the vm.""" vm.Install('fortran') headnode_vm.MoveFile(vm, 'hpcc', 'hpcc') headnode_vm.MoveFile(vm, '/usr/bin/orted', 'orted') vm.RemoteCommand('sudo mv orted /usr/bin/orted') if FLAGS.hpcc_math_library == hpcc.HPCC_MATH_LIBRARY_MKL: intel_repo.CopyIntelFiles(headnode_vm, vm) def Prepare(benchmark_spec: bm_spec.BenchmarkSpec) -> None: """Install HPCC on the target vms. Args: benchmark_spec: The benchmark specification. Contains all data that is required to run the benchmark. """ vms = benchmark_spec.vms headnode_vm = vms[0] PrepareHpcc(headnode_vm) CreateHpccinf(headnode_vm, benchmark_spec) hpc_util.CreateMachineFile(vms, remote_path=MACHINEFILE) headnode_vm.AuthenticateVm() PrepareBinaries(vms) def BaseMetadata(vm: linux_vm.BaseLinuxVirtualMachine) -> Dict[str, str]: """Update metadata with hpcc-related flag values.""" metadata = {} metadata['memory_size_mb'] = FLAGS.memory_size_mb if FLAGS['hpcc_binary'].present: metadata['override_binary'] = FLAGS.hpcc_binary if FLAGS['hpcc_mpi_env'].present: metadata['mpi_env'] = FLAGS.hpcc_mpi_env metadata['hpcc_math_library'] = FLAGS.hpcc_math_library metadata['hpcc_version'] = hpcc.HPCC_VERSION if FLAGS.hpcc_benchmarks: metadata['hpcc_benchmarks'] = FLAGS.hpcc_benchmarks if FLAGS.hpcc_math_library == hpcc.HPCC_MATH_LIBRARY_MKL: metadata['math_library_version'] = mkl.MKL_VERSION.value elif FLAGS.hpcc_math_library == hpcc.HPCC_MATH_LIBRARY_OPEN_BLAS: metadata['math_library_version'] = openblas.GetVersion(vm) metadata['openmpi_version'] = FLAGS.openmpi_version if FLAGS.hpcc_numa_binding: metadata['hpcc_numa_binding'] = FLAGS.hpcc_numa_binding if hpcc.USE_INTEL_COMPILED_HPL.value: metadata['hpcc_origin'] = 'intel' metadata['intel_mpi_version'] = intelmpi.MPI_VERSION.value else: metadata['hpcc_origin'] = 'source' return metadata def ParseOutput(hpcc_output: str) -> List[sample.Sample]: """Parses the output from HPCC. Args: hpcc_output: A string containing the text of hpccoutf.txt. Returns: A list of samples to be published (in the same format as Run() returns). """ results = [] # Parse all metrics from metric=value lines in the HPCC output. metric_values = regex_util.ExtractAllFloatMetrics(hpcc_output) # For each benchmark that is run, collect the metrics and metadata for that # benchmark from the metric_values map. benchmarks_run = FLAGS.hpcc_benchmarks or hpcc.HPCC_METRIC_MAP for benchmark in benchmarks_run: for metric, units in hpcc.HPCC_METRIC_MAP[benchmark].items(): value = metric_values[metric] # Common metadata for all runs done in Run's call to _AddCommonMetadata metadata = { metadata_item: metric_values[metadata_item] for metadata_item in hpcc.HPCC_METADATA_MAP[benchmark] } results.append(sample.Sample(metric, value, units, metadata)) return results def Run(benchmark_spec: bm_spec.BenchmarkSpec) -> List[sample.Sample]: """Run HPCC on the cluster. Args: benchmark_spec: The benchmark specification. Contains all data that is required to run the benchmark. Returns: A list of sample.Sample objects. """ # recreate the HPL config file with each run in case parameters change dimensions = CreateHpccinf(benchmark_spec.vms[0], benchmark_spec) logging.info('HPL.dat dimensions: %s', dimensions) if hpcc.USE_INTEL_COMPILED_HPL.value: samples = [RunIntelLinpack(benchmark_spec.vms, dimensions)] else: samples = RunHpccSource(benchmark_spec.vms) _AddCommonMetadata(samples, benchmark_spec, dataclasses.asdict(dimensions)) return samples def _AddCommonMetadata( samples: List[sample.Sample], benchmark_spec: bm_spec.BenchmarkSpec, dimensions: Dict[str, Any], ) -> None: """Adds metadata common to all samples.""" for item in samples: item.metadata.update(BaseMetadata(benchmark_spec.vms[0])) item.metadata['num_machines'] = len(benchmark_spec.vms) item.metadata.update(dimensions) def RunHpccSource( vms: List[linux_vm.BaseLinuxVirtualMachine], ) -> List[sample.Sample]: """Returns the parsed output from running the compiled from source HPCC.""" headnode_vm = vms[0] # backup existing HPCC output, if any headnode_vm.RemoteCommand( 'if [ -f hpccoutf.txt ]; then ' 'mv hpccoutf.txt hpccoutf-$(date +%s).txt; ' 'fi' ) num_processes = len(vms) * headnode_vm.NumCpusForBenchmark() run_as_root = '--allow-run-as-root' if FLAGS.mpirun_allow_run_as_root else '' mpi_flags = ( f'-machinefile {MACHINEFILE} --mca orte_rsh_agent ' f'"ssh -o StrictHostKeyChecking=no" {run_as_root} {_MpiEnv()}' ) mpi_cmd = 'mpirun ' hpcc_exec = './hpcc' if FLAGS.hpcc_math_library == hpcc.HPCC_MATH_LIBRARY_MKL: # Must exec HPCC wrapper script to pickup location of libiomp5.so background_tasks.RunThreaded(_CreateHpccWrapper, vms) hpcc_exec = f'./{HPCC_WRAPPER}' if FLAGS.hpcc_numa_binding: numa_map = numactl.GetNuma(headnode_vm) numa_hpcc_cmd = [] for node, num_cpus in numa_map.items(): numa_hpcc_cmd.append( f'-np {num_cpus} {mpi_flags} ' f'numactl --cpunodebind {node} ' f'--membind {node} {hpcc_exec}' ) mpi_cmd += ' : '.join(numa_hpcc_cmd) else: mpi_cmd += f'-np {num_processes} {mpi_flags} {hpcc_exec}' headnode_vm.RobustRemoteCommand( f'ulimit -n 32768; {mpi_cmd}', timeout=int(FLAGS.hpcc_timeout_hours * SECONDS_PER_HOUR), ) logging.info('HPCC Results:') stdout, _ = headnode_vm.RemoteCommand('cat hpccoutf.txt') if stdout.startswith('HPL ERROR'): # Annoyingly the mpi_cmd will succeed when there is an HPL error raise errors.Benchmarks.RunError(f'Error running HPL: {stdout}') return ParseOutput(stdout) def _CreateHpccWrapper(vm: linux_vm.BaseLinuxVirtualMachine) -> None: """Creates a bash script to run HPCC on the VM. This is required for when MKL is installed via the Intel repos as the libiomp5.so file is not in /lib but rather in one found via sourcing the mklvars.sh file. Args: vm: Virtual machine to put file on. """ text = ['#!/bin/bash', mkl.SourceVarsCommand(), './hpcc'] vm_util.CreateRemoteFile(vm, '\n'.join(text), HPCC_WRAPPER) vm.RemoteCommand(f'chmod +x {HPCC_WRAPPER}') def _MpiEnv(mpi_flag: str = '-x') -> str: """Returns the --hpcc_mpi_env flags as a string for the mpirun command.""" return ' '.join([f'{mpi_flag} {v}' for v in FLAGS.hpcc_mpi_env]) def Cleanup(benchmark_spec: bm_spec.BenchmarkSpec) -> None: """Cleanup HPCC on the cluster. Args: benchmark_spec: The benchmark specification. Contains all data that is required to run the benchmark. """ vms = benchmark_spec.vms headnode_vm = vms[0] headnode_vm.RemoveFile('hpcc*') headnode_vm.RemoveFile(MACHINEFILE) for vm in vms[1:]: vm.RemoveFile('hpcc') vm.RemoveFile('/usr/bin/orted') def RunIntelLinpack( vms: List[linux_vm.BaseLinuxVirtualMachine], dimensions: HpccDimensions ) -> sample.Sample: """Returns the parsed output from running the Intel compiled HPCC. Unlike the compiled from source linpack run the Intel compiled linpack can handle being cut off after --hpcc_timeout_hours as it parses the continuous output of linpack, reporting the last value found as the HPL_Tflops. The metadata argument value for "last_fraction_completed" is how much of the run was completed before being cut off. Args: vms: List of VMs to run benchmark on. dimensions: The HPCC configuration. Returns: Sample of the HPL_Tflops for the run. """ vm = vms[0] # Compiled from source HPL uses hpccinf.txt, one from Intel uses HPL.dat vm.RemoteCommand(f'cp {HPCCINF_FILE} HPL.dat') mpi_cmd, num_processes = _CreateIntelMpiRunCommand(vms, dimensions) run_cmd_txt, _ = vm.RobustRemoteCommand( mpi_cmd, ignore_failure=True, timeout=int(FLAGS.hpcc_timeout_hours * SECONDS_PER_HOUR), ) file_text, _ = vm.RemoteCommand('cat HPL.out', ignore_failure=True) tflops, metadata = _ParseIntelLinpackStdout(run_cmd_txt) if file_text: # HPL ran to completion, use the tflops from the file output tflops = _ParseIntelLinpackOutputFile(file_text) metadata['full'] = True else: # HPL timed out but have fractional metadata metadata['full'] = False metadata.update({ 'num_processes': num_processes, 'per_host': vm.numa_node_count, 'mpi_cmd': mpi_cmd, }) return sample.Sample('HPL_Tflops', tflops, 'Tflops/s', metadata) def _CreateIntelMpiRunCommand( vms: List[linux_vm.BaseLinuxVirtualMachine], dimensions: HpccDimensions ) -> Tuple[str, int]: """Creates the command to run HPL for Intel compiled linpack. Args: vms: List of virtual machines to run on. dimensions: The HpccDimensions for the run Returns: Tuple of the mpirun command and the number of processes to be used. """ headnode = vms[0] # Create the file for mpirun to execute hpl_path = '/opt/intel/mkl/benchmarks/mp_linpack/xhpl_intel64_static' bash_script = inspect.cleandoc(f""" #!/bin/bash export HPL_HOST_NODE=$((PMI_RANK % {headnode.numa_node_count})) {hpl_path} """) run_file = './hpl_run' for vm in vms: vm_util.CreateRemoteFile(vm, bash_script + '\n', run_file) vm.RemoteCommand(f'chmod +x {run_file}') logging.info('Using precompiled HPL at %s', hpl_path) num_processes = dimensions.num_rows * dimensions.num_columns hosts = ','.join([vm.internal_ip for vm in vms]) mpi_cmd = ( f'{intelmpi.SourceMpiVarsCommand(headnode)}; ' 'mpirun ' f'-perhost {headnode.numa_node_count} {_MpiEnv("-genv")} ' f'-np {num_processes} -host {hosts} {run_file}' ) return mpi_cmd, num_processes def _ParseIntelLinpackOutputFile(file_text: str) -> float: """Returns the tflops for the hpcc run. The last entry that matches WR11C2R4 50688 192 6 10 551.85 1.57334e+02 is the Gflops for the run: 157.33 Args: file_text: The hpcc output file contents. """ line_re = re.compile( r'\s+'.join([ r'WR\S+', r'\d+', r'\d+', r'\d+', r'\d+', r'\d+\.\d+', r'([\d\.e\+\-]+)', ]) ) gflops = None for line in file_text.splitlines(): match = line_re.match(line) if match: gflops = float(match[1]) return gflops / 1000 def _ParseIntelLinpackStdout(stdout: str) -> Tuple[float, Dict[str, float]]: """Parse the stdout of Intel HPL returning a condensed sample of results. Sample stdout: pkb-123-0 : Column=000576 Fraction=0.005 Kernel= 0.58 Mflops=1265648.19 pkb-123-0 : Column=001152 Fraction=0.010 Kernel=969908.14 Mflops=1081059.81 pkb-123-0 : Column=001728 Fraction=0.015 Kernel=956391.64 Mflops=1040609.60 Return: 1.0406096, {'fractions': '0.01,0.015', 'kernel_tflops': '0.96990814,0.95639164', 'last_fraction_completed': 0.015, 'tflops': '1.08105981,1.0406096' } Args: stdout: The stdout text from running HPL Returns: Tuple of the tflops/s and a dict of the fractional run information. Raises: ValueError: If no metrics could be found. """ line_re = re.compile( r"""Column=\s*(?P<column>\d+) \s*Fraction=\s*(?P<fraction>[\d\.]+) \s*Kernel=\s*(?P<kernel>[\d\.]+) \s*Mflops=\s*(?P<mflops>[\d\.]+)""", re.X, ) fractions = [] kernel_tflops = [] tflops = [] line_matches = line_re.finditer(stdout) try: next(line_matches) # first outputted values are artificially low except StopIteration: raise ValueError( f'Could not find a line in stdout to match {line_re.pattern}: {stdout}' ) for line_match in line_matches: fractions.append(float(line_match['fraction'])) kernel_tflops.append(float(line_match['kernel']) / 1e6) tflops.append(float(line_match['mflops']) / 1e6) if not tflops: raise ValueError('No metrics found in stdout') # Grab all the I_MPI* environment variables in debug output to put in metadata intel_env_re = re.compile( r'(.*MPI startup.*?)?\s*' r'(?P<key>I_MPI[A-Z_\d]+)=(?P<value>.*)\s*' ) env_vars = {row['key']: row['value'] for row in intel_env_re.finditer(stdout)} env_vars.pop('I_MPI_HYDRA_UUID', None) metadata = { 'fractions': ','.join([str(x) for x in fractions]), 'kernel_tflops': ','.join([str(x) for x in kernel_tflops]), 'tflops': ','.join([str(x) for x in tflops]), 'last_fraction_completed': fractions[-1], 'intel_mpi_env': vm_util.DictionaryToEnvString(env_vars, ';'), } return tflops[-1], metadata