Initial commit
[ta/infra-ansible.git] / roles / performance_nodes / files / configure_ovsdpdk.py
1 #!/usr/bin/env python
2
3 # Copyright 2019 Nokia
4
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #     http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 import argparse
18 import subprocess
19 import sys
20
21
22 class CoreHandler(object):
23     def __init__(self):
24         pass
25
26     @staticmethod
27     def hex_to_set(hexstr):
28         hexn = int(hexstr, 16)
29         b = 1
30         cpuset = set()
31         for i in range(0, 64):
32             if hexn & b != 0:
33                 cpuset.add(i)
34             b = b << 1
35         return cpuset
36
37     @staticmethod
38     def set_to_hex(cpuset):
39         cpumask = 0
40         for i in cpuset:
41             b = 1 << i
42             cpumask += b
43         return '0x{:x}'.format(cpumask)
44
45     @staticmethod
46     def string_to_set(cpustr):
47         if cpustr == '' or cpustr is None:
48             raise Exception("Empty string")
49         cpuset_ids = set()
50         cpuset_reject_ids = set()
51         for rule in cpustr.split(','):
52             rule = rule.strip()
53             # Handle multi ','
54             if len(rule) < 1:
55                 continue
56             # Note the count limit in the .split() call
57             range_parts = rule.split('-', 1)
58             if len(range_parts) > 1:
59                 # So, this was a range; start by converting the parts to ints
60                 try:
61                     start, end = [int(p.strip()) for p in range_parts]
62                 except ValueError:
63                     raise Exception("Invalid range expression {}".format(rule))
64                 # Make sure it's a valid range
65                 if start > end:
66                     raise Exception("Invalid range expression (start > end): {}".format(rule))
67                 # Add available CPU ids to set
68                 cpuset_ids |= set(range(start, end + 1))
69             elif rule[0] == '^':
70                 # Not a range, the rule is an exclusion rule; convert to int
71                 try:
72                     cpuset_reject_ids.add(int(rule[1:].strip()))
73                 except ValueError:
74                     raise Exception("Invalid exclusion expression {}".format(rule))
75             else:
76                 # OK, a single CPU to include; convert to int
77                 try:
78                     cpuset_ids.add(int(rule))
79                 except ValueError:
80                     raise Exception("Invalid inclusion expression {}".format(rule))
81
82         # Use sets to handle the exclusion rules for us
83         cpuset_ids -= cpuset_reject_ids
84         return cpuset_ids
85
86     @staticmethod
87     def set_to_string(cpuset):
88         ranges = []
89         previndex = None
90         for cpuindex in sorted(cpuset):
91             if previndex is None or previndex != (cpuindex - 1):
92                 ranges.append([])
93             ranges[-1].append(cpuindex)
94             previndex = cpuindex
95
96         parts = []
97         for entry in ranges:
98             if len(entry) == 1:
99                 parts.append(str(entry[0]))
100             else:
101                 parts.append("{}-{}".format(entry[0], entry[len(entry) - 1]))
102         return ",".join(parts)
103
104     def hex_to_string(self, hexstr):
105         """
106         :param hexstr: CPU mask as hex string
107         :returns: a formatted CPU range string
108         """
109         cpuset = self.hex_to_set(hexstr)
110         return self.set_to_string(cpuset)
111
112     def string_to_hex(self, cpustr):
113         cpuset = self.string_to_set(cpustr)
114         return self.set_to_hex(cpuset)
115
116
117 class OvsVsctl(object):
118     def __init__(self, cores, pcore, tcore, sockets):
119         self.cores = cores
120         self.pcore = pcore
121         self.threads_per_core = tcore
122         self.sockets = sockets
123
124     def is_hyperthreading_enabled(self):
125         if int(self.threads_per_core) >= 2:
126             return True
127         return False
128
129     def is_single_socket(self):
130         if int(self.sockets) == 1:
131             return True
132         return False
133
134     @staticmethod
135     def get_value(field):
136         """Get value for specified field in Open_vSwitch other_config.
137         Returns:
138             Value without quotes or newlines
139             None if field is not set
140         """
141         try:
142             current_value = subprocess.check_output(["ovs-vsctl", "get", "Open_vSwitch", ".", "other_config:{}".format(field)])
143         except Exception:
144             return None
145         return current_value.lstrip('\"').strip('\n').rstrip('\"')
146
147     def set_pmd_cpu_mask(self, value):
148         """Set DPDK core mask."""
149         current_value = self.get_value('pmd-cpu-mask')
150         print "INFO: New core mask {}, current_value {}".format(value, current_value)
151         if current_value == value:
152             return False
153         try:
154             subprocess.check_output(["ovs-vsctl", "set", "Open_vSwitch", ".", "other_config:pmd-cpu-mask=\"{}\"".format(value)])
155         except Exception:
156             sys.exit(2)
157         return True
158
159     def set_lcore_mask(self):
160         """Set DPDK library mask."""
161         #if self.is_single_socket():
162         mask = '0x0'
163         # TODO: measure if it would be beneficial to reserve more cores and from second socket
164         current_value = self.get_value('dpdk-lcore-mask')
165         print "INFO: Mask {},  current_value {}".format(mask, current_value)
166
167         if current_value == mask:
168             return False
169         try:
170             subprocess.check_output(["ovs-vsctl", "set", "Open_vSwitch", ".", "other_config:dpdk-lcore-mask=\"{}\"".format(mask)])
171         except Exception:
172             sys.exit(2)
173         return True
174
175     def set_socket_mem(self, value):
176         """Set DPDK memory."""
177         if self.is_single_socket():
178             mem = '{0},0'.format(value)
179         else:
180             mem = '{0},{0}'.format(value)
181         current_value = self.get_value('dpdk-socket-mem')
182         print "INFO: New mem {},  current_value {}".format(mem, current_value)
183
184         if current_value == mem:
185             return False
186         try:
187             subprocess.check_output(["ovs-vsctl", "set", "Open_vSwitch", ".", "other_config:dpdk-socket-mem=\"{}\"".format(mem)])
188         except Exception:
189             sys.exit(2)
190         return True
191
192     def enable_dpdk(self):
193         """Enable DPDK."""
194         current_value = self.get_value('dpdk-init')
195         print "INFO: Enable DPDK, current_value {}".format(current_value)
196         if current_value == 'true':
197             return False
198
199         try:
200             with open('/etc/performance-nodes/dpdk.enabled', 'a'):
201                 pass
202         except IOError:
203             sys.exit(2)
204
205         return True
206
207
208 def main():
209     parser = argparse.ArgumentParser()
210     parser.add_argument('--dpdkcores', dest='dpdkcores', help='DPDK cores', required=False, type=str)
211     parser.add_argument('--cores', dest='cores', help='Logical cores', required=False, type=str)
212     parser.add_argument('--pcore', dest='pcore', help='Cores per processor', required=False, type=str)
213     parser.add_argument('--tcore', dest='tcore', help='Threads per core', required=False, type=str)
214     parser.add_argument('--sockets', dest='sockets', help='Number of sockets', required=False, type=str)
215     parser.add_argument('--mem', dest='mem', help='Socket mem', required=False, type=str, default='4096')
216     args = parser.parse_args()
217
218     ovsvsctl = OvsVsctl(args.cores, args.pcore, args.tcore, args.sockets)
219     coreh = CoreHandler()
220
221     dpdkcoreset = None
222     try:
223         print "INFO: dpdk cores: {}, cpu count: {}".format(args.dpdkcores, args.cores)
224         if args.dpdkcores:
225             dpdkcoreset = coreh.string_to_set(args.dpdkcores)
226     except Exception as exp:
227         print "ERROR: Calculating of cpu/core set failed: {}".format(exp)
228         sys.exit(2)
229
230     if max(dpdkcoreset) > int(args.cores):
231         print "ERROR: invalid DPDK cores (too big)"
232         sys.exit(2)
233
234     # Check if hyperthreading is off and we can calculate new core set so that
235     # DPDK has a chance to start.
236     if not ovsvsctl.is_hyperthreading_enabled() and max(dpdkcoreset) > int(args.cores):
237         print "INFO: Hyperthreading off, DPDK cores contain too big core"
238         dpdkcoreset = set([x for x in dpdkcoreset if x < int(args.cores)])
239         print "INFO: new dpdkcoreset {}".format(dpdkcoreset)
240     else:
241         dpdkcoreset = dpdkcoreset
242     dpdkcoremask = coreh.set_to_hex(dpdkcoreset)
243
244     changed = False
245
246     changed |= ovsvsctl.set_pmd_cpu_mask(dpdkcoremask)
247     #changed |= ovsvsctl.set_lcore_mask()
248     changed |= ovsvsctl.set_socket_mem(args.mem)
249     changed |= ovsvsctl.enable_dpdk()
250     if changed:
251         exit(0)
252     else:
253         exit(1)
254
255 if __name__ == "__main__":
256     main()