Fixed compilation issue on dpdk 19.08 (and more recent)
[samplevnf.git] / VNFs / DPPD-PROX / helper-scripts / rapid / rapid_test.py
1 #!/usr/bin/python
2
3 ##
4 ## Copyright (c) 2020 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 ##
11 ##     http://www.apache.org/licenses/LICENSE-2.0
12 ##
13 ## Unless required by applicable law or agreed to in writing, software
14 ## distributed under the License is distributed on an "AS IS" BASIS,
15 ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 ## See the License for the specific language governing permissions and
17 ## limitations under the License.
18 ##
19
20 import time
21 from past.utils import old_div
22 from rapid_log import RapidLog
23 from rapid_log import bcolors
24 inf = float("inf")
25
26 class RapidTest(object):
27     """
28     Class to manage the testing
29     """
30     def __init__(self, test_param,  runtime, pushgateway, environment_file ):
31         self.test = test_param
32         self.test['runtime'] = runtime
33         self.test['pushgateway'] = pushgateway
34         self.test['environment_file'] = environment_file
35         if 'maxr' not in self.test.keys():
36             self.test['maxr'] = 1
37         if 'maxz' not in self.test.keys():
38             self.test['maxz'] = inf
39
40     @staticmethod
41     def get_percentageof10Gbps(pps_speed,size):
42         # speed is given in pps, returning % of 10Gb/s
43         # 12 bytes is the inter packet gap 
44         # pre-amble is 7 bytes
45         # SFD (start of frame delimiter) is 1 byte
46         # Total of 20 bytes overhead per packet
47         return (pps_speed / 1000000.0 * 0.08 * (size+20))
48
49     @staticmethod
50     def get_pps(speed,size):
51         # speed is given in % of 10Gb/s, returning Mpps
52         # 12 bytes is the inter packet gap 
53         # pre-amble is 7 bytes
54         # SFD (start of frame delimiter) is 1 byte
55         # Total of 20 bytes overhead per packet
56         return (speed * 100.0 / (8*(size+20)))
57
58     @staticmethod
59     def get_speed(packet_speed,size):
60         # return speed in Gb/s
61         # 12 bytes is the inter packet gap 
62         # pre-amble is 7 bytes
63         # SFD (start of frame delimiter) is 1 byte
64         # Total of 20 bytes overhead per packet
65         return (packet_speed / 1000.0 * (8*(size+20)))
66
67     @staticmethod
68     def set_background_flows(background_machines, number_of_flows):
69         for machine in background_machines:
70             _ = machine.set_flows(number_of_flows)
71
72     @staticmethod
73     def set_background_speed(background_machines, speed):
74         for machine in background_machines:
75             machine.set_generator_speed(speed)
76
77     @staticmethod
78     def set_background_size(background_machines, imix):
79         # imixs is a list of packet sizes
80         for machine in background_machines:
81             machine.set_udp_packet_size(imix)
82
83     @staticmethod
84     def start_background_traffic(background_machines):
85         for machine in background_machines:
86             machine.start()
87
88     @staticmethod
89     def stop_background_traffic(background_machines):
90         for machine in background_machines:
91             machine.stop()
92
93     @staticmethod
94     def report_result(flow_number, size, speed, pps_req_tx, pps_tx, pps_sut_tx,
95         pps_rx, lat_avg, lat_perc, lat_perc_max, lat_max, tx, rx, tot_drop,
96         elapsed_time,speed_prefix='', lat_avg_prefix='', lat_perc_prefix='',
97         lat_max_prefix='', abs_drop_rate_prefix='', drop_rate_prefix=''):
98         if flow_number < 0:
99             flow_number_str = '| ({:>4}) |'.format(abs(flow_number))
100         else:
101             flow_number_str = '|{:>7} |'.format(flow_number)
102         if pps_req_tx is None:
103             pps_req_tx_str = '{0: >14}'.format('   NA     |')
104         else:
105             pps_req_tx_str = '{:>7.3f} Mpps |'.format(pps_req_tx)
106         if pps_tx is None:
107             pps_tx_str = '{0: >14}'.format('   NA     |')
108         else:
109             pps_tx_str = '{:>7.3f} Mpps |'.format(pps_tx) 
110         if pps_sut_tx is None:
111             pps_sut_tx_str = '{0: >14}'.format('   NA     |')
112         else:
113             pps_sut_tx_str = '{:>7.3f} Mpps |'.format(pps_sut_tx)
114         if pps_rx is None:
115             pps_rx_str = '{0: >25}'.format('NA        |')
116         else:
117             pps_rx_str = bcolors.OKBLUE + '{:>4.1f} Gb/s |{:7.3f} Mpps {}|'.format(RapidTest.get_speed(pps_rx,size),pps_rx,bcolors.ENDC)
118         if tot_drop is None:
119             tot_drop_str = ' |       NA  | '
120         else:
121             tot_drop_str = ' | {:>9.0f} | '.format(tot_drop)
122         if lat_perc is None:
123             lat_perc_str = ' |{:^10.10}|'.format('NA')
124         elif lat_perc_max == True:
125             lat_perc_str = '|>{}{:>5.0f} us{} |'.format(lat_perc_prefix,float(lat_perc), bcolors.ENDC) 
126         else:
127             lat_perc_str = '| {}{:>5.0f} us{} |'.format(lat_perc_prefix,float(lat_perc), bcolors.ENDC) 
128         if elapsed_time is None:
129             elapsed_time_str = ' NA |'
130         else:
131             elapsed_time_str = '{:>3.0f} |'.format(elapsed_time)
132         return(flow_number_str + '{:>5.1f}'.format(speed) + '% '+speed_prefix +'{:>6.3f}'.format(RapidTest.get_pps(speed,size)) + ' Mpps|'+ pps_req_tx_str + pps_tx_str + bcolors.ENDC + pps_sut_tx_str + pps_rx_str +lat_avg_prefix+ ' {:>6.0f}'.format(lat_avg)+' us'+lat_perc_str+lat_max_prefix+'{:>6.0f}'.format(lat_max)+' us | ' + '{:>9.0f}'.format(tx) + ' | {:>9.0f}'.format(rx) + ' | '+ abs_drop_rate_prefix+ '{:>9.0f}'.format(tx-rx) + tot_drop_str +drop_rate_prefix+ '{:>5.2f}'.format(old_div(float(tx-rx),tx))  +bcolors.ENDC+' |' + elapsed_time_str)
133             
134     def run_iteration(self, requested_duration, flow_number, size, speed):
135         BUCKET_SIZE_EXP = self.gen_machine.bucket_size_exp
136         LAT_PERCENTILE = self.test['lat_percentile']
137         r = 0;
138         sleep_time = 2
139         while (r < self.test['maxr']):
140             time.sleep(sleep_time)
141             # Sleep_time is needed to be able to do accurate measurements to check for packet loss. We need to make this time large enough so that we do not take the first measurement while some packets from the previous tests migth still be in flight
142             t1_rx, t1_non_dp_rx, t1_tx, t1_non_dp_tx, t1_drop, t1_tx_fail, t1_tsc, abs_tsc_hz = self.gen_machine.core_stats()
143             t1_dp_rx = t1_rx - t1_non_dp_rx
144             t1_dp_tx = t1_tx - t1_non_dp_tx
145             self.gen_machine.start_gen_cores()
146             time.sleep(2) ## Needs to be 2 seconds since this 1 sec is the time that PROX uses to refresh the stats. Note that this can be changed in PROX!! Don't do it.
147             if self.sut_machine!= None:
148                 t2_sut_rx, t2_sut_non_dp_rx, t2_sut_tx, t2_sut_non_dp_tx, t2_sut_drop, t2_sut_tx_fail, t2_sut_tsc, sut_tsc_hz = self.sut_machine.core_stats()
149             t2_rx, t2_non_dp_rx, t2_tx, t2_non_dp_tx, t2_drop, t2_tx_fail, t2_tsc, tsc_hz = self.gen_machine.core_stats()
150             tx = t2_tx - t1_tx
151             dp_tx =  tx - (t2_non_dp_tx - t1_non_dp_tx )
152             dp_rx =  t2_rx - t1_rx - (t2_non_dp_rx - t1_non_dp_rx) 
153             tot_dp_drop = dp_tx - dp_rx
154             if tx == 0:
155                 RapidLog.critical("TX = 0. Test interrupted since no packet has been sent.")
156             if dp_tx == 0:
157                 RapidLog.critical("Only non-dataplane packets (e.g. ARP) sent. Test interrupted since no packet has been sent.")
158             # Ask PROX to calibrate the bucket size once we have a PROX function to do this.
159             # Measure latency statistics per second
160             lat_min, lat_max, lat_avg, used_avg, t2_lat_tsc, lat_hz, buckets = self.gen_machine.lat_stats()
161             lat_samples = sum(buckets)
162             sample_count = 0
163             for sample_percentile, bucket in enumerate(buckets,start=1):
164                 sample_count += bucket
165                 if sample_count > (lat_samples * LAT_PERCENTILE):
166                     break
167             percentile_max = (sample_percentile == len(buckets))
168             sample_percentile = sample_percentile *  float(2 ** BUCKET_SIZE_EXP) / (old_div(float(lat_hz),float(10**6)))
169             if self.test['test'] == 'fixed_rate':
170                 RapidLog.info(self.report_result(flow_number,size,speed,None,None,None,None,lat_avg,sample_percentile,percentile_max,lat_max, dp_tx, dp_rx , None, None))
171             tot_rx = tot_non_dp_rx = tot_tx = tot_non_dp_tx = tot_drop = 0
172             lat_avg = used_avg = 0
173             buckets_total = [0] * 128
174             tot_lat_samples = 0
175             tot_lat_measurement_duration = float(0)
176             tot_core_measurement_duration = float(0)
177             tot_sut_core_measurement_duration = float(0)
178             tot_sut_rx = tot_sut_non_dp_rx = tot_sut_tx = tot_sut_non_dp_tx = tot_sut_drop = tot_sut_tx_fail = tot_sut_tsc = 0
179             lat_avail = core_avail = sut_avail = False
180             while (tot_core_measurement_duration - float(requested_duration) <= 0.1) or (tot_lat_measurement_duration - float(requested_duration) <= 0.1):
181                 time.sleep(0.5)
182                 lat_min_sample, lat_max_sample, lat_avg_sample, used_sample, t3_lat_tsc, lat_hz, buckets = self.gen_machine.lat_stats()
183                 # Get statistics after some execution time
184                 if t3_lat_tsc != t2_lat_tsc:
185                     single_lat_measurement_duration = (t3_lat_tsc - t2_lat_tsc) * 1.0 / lat_hz  # time difference between the 2 measurements, expressed in seconds.
186                     # A second has passed in between to lat_stats requests. Hence we need to process the results
187                     tot_lat_measurement_duration = tot_lat_measurement_duration + single_lat_measurement_duration
188                     if lat_min > lat_min_sample:
189                         lat_min = lat_min_sample
190                     if lat_max < lat_max_sample:
191                         lat_max = lat_max_sample
192                     lat_avg = lat_avg + lat_avg_sample * single_lat_measurement_duration # Sometimes, There is more than 1 second between 2 lat_stats. Hence we will take the latest measurement
193                     used_avg = used_avg + used_sample * single_lat_measurement_duration  # and give it more weigth.
194                     lat_samples = sum(buckets)
195                     tot_lat_samples += lat_samples
196                     sample_count = 0
197                     for sample_percentile, bucket in enumerate(buckets,start=1):
198                         sample_count += bucket
199                         if sample_count > lat_samples * LAT_PERCENTILE:
200                             break
201                     percentile_max = (sample_percentile == len(buckets))
202                     sample_percentile = sample_percentile *  float(2 ** BUCKET_SIZE_EXP) / (old_div(float(lat_hz),float(10**6)))
203                     buckets_total = [buckets_total[i] + buckets[i] for i in range(len(buckets_total))] 
204                     t2_lat_tsc = t3_lat_tsc
205                     lat_avail = True
206                 t3_rx, t3_non_dp_rx, t3_tx, t3_non_dp_tx, t3_drop, t3_tx_fail, t3_tsc, tsc_hz = self.gen_machine.core_stats()
207                 if t3_tsc != t2_tsc:
208                     single_core_measurement_duration = (t3_tsc - t2_tsc) * 1.0 / tsc_hz  # time difference between the 2 measurements, expressed in seconds.
209                     tot_core_measurement_duration = tot_core_measurement_duration + single_core_measurement_duration
210                     delta_rx = t3_rx - t2_rx
211                     tot_rx += delta_rx
212                     delta_non_dp_rx = t3_non_dp_rx - t2_non_dp_rx
213                     tot_non_dp_rx += delta_non_dp_rx
214                     delta_tx = t3_tx - t2_tx
215                     tot_tx += delta_tx
216                     delta_non_dp_tx = t3_non_dp_tx - t2_non_dp_tx
217                     tot_non_dp_tx += delta_non_dp_tx
218                     delta_dp_tx = delta_tx -delta_non_dp_tx
219                     delta_dp_rx = delta_rx -delta_non_dp_rx
220                     delta_dp_drop = delta_dp_tx - delta_dp_rx
221                     tot_dp_drop += delta_dp_drop
222                     delta_drop = t3_drop - t2_drop
223                     tot_drop += delta_drop
224                     t2_rx, t2_non_dp_rx, t2_tx, t2_non_dp_tx, t2_drop, t2_tx_fail, t2_tsc = t3_rx, t3_non_dp_rx, t3_tx, t3_non_dp_tx, t3_drop, t3_tx_fail, t3_tsc
225                     core_avail = True
226                 if self.sut_machine!=None:
227                     t3_sut_rx, t3_sut_non_dp_rx, t3_sut_tx, t3_sut_non_dp_tx, t3_sut_drop, t3_sut_tx_fail, t3_sut_tsc, sut_tsc_hz = self.sut_machine.core_stats()
228                     if t3_sut_tsc != t2_sut_tsc:
229                         single_sut_core_measurement_duration = (t3_sut_tsc - t2_sut_tsc) * 1.0 / tsc_hz  # time difference between the 2 measurements, expressed in seconds.
230                         tot_sut_core_measurement_duration = tot_sut_core_measurement_duration + single_sut_core_measurement_duration
231                         tot_sut_rx += t3_sut_rx - t2_sut_rx
232                         tot_sut_non_dp_rx += t3_sut_non_dp_rx - t2_sut_non_dp_rx
233                         delta_sut_tx = t3_sut_tx - t2_sut_tx
234                         tot_sut_tx += delta_sut_tx
235                         delta_sut_non_dp_tx = t3_sut_non_dp_tx - t2_sut_non_dp_tx
236                         tot_sut_non_dp_tx += delta_sut_non_dp_tx 
237                         t2_sut_rx, t2_sut_non_dp_rx, t2_sut_tx, t2_sut_non_dp_tx, t2_sut_drop, t2_sut_tx_fail, t2_sut_tsc = t3_sut_rx, t3_sut_non_dp_rx, t3_sut_tx, t3_sut_non_dp_tx, t3_sut_drop, t3_sut_tx_fail, t3_sut_tsc
238                         sut_avail = True
239                 if self.test['test'] == 'fixed_rate':
240                     if lat_avail == core_avail == True:
241                         lat_avail = core_avail = False
242                         pps_req_tx = (delta_tx + delta_drop - delta_rx)/single_core_measurement_duration/1000000
243                         pps_tx = delta_tx/single_core_measurement_duration/1000000
244                         if self.sut_machine != None and sut_avail:
245                             pps_sut_tx = delta_sut_tx/single_sut_core_measurement_duration/1000000
246                             sut_avail = False
247                         else:
248                             pps_sut_tx = None
249                         pps_rx = delta_rx/single_core_measurement_duration/1000000
250                         RapidLog.info(self.report_result(flow_number, size,
251                             speed, pps_req_tx, pps_tx, pps_sut_tx, pps_rx,
252                             lat_avg_sample, sample_percentile, percentile_max,
253                             lat_max_sample, delta_dp_tx, delta_dp_rx,
254                             tot_dp_drop, single_core_measurement_duration))
255             #Stop generating
256             self.gen_machine.stop_gen_cores()
257             r += 1
258             lat_avg = old_div(lat_avg, float(tot_lat_measurement_duration))
259             used_avg = old_div(used_avg, float(tot_lat_measurement_duration))
260             t4_tsc = t2_tsc
261             while t4_tsc == t2_tsc:
262                 t4_rx, t4_non_dp_rx, t4_tx, t4_non_dp_tx, t4_drop, t4_tx_fail, t4_tsc, abs_tsc_hz = self.gen_machine.core_stats()
263             if self.test['test'] == 'fixed_rate':
264                 t4_lat_tsc = t2_lat_tsc
265                 while t4_lat_tsc == t2_lat_tsc:
266                     lat_min_sample, lat_max_sample, lat_avg_sample, used_sample, t4_lat_tsc, lat_hz, buckets = self.gen_machine.lat_stats()
267                 sample_count = 0
268                 lat_samples = sum(buckets)
269                 for percentile, bucket in enumerate(buckets,start=1):
270                     sample_count += bucket
271                     if sample_count > lat_samples * LAT_PERCENTILE:
272                         break
273                 percentile_max = (percentile == len(buckets))
274                 percentile = percentile *  float(2 ** BUCKET_SIZE_EXP) / (old_div(float(lat_hz),float(10**6)))
275                 lat_max = lat_max_sample
276                 lat_avg = lat_avg_sample
277                 delta_rx = t4_rx - t2_rx
278                 delta_non_dp_rx = t4_non_dp_rx - t2_non_dp_rx
279                 delta_tx = t4_tx - t2_tx
280                 delta_non_dp_tx = t4_non_dp_tx - t2_non_dp_tx
281                 delta_dp_tx = delta_tx -delta_non_dp_tx
282                 delta_dp_rx = delta_rx -delta_non_dp_rx
283                 dp_tx = delta_dp_tx
284                 dp_rx = delta_dp_rx
285                 tot_dp_drop += delta_dp_tx - delta_dp_rx
286                 pps_req_tx = None
287                 pps_tx = None
288                 pps_sut_tx = None
289                 pps_rx = None
290                 drop_rate = 100.0*(dp_tx-dp_rx)/dp_tx
291                 tot_core_measurement_duration = None
292                 break ## Not really needed since the while loop will stop when evaluating the value of r
293             else:
294                 sample_count = 0
295                 for percentile, bucket in enumerate(buckets_total,start=1):
296                     sample_count += bucket
297                     if sample_count > tot_lat_samples * LAT_PERCENTILE:
298                         break
299                 percentile_max = (percentile == len(buckets_total))
300                 percentile = percentile *  float(2 ** BUCKET_SIZE_EXP) / (old_div(float(lat_hz),float(10**6)))
301                 pps_req_tx = (tot_tx + tot_drop - tot_rx)/tot_core_measurement_duration/1000000.0 # tot_drop is all packets dropped by all tasks. This includes packets dropped at the generator task + packets dropped by the nop task. In steady state, this equals to the number of packets received by this VM
302                 pps_tx = tot_tx/tot_core_measurement_duration/1000000.0 # tot_tx is all generated packets actually accepted by the interface
303                 pps_rx = tot_rx/tot_core_measurement_duration/1000000.0 # tot_rx is all packets received by the nop task = all packets received in the gen VM
304                 if self.sut_machine != None and sut_avail:
305                     pps_sut_tx = tot_sut_tx / tot_sut_core_measurement_duration / 1000000.0
306                 else:
307                     pps_sut_tx = None
308                 dp_tx = (t4_tx - t1_tx) - (t4_non_dp_tx - t1_non_dp_tx)
309                 dp_rx = (t4_rx - t1_rx) - (t4_non_dp_rx - t1_non_dp_rx)
310                 tot_dp_drop = dp_tx - dp_rx
311                 drop_rate = 100.0*tot_dp_drop/dp_tx
312                 if ((drop_rate < self.test['drop_rate_threshold']) or (tot_dp_drop == self.test['drop_rate_threshold'] ==0) or (tot_dp_drop > self.test['maxz'])):
313                     break
314         return(pps_req_tx,pps_tx,pps_sut_tx,pps_rx,lat_avg,percentile,percentile_max,lat_max,dp_tx,dp_rx,tot_dp_drop,(t4_tx_fail - t1_tx_fail),drop_rate,lat_min,used_avg,r,tot_core_measurement_duration)