Merge "[l2l3 stack] implements new nd state machine & nd buffering"
[samplevnf.git] / VNFs / DPPD-PROX / helper-scripts / testvRouter / characterize_BNG_8ports.py
1 #!/bin/env python
2
3 ##
4 ## Copyright (c) 2010-2017 Intel Corporation
5 ##
6 ## Licensed under the Apache License, Version 2.0 (the "License");
7 ## you may not use this file except in compliance with the License.
8 ## You may obtain a copy of the License at
9 ##
10 ##     http://www.apache.org/licenses/LICENSE-2.0
11 ##
12 ## Unless required by applicable law or agreed to in writing, software
13 ## distributed under the License is distributed on an "AS IS" BASIS,
14 ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 ## See the License for the specific language governing permissions and
16 ## limitations under the License.
17 ##
18
19 import socket
20 import sys
21 import os
22 from time import *
23 from datetime import  datetime
24 from optparse import OptionParser
25 import time
26 from remote_system import *
27 from math import log
28
29 # General parameters
30 accuracy = 0.1  # in percent of line rate
31 max_dropped = 0.1               # in percent
32 all_pkt_size = [64,128,256,512,1024,1280,1494]
33 all_ip_src = [0,6,12,18]
34 all_ip_dst = [0,6,12,18]
35
36 # Stear parameters
37 step_time = 0.001               # in seconds
38 step_delta = 10         # in percent of line rate
39
40 ##### Use case 1: packet loss and latency #####
41 low_steps_delta_for_loss = 0.01                 # Use increment of 0.01% from 0 to low_steps
42 medium_steps_delta_for_loss = 0.1               # Use increment of 0.1% from low_steps to medium_steps
43 normal_steps_delta_for_loss = 1.0               # Use increment of 1% from medium_steps till 100%
44 low_steps = 0.1 
45 medium_steps = 1.0 
46
47 # Prox parameters
48 tx_port0 = [4]
49 tx_port1 = [6]
50 tx_port2 = [8]
51 tx_port3 = [10]
52 tx_port4 = [12]
53 tx_port5 = [14]
54 tx_port6 = [16]
55 tx_port7 = [18]
56 tx_task = 0
57
58 all_rx_cores = [20,22,24,26,28,30,32,34]
59 rx_lat_cores = [20,22,24,26,28,30,32,34]
60 rx_task = 0
61
62 # Some variables, do not change
63
64 # Program arguments
65 parser = OptionParser()
66 parser.add_option("-d", "--duration", dest="test_duration", help="Duration of each steps", metavar="integer", default=10)
67 parser.add_option("-s", "--speed", dest="init_speed", help="Initial speed", metavar="integer", default=100)
68 parser.add_option("-r", "--run", dest="run", help="Run test", metavar="integer", default=0)
69 parser.add_option("-c", "--configure", dest="configure", help="Configure Test", metavar="integer", default=0)
70 (options, args) = parser.parse_args()
71
72 init_speed = int(options.init_speed)
73 test_duration = int(options.test_duration)
74 configure = int(options.configure)
75 run = int(options.run)
76
77 nb_cores_per_interface = len(tx_port0)
78 max_speed = (100.0/nb_cores_per_interface)
79 init_speed = (init_speed * 1.0/nb_cores_per_interface)
80 accuracy = (accuracy * 1.0/nb_cores_per_interface)
81 normal_steps_delta_for_loss = (normal_steps_delta_for_loss /nb_cores_per_interface)
82 medium_steps_delta_for_loss = (medium_steps_delta_for_loss /nb_cores_per_interface)
83 low_steps_delta_for_loss = (low_steps_delta_for_loss /nb_cores_per_interface)
84 medium_steps = (medium_steps /nb_cores_per_interface)
85 low_steps = (low_steps /nb_cores_per_interface)
86
87 max_dropped = max_dropped / 100
88
89 def to_str(arr):
90     ret = ""
91     first = 1;
92     for a in arr:
93         if (first == 0):
94             ret += ","
95
96         ret += str(a)
97         first = 0;
98     return ret;
99
100 tx_cores_cpe = tx_port0 + tx_port1 + tx_port2 + tx_port3 
101 tx_cores_inet = tx_port4 + tx_port5 + tx_port6 + tx_port7
102 tx_cores = tx_cores_cpe + tx_cores_inet
103
104 def send_all_pkt_size(cores, pkt_size):
105     for c in cores:
106         sock.sendall("pkt_size " + str(c) + " 0 " + str(pkt_size) + "\n");
107
108 def send_all_value(cores, offset, value, len):
109     for c in cores:
110         sock.sendall("set value " + str(c) + " 0 " + str(offset) + " " + str(value) + " " + str(len)+ "\n");
111
112 def send_all_random(cores, offset, rand_str, len):
113     for c in cores:
114         sock.sendall("set random " + str(c) + " 0 " + str(offset) + " " + str(rand_str) + " " + str(len)+ "\n");
115         #print("set random " + str(c) + " 0 " + str(offset) + " " + str(rand_str) + " " + str(len)+ "\n");
116
117 def send_all_speed(cores, speed_perc):
118     for c in cores:
119         sock.sendall("speed " + str(c) + " 0 " + str(speed_perc) + "\n");
120
121 def send_reset_random():
122         sock.sendall("reset randoms all" + "\n");
123
124 def send_reset_value():
125         sock.sendall("reset values all" + "\n");
126
127 def rx_stats(tx_cores, tx_task, rx_cores, rx_task):
128         rx = tx = drop = tsc = tsc_hs = ierrors = 0
129         for e in tx_cores:
130                 sock.sendall("core stats " + str(e) + " " + str(tx_task) +  "\n")
131                 recv = recv_once()
132                 rx += int(recv.split(",")[0])
133                 tx += int(recv.split(",")[1])
134                 drop += int(recv.split(",")[2])
135                 tsc = int(recv.split(",")[3])
136                 tsc_hz = int(recv.split(",")[4])
137         for e in rx_cores:
138                 sock.sendall("core stats " + str(e) + " " + str(rx_task) +  "\n")
139                 recv = recv_once()
140                 rx += int(recv.split(",")[0])
141                 tx += int(recv.split(",")[1])
142                 drop += int(recv.split(",")[2])
143                 tsc = int(recv.split(",")[3])
144                 tsc_hz = int(recv.split(",")[4])
145         # Also get the ierrors as generators might be the bottleneck...
146         sock.sendall("tot ierrors tot\n")
147         recv = recv_once()
148         ierrors += int(recv.split(",")[0])
149         rx+=ierrors
150         return rx,tx,drop,tsc,tsc_hz
151
152 def lat_stats(cores,task):
153         lat_min = [0 for e in range(127)]
154         lat_max = [0 for e in range(127)]
155         lat_avg = [0 for e in range(127)]
156         for e in cores:
157                 sock.sendall("lat stats " + str(e) + " " + str(task) + " " +  "\n")
158                 recv = recv_once()
159                 lat_min[e] = int(recv.split(",")[0])
160                 lat_max[e] = int(recv.split(",")[1])
161                 lat_avg[e] = int(recv.split(",")[2])
162         return lat_min, lat_max, lat_avg
163
164 def recv_once():
165     ret_str = "";
166     done = 0;
167     while done == 0:
168         dat = sock.recv(256);
169         i = 0;
170         while(i < len(dat)):
171             if (dat[i] == '\n'):
172                 done = 1
173             else:
174                 ret_str += dat[i];
175             i = i + 1;
176     return ret_str
177
178 def set_pkt_sizes(tx_cores, p):
179         send_all_pkt_size(tx_cores, p-4)
180         # For all cores, need to adapt IP Length (byte 16) and UDP Length (byte 38) to pkt size
181         send_all_value(tx_cores, 16, p - 18, 2)         # 14 for MAC (12) EthType (2) 
182         send_all_value(tx_cores, 38, p - 38, 2)         # 34 for MAC (12) EthType (2) IP (20)
183
184 def set_pkt_sizes_cpe(tx_cores, p):
185         send_all_pkt_size(tx_cores, p-4)
186         # For all cores, need to adapt IP Length (byte 16) and UDP Length (byte 38) to pkt size
187         send_all_value(tx_cores, 24, p - 26, 2)         # 22 for QinQ (8) MAC (12) EthType (2) 
188         send_all_value(tx_cores, 46, p - 46, 2)         # 42 for QinQ (8) MAC (12) EthType (2) IP (20)
189
190 def set_pkt_sizes_inet(tx_cores, p):
191         send_all_pkt_size(tx_cores, p+24-4)
192         # For all cores, need to adapt IP Length (byte 16) and UDP Length (byte 38) to pkt size
193         send_all_value(tx_cores, 20, p + 2, 2)          # 14 for MAC (12) EthType (2) 
194         send_all_value(tx_cores, 48, p - 26, 2)         # 14 for MAC (12) EthType (2) 
195         send_all_value(tx_cores, 70, p - 46, 2)         # 34 for MAC (12) EthType (2) IP (20)
196
197 def run_measure_throughput(speed, speed_cpe):
198         done = 0
199         # Intialize tests by stopping cores and resetting stats
200         step=0
201         steps_done = 0
202         sock.sendall("start " + to_str(all_rx_cores) + "\n")
203         sleep(2)
204         sock.sendall("stop " + to_str(all_rx_cores) + "\n")
205         sock.sendall("reset stats\n")
206         print "Speed    = " + str(speed * nb_cores_per_interface) 
207         sleep(1);
208         
209         send_all_speed(tx_cores, step);
210
211         # Now starting the steps. First go to the common speed, then increase steps for the faster one.
212         sock.sendall("start " + to_str(tx_cores) + "," + to_str(rx_lat_cores) + "\n")
213         while (steps_done == 0):
214                 sleep(step_time)
215                 if (step + step_delta <= speed):
216                         step+=step_delta
217                 else:
218                         steps_done = 1;
219                 send_all_speed(tx_cores, step)
220         
221         # Steps are now OK.  Set speed
222         send_all_speed(tx_cores_inet, speed);
223         send_all_speed(tx_cores_cpe, speed_cpe);
224         sleep(2);
225
226         # Getting statistics to calculate PPS at right speed....
227         rx_pps_beg,tx_pps_beg,drop_pps_beg,tsc_pps_beg,tsc_hz = rx_stats(tx_cores, tx_task, all_rx_cores, rx_task);
228         sleep(test_duration);
229
230         # Collect statistics before test stops...and stop the test. Important to get stats before stopping as stops take some time...
231         rx_pps_end,tx_pps_end,drop_pps_end,tsc_pps_end,tsc_hz = rx_stats(tx_cores, tx_task, all_rx_cores, rx_task);
232         lat_min,lat_max,lat_avg = lat_stats(rx_lat_cores, rx_task)
233         sock.sendall("stop " + to_str(tx_cores) + "\n")
234         sock.sendall("start " + to_str(all_rx_cores) + "\n")
235         sleep(3);
236         sock.sendall("stop " + to_str(all_rx_cores) + "\n")
237         
238         rx_end, tx_end,drop_end,tsc_end,tsc_hz = rx_stats(tx_cores, tx_task, all_rx_cores, rx_task);
239         rx = rx_pps_end - rx_pps_beg
240         tsc = tsc_pps_end - tsc_pps_beg
241         mpps = rx / (tsc/float(tsc_hz)) / 1000000
242         tx = tx_pps_end - tx_pps_beg
243         tx_mpps = tx / (tsc/float(tsc_hz)) / 1000000
244         
245         #print "Runtime = " +  str((tsc)/float(tsc_hz));
246         if (tx_end == 0):
247                 dropped_tot = tx_end - rx_end
248                 dropped_pct = 0
249         else:
250                 dropped_tot = tx_end - rx_end
251                 dropped_pct = ((dropped_tot) * 1.0) / tx_end
252
253         if (dropped_tot > 0):
254                 if (dropped_pct >= max_dropped):
255                         print "** FAILED **: lost " + str(100*dropped_pct) + "% packets RX = " + str(rx_end) + " TX = " + str(tx_end) + " DROPPED = " + str(tx_end - rx_end)
256                 else:
257                         print "OK but lost " + str(100*dropped_pct) + "% packets RX = " + str(rx_end) + " TX = " + str(tx_end) + " DROPPED = " + str(tx_end - rx_end)
258         else:
259                 if (dropped_tot < 0):
260                         print "Something wrong happened - received more packets than transmitted"
261                 else:
262                         print "**   OK   **: RX = " + str(rx_end) + " TX = " + str(tx_end) + " DROPPED = " + str(tx_end - rx_end) 
263         print "MPPS = " + str(mpps)
264         print "===================================================="
265         return dropped_pct, mpps, tx_mpps, dropped_tot,lat_min,lat_max,lat_avg
266
267 def write_results(f, pkt_size, tx_mpps, mpps, dropped_pct, dropped_tot, speed, nb_cores_per_interface, number_flows, lat_min, lat_max, lat_avg):
268         f.write(str(pkt_size) + "; " + str(tx_mpps) + "; " + str(mpps) + "; " + str(100 * dropped_pct) + "; " + str(dropped_tot) + "; " + str(speed * nb_cores_per_interface) + "; " + str(number_flows) +  "; " )
269         for e in rx_lat_cores:
270                 f.write(str(lat_min[e]) + "; " + str(lat_max[e]) + "; " + str(lat_avg[e]) + "; ")
271         f.write("\n");
272         f.flush()
273
274 def run_dicho_search(number_flows, pkt_size):
275         previous_success_speed = 0.0
276         previous_error_speed = max_speed
277         speed = init_speed * 1.0
278         done = 0;
279         good_tx_mpps = 0
280         good_mpps = 0
281         good_dropped_pct = 0
282         good_dropped_tot = 0
283         good_speed = 0
284         good_lat_min = [0 for e in range(127)]
285         good_lat_max = [0 for e in range(127)]
286         good_lat_avg = [0 for e in range(127)]
287
288         while done == 0:
289                 speed_cpe = (speed * (pkt_size + 20)) / (pkt_size + 24 + 20)
290                 dropped_pct, mpps, tx_mpps, dropped_tot,lat_min,lat_max,lat_avg = run_measure_throughput(speed, speed_cpe)
291                 if ((dropped_tot >= 0) and (dropped_pct <= max_dropped)):
292                         good_tx_mpps = tx_mpps
293                         good_mpps = mpps
294                         good_dropped_pct = dropped_pct
295                         good_dropped_tot = dropped_tot
296                         good_speed = speed
297                         good_lat_min = lat_min
298                         good_lat_max = lat_max
299                         good_lat_avg = lat_avg
300                         write_results(f, pkt_size, tx_mpps, mpps, dropped_pct, dropped_tot, speed, nb_cores_per_interface, number_flows, lat_min, lat_max, lat_avg);
301                         write_results(f_all, pkt_size, tx_mpps, mpps, dropped_pct, dropped_tot, speed, nb_cores_per_interface, number_flows, lat_min, lat_max, lat_avg);
302                 else:
303                         write_results(f_all, pkt_size, tx_mpps, mpps, dropped_pct, dropped_tot, speed, nb_cores_per_interface, number_flows, lat_min, lat_max, lat_avg);
304
305                 if ((speed == max_speed) and (dropped_pct <= max_dropped)):
306                         write_results(f_minimal, pkt_size, tx_mpps, mpps, dropped_pct, dropped_tot, speed, nb_cores_per_interface, number_flows, lat_min, lat_max, lat_avg);
307                         done = 1
308                 if (dropped_pct <= max_dropped):
309                         previous_success_speed = speed
310                         if (speed > max_speed - accuracy):
311                                 speed = max_speed
312                         else:
313                                 if (previous_error_speed - speed < accuracy):
314                                         write_results(f_minimal, pkt_size, good_tx_mpps, good_mpps, good_dropped_pct, good_dropped_tot, good_speed, nb_cores_per_interface, number_flows, good_lat_min, good_lat_max, good_lat_avg);
315                                         done = 1
316                                 else:
317                                         speed = speed + (previous_error_speed - speed)/2;
318                 else:
319                         previous_error_speed = speed
320                         if (speed - previous_success_speed < accuracy):
321                                 write_results(f_minimal, pkt_size, good_tx_mpps, good_mpps, good_dropped_pct, good_dropped_tot, good_speed, nb_cores_per_interface, number_flows, good_lat_min, good_lat_max, good_lat_avg);
322                                 done = 1        
323                         else:
324                                 speed  = speed - (speed - previous_success_speed) / 2;
325
326         
327 def set_source_destination_ip(nb_sources, nb_destinations):
328         # Destination addressese: "00XXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXX10"
329         # Starting with 00 to be in class A and skipping 0.x.y.z and 127.x.y.z
330         # Ending with 10 to avoid x.y.z.0 and x.y.z.255
331
332         dst_mask = "10"
333         for i in range (nb_destinations):
334                 dst_mask = "X" + str(dst_mask)
335         for i in range (32 - nb_destinations - 2):
336                 dst_mask = "0" + str(dst_mask)
337         
338         src_mask = "10"
339         for i in range (nb_sources):
340                 src_mask = "X" + str(src_mask)
341         for i in range (32 - nb_sources - 2):
342                 src_mask = "0" + str(src_mask)
343         
344         for c in tx_port0:
345                 send_all_random([c], 26, src_mask, 4)
346                 send_all_random([c], 30, dst_mask, 4)
347         for c in tx_port1:
348                 send_all_random([c], 26, src_mask, 4)
349                 send_all_random([c], 30, dst_mask, 4)
350         for c in tx_port2:
351                 send_all_random([c], 26, src_mask, 4)
352                 send_all_random([c], 30, dst_mask, 4)
353         for c in tx_port3:
354                 send_all_random([c], 26, src_mask, 4)
355                 send_all_random([c], 30, dst_mask, 4)
356         for c in tx_port4:
357                 send_all_random([c], 26, src_mask, 4)
358                 send_all_random([c], 30, dst_mask, 4)
359         for c in tx_port5:
360                 send_all_random([c], 26, src_mask, 4)
361                 send_all_random([c], 30, dst_mask, 4)
362         for c in tx_port6:
363                 send_all_random([c], 26, src_mask, 4)
364                 send_all_random([c], 30, dst_mask, 4)
365         for c in tx_port7:
366                 send_all_random([c], 26, src_mask, 4)
367                 send_all_random([c], 30, dst_mask, 4)
368                 
369 #========================================================================
370 class TestDefinition():
371     "Stores test parameters"
372     def __init__(self, number_ip_src, number_ip_dst, pkt_size):
373         self.number_ip_src = number_ip_src
374         self.number_ip_dst = number_ip_dst
375         self.pkt_size = pkt_size
376
377 #========================================================================
378 def run_use_case(number_ip_src, number_ip_dst, pkt_size):
379         number_flows = (2 ** number_ip_src) * (2 ** number_ip_dst)
380 #       send_reset_random()
381 #       send_reset_value()
382 #       set_source_destination_ip(number_ip_src, number_ip_dst)
383         set_pkt_sizes_inet(tx_cores_inet, pkt_size)
384         set_pkt_sizes_cpe(tx_cores_cpe, pkt_size)
385         print "Running test with pkt size= " + str(pkt_size) + " number_ip_src = " + str(number_ip_src) + " number_ip_dst = " + str(number_ip_dst) + " Number flows = " + str(number_flows) + "; \n"
386         run_dicho_search(number_flows, pkt_size)
387         sleep(3)
388
389 #========================================================================
390 def run_all_use_cases():
391         use_case_nb = 1
392         # Connect to dppd 
393         file_path = '/tmp/prox.sock'
394         sock.connect(file_path)
395
396         f.write("pkt_size; tx_mpps; rx_mpps; dropped_pct; dropped_tot; percent_line_rate; latency per core\n")
397         f_all.write("pkt_size; tx_mpps; rx_mpps; dropped_pct; dropped_tot; percent_line_rate; latency per core\n")
398         f_minimal.write("pkt_size; tx_mpps; rx_mpps; dropped_pct; dropped_tot; percent_line_rate; latency per core\n")
399         f.flush();
400         f_all.flush();
401         f_minimal.flush();
402
403         # Starting tests
404         print "Stopping all cores and resetting all values and randoms before starting\n"
405         sock.sendall("stop " + to_str(all_rx_cores) + "\n")
406         sock.sendall("stop " + to_str(tx_cores) + "\n")
407         #sock.sendall("stop all")
408         sock.sendall("reset stats\n")
409         sleep(3);
410         for line in file_tests:
411                 info = line.split(';')
412                 if (info[0][0] == '#'):
413                         continue
414                 if (info[0][0] == ''):
415                         break
416                 number_ip_src = int(info[0])
417                 number_ip_dst = int(info[1])
418                 pkt_size = int(info[2])
419                 run_use_case(number_ip_src, number_ip_dst, pkt_size)
420
421 #========================================================================
422 def configure_use_case():
423         Tests = []
424         number_ip_dst = 0
425         number_ip_src = 0
426         for pkt_size in all_pkt_size:
427                 Tests.append(TestDefinition(number_ip_src, number_ip_dst, pkt_size))
428
429         pkt_size = 64
430         while (pkt_size < 1494):
431                 Tests.append(TestDefinition(number_ip_src, number_ip_dst, pkt_size))
432                 pkt_size = (pkt_size *11) / 10
433
434         file_tests = open('test_description.txt', 'w')
435         file_tests.write("# Number_ip_src; number_ip_dst; pkt_size; \n")
436         for test in Tests:
437                 file_tests.write(str(test.number_ip_src) + "; " + str(test.number_ip_dst) + "; " + str(test.pkt_size) + "; " + ";\n")
438         file_tests.close()
439
440 #========================================================================
441 if ((configure == 0) and (run == 0)):
442         print "Nothing to do - please use -r 1 or -c 1"
443 if (configure == 1):
444         configure_use_case()
445 if (run == 1):
446         print "****************************************************************************************************************"
447         print "** Running Characterization with " + str(test_duration) + " seconds steps and starting at " + str(init_speed)   + " percent of line rate **"
448         print "****************************************************************************************************************"
449         sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
450         f_all = open('all_results.txt', 'w')
451         f = open('detailed_results.txt', 'w')
452         f_minimal = open('minimal_results.txt', 'w')
453         file_tests = open('test_description.txt', 'r')
454         run_all_use_cases()
455         f.close();
456         sock.close();
457