X-Git-Url: https://gerrit.akraino.org/r/gitweb?a=blobdiff_plain;f=roles%2Fperformance_nodes%2Ffiles%2Fconfigure_ovsdpdk.py;fp=roles%2Fperformance_nodes%2Ffiles%2Fconfigure_ovsdpdk.py;h=31e784ce394d3e78d8029e52b82ab80bb00c2e3c;hb=74a49ba6ef2ea715fa492db0bcd85c30398688e8;hp=0000000000000000000000000000000000000000;hpb=a936af362724cca0c5dc2c424902d398f9833410;p=ta%2Finfra-ansible.git diff --git a/roles/performance_nodes/files/configure_ovsdpdk.py b/roles/performance_nodes/files/configure_ovsdpdk.py new file mode 100644 index 0000000..31e784c --- /dev/null +++ b/roles/performance_nodes/files/configure_ovsdpdk.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python + +# Copyright 2019 Nokia + +# 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. + +import argparse +import subprocess +import sys + + +class CoreHandler(object): + def __init__(self): + pass + + @staticmethod + def hex_to_set(hexstr): + hexn = int(hexstr, 16) + b = 1 + cpuset = set() + for i in range(0, 64): + if hexn & b != 0: + cpuset.add(i) + b = b << 1 + return cpuset + + @staticmethod + def set_to_hex(cpuset): + cpumask = 0 + for i in cpuset: + b = 1 << i + cpumask += b + return '0x{:x}'.format(cpumask) + + @staticmethod + def string_to_set(cpustr): + if cpustr == '' or cpustr is None: + raise Exception("Empty string") + cpuset_ids = set() + cpuset_reject_ids = set() + for rule in cpustr.split(','): + rule = rule.strip() + # Handle multi ',' + if len(rule) < 1: + continue + # Note the count limit in the .split() call + range_parts = rule.split('-', 1) + if len(range_parts) > 1: + # So, this was a range; start by converting the parts to ints + try: + start, end = [int(p.strip()) for p in range_parts] + except ValueError: + raise Exception("Invalid range expression {}".format(rule)) + # Make sure it's a valid range + if start > end: + raise Exception("Invalid range expression (start > end): {}".format(rule)) + # Add available CPU ids to set + cpuset_ids |= set(range(start, end + 1)) + elif rule[0] == '^': + # Not a range, the rule is an exclusion rule; convert to int + try: + cpuset_reject_ids.add(int(rule[1:].strip())) + except ValueError: + raise Exception("Invalid exclusion expression {}".format(rule)) + else: + # OK, a single CPU to include; convert to int + try: + cpuset_ids.add(int(rule)) + except ValueError: + raise Exception("Invalid inclusion expression {}".format(rule)) + + # Use sets to handle the exclusion rules for us + cpuset_ids -= cpuset_reject_ids + return cpuset_ids + + @staticmethod + def set_to_string(cpuset): + ranges = [] + previndex = None + for cpuindex in sorted(cpuset): + if previndex is None or previndex != (cpuindex - 1): + ranges.append([]) + ranges[-1].append(cpuindex) + previndex = cpuindex + + parts = [] + for entry in ranges: + if len(entry) == 1: + parts.append(str(entry[0])) + else: + parts.append("{}-{}".format(entry[0], entry[len(entry) - 1])) + return ",".join(parts) + + def hex_to_string(self, hexstr): + """ + :param hexstr: CPU mask as hex string + :returns: a formatted CPU range string + """ + cpuset = self.hex_to_set(hexstr) + return self.set_to_string(cpuset) + + def string_to_hex(self, cpustr): + cpuset = self.string_to_set(cpustr) + return self.set_to_hex(cpuset) + + +class OvsVsctl(object): + def __init__(self, cores, pcore, tcore, sockets): + self.cores = cores + self.pcore = pcore + self.threads_per_core = tcore + self.sockets = sockets + + def is_hyperthreading_enabled(self): + if int(self.threads_per_core) >= 2: + return True + return False + + def is_single_socket(self): + if int(self.sockets) == 1: + return True + return False + + @staticmethod + def get_value(field): + """Get value for specified field in Open_vSwitch other_config. + Returns: + Value without quotes or newlines + None if field is not set + """ + try: + current_value = subprocess.check_output(["ovs-vsctl", "get", "Open_vSwitch", ".", "other_config:{}".format(field)]) + except Exception: + return None + return current_value.lstrip('\"').strip('\n').rstrip('\"') + + def set_pmd_cpu_mask(self, value): + """Set DPDK core mask.""" + current_value = self.get_value('pmd-cpu-mask') + print "INFO: New core mask {}, current_value {}".format(value, current_value) + if current_value == value: + return False + try: + subprocess.check_output(["ovs-vsctl", "set", "Open_vSwitch", ".", "other_config:pmd-cpu-mask=\"{}\"".format(value)]) + except Exception: + sys.exit(2) + return True + + def set_lcore_mask(self): + """Set DPDK library mask.""" + #if self.is_single_socket(): + mask = '0x0' + # TODO: measure if it would be beneficial to reserve more cores and from second socket + current_value = self.get_value('dpdk-lcore-mask') + print "INFO: Mask {}, current_value {}".format(mask, current_value) + + if current_value == mask: + return False + try: + subprocess.check_output(["ovs-vsctl", "set", "Open_vSwitch", ".", "other_config:dpdk-lcore-mask=\"{}\"".format(mask)]) + except Exception: + sys.exit(2) + return True + + def set_socket_mem(self, value): + """Set DPDK memory.""" + if self.is_single_socket(): + mem = '{0},0'.format(value) + else: + mem = '{0},{0}'.format(value) + current_value = self.get_value('dpdk-socket-mem') + print "INFO: New mem {}, current_value {}".format(mem, current_value) + + if current_value == mem: + return False + try: + subprocess.check_output(["ovs-vsctl", "set", "Open_vSwitch", ".", "other_config:dpdk-socket-mem=\"{}\"".format(mem)]) + except Exception: + sys.exit(2) + return True + + def enable_dpdk(self): + """Enable DPDK.""" + current_value = self.get_value('dpdk-init') + print "INFO: Enable DPDK, current_value {}".format(current_value) + if current_value == 'true': + return False + + try: + with open('/etc/performance-nodes/dpdk.enabled', 'a'): + pass + except IOError: + sys.exit(2) + + return True + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--dpdkcores', dest='dpdkcores', help='DPDK cores', required=False, type=str) + parser.add_argument('--cores', dest='cores', help='Logical cores', required=False, type=str) + parser.add_argument('--pcore', dest='pcore', help='Cores per processor', required=False, type=str) + parser.add_argument('--tcore', dest='tcore', help='Threads per core', required=False, type=str) + parser.add_argument('--sockets', dest='sockets', help='Number of sockets', required=False, type=str) + parser.add_argument('--mem', dest='mem', help='Socket mem', required=False, type=str, default='4096') + args = parser.parse_args() + + ovsvsctl = OvsVsctl(args.cores, args.pcore, args.tcore, args.sockets) + coreh = CoreHandler() + + dpdkcoreset = None + try: + print "INFO: dpdk cores: {}, cpu count: {}".format(args.dpdkcores, args.cores) + if args.dpdkcores: + dpdkcoreset = coreh.string_to_set(args.dpdkcores) + except Exception as exp: + print "ERROR: Calculating of cpu/core set failed: {}".format(exp) + sys.exit(2) + + if max(dpdkcoreset) > int(args.cores): + print "ERROR: invalid DPDK cores (too big)" + sys.exit(2) + + # Check if hyperthreading is off and we can calculate new core set so that + # DPDK has a chance to start. + if not ovsvsctl.is_hyperthreading_enabled() and max(dpdkcoreset) > int(args.cores): + print "INFO: Hyperthreading off, DPDK cores contain too big core" + dpdkcoreset = set([x for x in dpdkcoreset if x < int(args.cores)]) + print "INFO: new dpdkcoreset {}".format(dpdkcoreset) + else: + dpdkcoreset = dpdkcoreset + dpdkcoremask = coreh.set_to_hex(dpdkcoreset) + + changed = False + + changed |= ovsvsctl.set_pmd_cpu_mask(dpdkcoremask) + #changed |= ovsvsctl.set_lcore_mask() + changed |= ovsvsctl.set_socket_mem(args.mem) + changed |= ovsvsctl.enable_dpdk() + if changed: + exit(0) + else: + exit(1) + +if __name__ == "__main__": + main()