#!/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()