perfkitbenchmarker/linux_benchmarks/bonnie_benchmark.py (216 lines of code) (raw):

# Copyright 2014 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 plain vanilla bonnie++.""" import logging from perfkitbenchmarker import configs from perfkitbenchmarker import regex_util from perfkitbenchmarker import sample BENCHMARK_NAME = 'bonnieplusplus' BENCHMARK_CONFIG = """ bonnieplusplus: description: > Runs Bonnie++. Running this benchmark inside a container is currently not supported, since Docker tries to run it as root, which is not recommended. vm_groups: default: vm_spec: *default_dual_core disk_spec: *default_500_gb """ LATENCY_REGEX = r'([0-9]*\.?[0-9]+)(\w+)' # Bonnie++ result fields mapping, see man bon_csv2txt for details. BONNIE_RESULTS_MAPPING_1_96 = { 'format_version': 0, 'bonnie_version': 1, 'name': 2, 'concurrency': 3, 'seed': 4, 'file_size': 5, 'chunk_size': 6, 'putc': 7, 'putc_cpu': 8, 'put_block': 9, 'put_block_cpu': 10, 'rewrite': 11, 'rewrite_cpu': 12, 'getc': 13, 'getc_cpu': 14, 'get_block': 15, 'get_block_cpu': 16, 'seeks': 17, 'seeks_cpu': 18, 'num_files': 19, 'max_size': 20, 'min_size': 21, 'num_dirs': 22, 'file_chunk_size': 23, 'seq_create': 24, 'seq_create_cpu': 25, 'seq_stat': 26, 'seq_stat_cpu': 27, 'seq_del': 28, 'seq_del_cpu': 29, 'ran_create': 30, 'ran_create_cpu': 31, 'ran_stat': 32, 'ran_stat_cpu': 33, 'ran_del': 34, 'ran_del_cpu': 35, 'putc_latency': 36, 'put_block_latency': 37, 'rewrite_latency': 38, 'getc_latency': 39, 'get_block_latency': 40, 'seeks_latency': 41, 'seq_create_latency': 42, 'seq_stat_latency': 43, 'seq_del_latency': 44, 'ran_create_latency': 45, 'ran_stat_latency': 46, 'ran_del_latency': 47, } # Bonnie 1.97 looks the same as 1.96 as far as headings BONNIE_RESULTS_MAPPING_1_97 = BONNIE_RESULTS_MAPPING_1_96 BONNIE_RESULTS_MAPPING_1_98 = { 'format_version': 0, 'bonnie_version': 1, 'name': 2, 'concurrency': 3, 'seed': 4, 'file_size': 5, 'chunk_size': 6, 'seeks_count': 7, 'seek_proc_count': 8, 'putc': 9, 'putc_cpu': 10, 'put_block': 11, 'put_block_cpu': 12, 'rewrite': 13, 'rewrite_cpu': 14, 'getc': 15, 'getc_cpu': 16, 'get_block': 17, 'get_block_cpu': 18, 'seeks': 19, 'seeks_cpu': 20, 'num_files': 21, 'max_size': 22, 'min_size': 23, 'num_dirs': 24, 'file_chunk_size': 25, 'seq_create': 26, 'seq_create_cpu': 27, 'seq_stat': 28, 'seq_stat_cpu': 29, 'seq_del': 30, 'seq_del_cpu': 31, 'ran_create': 32, 'ran_create_cpu': 33, 'ran_stat': 34, 'ran_stat_cpu': 35, 'ran_del': 36, 'ran_del_cpu': 37, 'putc_latency': 38, 'put_block_latency': 39, 'rewrite_latency': 40, 'getc_latency': 41, 'get_block_latency': 42, 'seeks_latency': 43, 'seq_create_latency': 44, 'seq_stat_latency': 45, 'seq_del_latency': 46, 'ran_create_latency': 47, 'ran_stat_latency': 48, 'ran_del_latency': 49, } BONNIE_SUPPORTED_VERSIONS = { '1.96': BONNIE_RESULTS_MAPPING_1_96, '1.97': BONNIE_RESULTS_MAPPING_1_97, '1.98': BONNIE_RESULTS_MAPPING_1_98, } def GetConfig(user_config): return configs.LoadConfig(BENCHMARK_CONFIG, user_config, BENCHMARK_NAME) def Prepare(benchmark_spec): """Install Bonnie++ on the target vm. Args: benchmark_spec: The benchmark specification. Contains all data that is required to run the benchmark. """ vms = benchmark_spec.vms vm = vms[0] logging.info('Bonnie++ prepare on %s', vm) vm.InstallPackages('bonnie++') def IsValueValid(value): """Validate the value. An invalid value is either an empty string or a string of multiple '+'. Args: value: string. The value in raw result. Returns: A boolean indicates if the value is valid or not. """ if value == '' or '+' in value: return False return True def IsCpuField(field): """Check if the field is cpu percentage. Args: field: string. The name of the field. Returns: A boolean indicates if the field contains keyword 'cpu'. """ return 'cpu' in field def IsLatencyField(field): """Check if the field is latency. Args: field: string. The name of the field. Returns: A boolean indicates if the field contains keyword 'latency'. """ return 'latency' in field def ParseLatencyResult(result): """Parse latency result into value and unit. Args: result: string. Latency value in string format, contains value and unit. eg. 200ms Returns: A tuple of value (float) and unit (string). """ match = regex_util.ExtractAllMatches(LATENCY_REGEX, result)[0] return float(match[0]), match[1] def UpdateMetadata(metadata, key, value): """Check if the value is valid, update metadata with the key, value pair. Args: metadata: dict. A dictionary of sample metadata. key: string. Key that will be added into metadata dictionary. value: Value that of the key. """ if IsValueValid(value): metadata[key] = value def CreateSamples( results, start_index, end_index, metadata, field_index_mapping ): """Create samples with data in results from start_index to end_index. Args: results: A list of string representing bonnie++ results. start_index: integer. The start index in results list of the samples. end_index: integer. The end index in results list of the samples. metadata: dict. A dictionary of metadata added into samples. field_index_mapping: dict. A dictionary maps field index to field names. Returns: A list of sample.Sample instances. """ samples = [] for field_index in range(start_index, end_index): field_name = field_index_mapping[field_index] value = results[field_index] if not IsValueValid(value): continue if IsCpuField(field_name): unit = '%s' elif IsLatencyField(field_name): value, unit = ParseLatencyResult(value) else: unit = 'K/sec' samples.append(sample.Sample(field_name, float(value), unit, metadata)) return samples def ParseCSVResults(results): """Parse csv format bonnie++ results. Sample Results: 1.96,1.96,perfkit-7b22f510-0,1,1421800799,7423M,,,,72853,15,47358,5,,, 156821,7,537.7,10,100,,,,,49223,58,+++++,+++,54405,53,2898,97,+++++,+++, 59089,60,,512ms,670ms,,44660us,200ms,3747us,1759us,1643us,33518us,192us, 839us Args: results: string. Bonnie++ results. Returns: A list of samples in the form of 3 or 4 tuples. The tuples contain the sample metric (string), value (float), and unit (string). If a 4th element is included, it is a dictionary of sample metadata. """ results = results.split(',') format_version = results[0] if format_version in BONNIE_SUPPORTED_VERSIONS: bonnie_results_mapping = BONNIE_SUPPORTED_VERSIONS[format_version] logging.info('Detected bonnie++ CSV format version %s', format_version) else: raise ValueError( f'Unsupported bonnie++ CSV Format version: {format_version} ' f'(expected version {BONNIE_SUPPORTED_VERSIONS.keys()})' ) field_index_mapping = {} for field, value in bonnie_results_mapping.items(): field_index_mapping[value] = field assert len(results) == len(bonnie_results_mapping) samples = [] metadata = {} for field_index in range( bonnie_results_mapping['format_version'], bonnie_results_mapping['chunk_size'] + 1, ): UpdateMetadata( metadata, field_index_mapping[field_index], results[field_index] ) for field_index in range( bonnie_results_mapping['num_files'], bonnie_results_mapping['file_chunk_size'] + 1, ): UpdateMetadata( metadata, field_index_mapping[field_index], results[field_index] ) samples.extend( CreateSamples( results, bonnie_results_mapping['putc'], bonnie_results_mapping['num_files'], metadata, field_index_mapping, ) ) samples.extend( CreateSamples( results, bonnie_results_mapping['seq_create'], bonnie_results_mapping['ran_del_latency'] + 1, metadata, field_index_mapping, ) ) return samples def Run(benchmark_spec): """Run Bonnie++ on the target vm. Args: benchmark_spec: The benchmark specification. Contains all data that is required to run the benchmark. Returns: A list of samples in the form of 3 or 4 tuples. The tuples contain the sample metric (string), value (float), and unit (string). If a 4th element is included, it is a dictionary of sample metadata. """ vms = benchmark_spec.vms vm = vms[0] logging.info('Bonnie++ running on %s', vm) bonnie_command = '/usr/sbin/bonnie++ -q -d %s -s %d -n 100 -f' % ( vm.GetScratchDir(), 2 * vm.total_memory_kb / 1024, ) logging.info('Bonnie++ Results:') out, _ = vm.RemoteCommand(bonnie_command) return ParseCSVResults(out.strip()) def Cleanup(benchmark_spec): """Cleanup Bonnie++ on the target vm (by uninstalling). Args: benchmark_spec: The benchmark specification. Contains all data that is required to run the benchmark. """ pass